diff options
1052 files changed, 13051 insertions, 5216 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml index e5636a13783..42afed54371 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -10,10 +10,10 @@ engines: languages: - ruby - javascript + exclude_paths: + - "lib/api/v3/*" eslint: enabled: true - fixme: - enabled: true rubocop: enabled: true ratings: @@ -35,4 +35,13 @@ exclude_paths: - node_modules/ - spec/ - vendor/ -- lib/api/v3/ +- .yarn-cache/ +- tmp/ +- builds/ +- coverage/ +- public/ +- shared/ +- webpack-report/ +- log/ +- backups/ +- coverage-javascript/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b442e48a3d0..f0c266485b6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -441,21 +441,40 @@ gitlab:assets:compile: - webpack-report/ karma: + image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6" stage: test <<: *use-pg <<: *dedicated-runner <<: *except-docs variables: BABEL_ENV: "coverage" + CHROME_LOG_FILE: "chrome_debug.log" script: - bundle exec rake karma coverage: '/^Statements *: (\d+\.\d+%)/' artifacts: name: coverage-javascript expire_in: 31d + when: always paths: + - chrome_debug.log - coverage-javascript/ +codeclimate: + before_script: [] + image: docker:latest + stage: test + variables: + SETUP_DB: "false" + DOCKER_DRIVER: overlay + services: + - docker:dind + script: + - docker pull codeclimate/codeclimate + - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json + artifacts: + paths: [codeclimate.json] + coverage: stage: post-test services: [] diff --git a/.rubocop.yml b/.rubocop.yml index 66a40f2cf57..4537e710dd4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1064,6 +1064,13 @@ RSpec/NotToNot: RSpec/RepeatedDescription: Enabled: false +# Ensure RSpec hook blocks are always multi-line. +RSpec/SingleLineHook: + Enabled: true + Exclude: + - 'spec/factories/*' + - 'spec/requests/api/v3/*' + # Checks for stubbed test subjects. RSpec/SubjectStub: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index e5567dc3b39..f43858a00a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.2.6 (2017-06-16) + +- Fix the last coverage in trace log should be extracted. !11128 (dosuken123) +- Respect merge, instead of push, permissions for protected actions. !11648 +- Fix pipeline_schedules pages throwing error 500. !11706 (dosuken123) +- Make backup task to continue on corrupt repositories. !11962 +- Fix incorrect ETag cache key when relative instance URL is used. !11964 +- Fix math rendering on blob pages. +- Invalidate cache for issue and MR counters more granularly. +- Fix terminals support for Kubernetes Service. +- Fix LFS timeouts when trying to save large files. +- Strip trailing whitespaces in submodule URLs. +- Make sure reCAPTCHA configuration is loaded when spam checks are initiated. +- Remove foreigh key on ci_trigger_schedules only if it exists. + ## 9.2.5 (2017-06-07) - No changes. diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 6680834a8d1..56fa0d71a9a 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -77,7 +77,7 @@ const Api = { dataType: 'json', }) .done(label => callback(label)) - .error(message => callback(message.responseJSON)); + .fail(message => callback(message.responseJSON)); }, // Return group projects list. Filtered by query @@ -134,7 +134,7 @@ const Api = { dataType: 'json', }) .done(file => callback(null, file)) - .error(callback); + .fail(callback); }, users(query, options) { diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index b37698fe9ca..3f083655f95 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -11,7 +11,6 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager { // Issue boards is slightly different, we handle all the requests async // instead or reloading the page, we just re-fire the list ajax requests this.isHandledAsync = true; - this.cantEdit = cantEdit; } diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 082fbafb740..70ba83ce5b9 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Visibility from 'visibilityjs'; -import pipelinesTableComponent from '../../vue_shared/components/pipelines_table'; +import pipelinesTableComponent from '../../vue_shared/components/pipelines_table.vue'; import PipelinesService from '../../pipelines/services/pipelines_service'; import PipelineStore from '../../pipelines/stores/pipelines_store'; import eventHub from '../../pipelines/event_hub'; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 5f87a05067b..88b4b567fa9 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -79,7 +79,18 @@ import initSettingsPanels from './settings_panels'; path = page.split(':'); shortcut_handler = null; - new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(); + $('.js-gfm-input').each((i, el) => { + const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); + const enableGFM = gl.utils.convertPermissionToBoolean(el.dataset.supportsAutocomplete); + gfm.setup($(el), { + emojis: true, + members: enableGFM, + issues: enableGFM, + milestones: enableGFM, + mergeRequests: enableGFM, + labels: enableGFM, + }); + }); function initBlob() { new LineHighlighter(); @@ -176,7 +187,7 @@ import initSettingsPanels from './settings_panels'; case 'groups:milestones:update': new ZenMode(); new gl.DueDateSelectors(); - new gl.GLForm($('.milestone-form')); + new gl.GLForm($('.milestone-form'), true); break; case 'projects:compare:show': new gl.Diff(); @@ -188,7 +199,7 @@ import initSettingsPanels from './settings_panels'; case 'projects:issues:new': case 'projects:issues:edit': shortcut_handler = new ShortcutsNavigation(); - new gl.GLForm($('.issue-form')); + new gl.GLForm($('.issue-form'), true); new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); @@ -199,7 +210,7 @@ import initSettingsPanels from './settings_panels'; case 'projects:merge_requests:edit': new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); - new gl.GLForm($('.merge-request-form')); + new gl.GLForm($('.merge-request-form'), true); new IssuableForm($('.merge-request-form')); new LabelsSelect(); new MilestoneSelect(); @@ -208,22 +219,24 @@ import initSettingsPanels from './settings_panels'; break; case 'projects:tags:new': new ZenMode(); - new gl.GLForm($('.tag-form')); + new gl.GLForm($('.tag-form'), true); new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs); break; case 'projects:snippets:new': case 'projects:snippets:edit': case 'projects:snippets:create': case 'projects:snippets:update': + new gl.GLForm($('.snippet-form'), true); + break; case 'snippets:new': case 'snippets:edit': case 'snippets:create': case 'snippets:update': - new gl.GLForm($('.snippet-form')); + new gl.GLForm($('.snippet-form'), false); break; case 'projects:releases:edit': new ZenMode(); - new gl.GLForm($('.release-form')); + new gl.GLForm($('.release-form'), true); break; case 'projects:merge_requests:show': new gl.Diff(); @@ -471,7 +484,7 @@ import initSettingsPanels from './settings_panels'; new gl.Wikis(); shortcut_handler = new ShortcutsWiki(); new ZenMode(); - new gl.GLForm($('.wiki-form')); + new gl.GLForm($('.wiki-form'), true); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 98ddcc20036..73675d300be 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -287,6 +287,10 @@ window.DropzoneInput = (function() { $uploadingErrorMessage.html(message); }; + closeAlertMessage = function() { + return form.find('.div-dropzone-alert').alert('close'); + }; + form.find('.markdown-selector').click(function(e) { e.preventDefault(); $(this).closest('.gfm-form').find('.div-dropzone').click(); diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index de2269118cd..809c147bf25 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -9,7 +9,7 @@ import StopComponent from './environment_stop.vue'; import RollbackComponent from './environment_rollback.vue'; import TerminalButtonComponent from './environment_terminal_button.vue'; import MonitoringButtonComponent from './environment_monitoring.vue'; -import CommitComponent from '../../vue_shared/components/commit'; +import CommitComponent from '../../vue_shared/components/commit.vue'; import eventHub from '../event_hub'; /** @@ -535,10 +535,10 @@ export default { </span> </div> - <div class="table-section section-30 environments-actions table-button-footer" role="gridcell"> + <div class="table-section section-30 table-button-footer" role="gridcell"> <div v-if="!model.isFolder" - class="btn-group environment-action-buttons" + class="btn-group table-action-buttons" role="group"> <actions-component diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index 8a2f6a473de..a5773dd7e4f 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -158,5 +158,4 @@ export default class EnvironmentsStore { return environments.filter(env => env.isFolder && env.isOpen); } - } diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index 2af242a69df..5838b1bdbb7 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -56,7 +56,7 @@ class DropdownHint extends gl.FilteredSearchDropdown { } renderContent() { - const dropdownData = gl.FilteredSearchTokenKeys.get() + const dropdownData = this.tokenKeys.get() .map(tokenKey => ({ icon: `fa-${tokenKey.icon}`, hint: tokenKey.key, diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 401dec1a370..105762cb1ba 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -34,7 +34,7 @@ class GfmAutoComplete { const $input = $(input); $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input)); // This triggers at.js again - // Needed for slash commands with suffixes (ex: /label ~) + // Needed for quick actions with suffixes (ex: /label ~) $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup')); $input.on('clear-commands-cache.atwho', () => this.clearCache()); }); @@ -48,8 +48,8 @@ class GfmAutoComplete { if (this.enableMap.mergeRequests) this.setupMergeRequests($input); if (this.enableMap.labels) this.setupLabels($input); - // We don't instantiate the slash commands autocomplete for note and issue/MR edit forms - $input.filter('[data-supports-slash-commands="true"]').atwho({ + // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms + $input.filter('[data-supports-quick-actions="true"]').atwho({ at: '/', alias: 'commands', searchKey: 'search', diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 32815b9f73e..b1db34b9c50 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -27,7 +27,7 @@ export default { if (this.group.hasSubgroups) { eventHub.$emit('toggleSubGroups', this.group); } else { - window.location.href = this.group.webUrl; + window.location.href = this.group.groupPath; } } }, @@ -192,7 +192,7 @@ export default { <div class="avatar-container s40 hidden-xs"> <a - :href="group.webUrl"> + :href="group.groupPath"> <img class="avatar s40" :src="group.avatarUrl" @@ -202,7 +202,7 @@ export default { <div class="title"> <a - :href="group.webUrl">{{fullPath}}</a> + :href="group.groupPath">{{fullPath}}</a> <template v-if="group.permissions.humanGroupAccess"> as <span class="access-type">{{group.permissions.humanGroupAccess}}</span> diff --git a/app/assets/javascripts/groups/stores/groups_store.js b/app/assets/javascripts/groups/stores/groups_store.js index 67ee7d140ce..f6dc4290fd5 100644 --- a/app/assets/javascripts/groups/stores/groups_store.js +++ b/app/assets/javascripts/groups/stores/groups_store.js @@ -122,6 +122,7 @@ export default class GroupsStore { canEdit: rawGroup.can_edit, description: rawGroup.description, webUrl: rawGroup.web_url, + groupPath: rawGroup.group_path, parentId: rawGroup.parent_id, visibility: rawGroup.visibility, leavePath: rawGroup.leave_path, diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue index 30a1be5cb50..54650d2f184 100644 --- a/app/assets/javascripts/issue_show/components/fields/description.vue +++ b/app/assets/javascripts/issue_show/components/fields/description.vue @@ -41,7 +41,7 @@ <textarea id="issue-description" class="note-textarea js-gfm-input js-autosize markdown-area" - data-supports-slash-commands="false" + data-supports-quick-actionss="false" aria-label="Description" v-model="formState.description" ref="textarea" diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 54c0da3fc9c..bfcc50996cc 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -34,7 +34,7 @@ window.dateFormat = dateFormat; w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) { $timeagoEls.each((i, el) => { - el.setAttribute('title', gl.utils.formatDate(el.getAttribute('datetime'))); + el.setAttribute('title', el.getAttribute('title')); if (setTimeago) { // Recreate with custom template diff --git a/app/assets/javascripts/locale/en/app.js b/app/assets/javascripts/locale/en/app.js index 0bb76c80b7a..d634af959e5 100644 --- a/app/assets/javascripts/locale/en/app.js +++ b/app/assets/javascripts/locale/en/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['en'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:36-0500","Last-Translator":"FULL NAME <EMAIL@ADDRESS>","Language-Team":"English","Language":"en","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"en","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":[""],"Cancel":[""],"Commit":["",""],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Delete":[""],"Deploy":["",""],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Interval Pattern":[""],"Introducing Cycle Analytics":[""],"Last %d day":["",""],"Last Pipeline":[""],"Limited to showing %d event at most":["",""],"Median":[""],"New Issue":["",""],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":[""],"Not enough data":[""],"OpenedNDaysAgo|Opened":[""],"Owner":[""],"Pipeline Health":[""],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":[""],"Read more":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["",""],"Target Branch":[""],"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.":[""],"The collection of events added to the data gathered for that stage.":[""],"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.":[""],"The phase of the development lifecycle.":[""],"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.":[""],"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.":[""],"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.":[""],"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.":[""],"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.":[""],"The time taken by each data entry gathered by that stage.":[""],"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.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"You need permission.":[""],"day":["",""]}}};
\ No newline at end of file +var locales = locales || {}; locales['en'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:36-0500","Last-Translator":"FULL NAME <EMAIL@ADDRESS>","Language-Team":"English","Language":"en","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"en","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":[""],"Active":[""],"Activity":[""],"Add Changelog":[""],"Add Contribution guide":[""],"Add License":[""],"Add an SSH key to your profile to pull or push via SSH.":[""],"Add new directory":[""],"Archived project! Repository is read-only":[""],"Are you sure you want to delete this pipeline schedule?":[""],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["",""],"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}":[""],"Branches":[""],"Browse files":[""],"ByAuthor|by":[""],"CI configuration":[""],"Cancel":[""],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"Changelog":[""],"Charts":[""],"Cherry-pick this commit":[""],"Cherry-pick this merge request":[""],"CiStatusLabel|canceled":[""],"CiStatusLabel|created":[""],"CiStatusLabel|failed":[""],"CiStatusLabel|manual action":[""],"CiStatusLabel|passed":[""],"CiStatusLabel|passed with warnings":[""],"CiStatusLabel|pending":[""],"CiStatusLabel|skipped":[""],"CiStatusLabel|waiting for manual action":[""],"CiStatusText|blocked":[""],"CiStatusText|canceled":[""],"CiStatusText|created":[""],"CiStatusText|failed":[""],"CiStatusText|manual":[""],"CiStatusText|passed":[""],"CiStatusText|pending":[""],"CiStatusText|skipped":[""],"CiStatus|running":[""],"Commit":["",""],"Commit message":[""],"CommitBoxTitle|Commit":[""],"CommitMessage|Add %{file_name}":[""],"Commits":[""],"Commits|History":[""],"Committed by":[""],"Compare":[""],"Contribution guide":[""],"Contributors":[""],"Copy URL to clipboard":[""],"Copy commit SHA to clipboard":[""],"Create New Directory":[""],"Create directory":[""],"Create empty bare repository":[""],"Create merge request":[""],"Create new...":[""],"CreateNewFork|Fork":[""],"CreateTag|Tag":[""],"Cron Timezone":[""],"Cron syntax":[""],"Custom notification events":[""],"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}.":[""],"Cycle Analytics":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Define a custom pattern with cron syntax":[""],"Delete":[""],"Deploy":["",""],"Description":[""],"Directory name":[""],"Don't show again":[""],"Download":[""],"Download tar":[""],"Download tar.bz2":[""],"Download tar.gz":[""],"Download zip":[""],"DownloadArtifacts|Download":[""],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Files":[""],"Find by path":[""],"Find file":[""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"Fork":["",""],"ForkedFromProjectPath|Forked from":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Go to your fork":[""],"GoToYourFork|Fork":[""],"Home":[""],"Housekeeping successfully started":[""],"Import repository":[""],"Interval Pattern":[""],"Introducing Cycle Analytics":[""],"LFSStatus|Disabled":[""],"LFSStatus|Enabled":[""],"Last %d day":["",""],"Last Pipeline":[""],"Last Update":[""],"Last commit":[""],"Learn more in the":[""],"Learn more in the|pipeline schedules documentation":[""],"Leave group":[""],"Leave project":[""],"Limited to showing %d event at most":["",""],"Median":[""],"MissingSSHKeyWarningLink|add an SSH key":[""],"New Issue":["",""],"New Pipeline Schedule":[""],"New branch":[""],"New directory":[""],"New file":[""],"New issue":[""],"New merge request":[""],"New schedule":[""],"New snippet":[""],"New tag":[""],"No repository":[""],"No schedules":[""],"Not available":[""],"Not enough data":[""],"Notification events":[""],"NotificationEvent|Close issue":[""],"NotificationEvent|Close merge request":[""],"NotificationEvent|Failed pipeline":[""],"NotificationEvent|Merge merge request":[""],"NotificationEvent|New issue":[""],"NotificationEvent|New merge request":[""],"NotificationEvent|New note":[""],"NotificationEvent|Reassign issue":[""],"NotificationEvent|Reassign merge request":[""],"NotificationEvent|Reopen issue":[""],"NotificationEvent|Successful pipeline":[""],"NotificationLevel|Custom":[""],"NotificationLevel|Disabled":[""],"NotificationLevel|Global":[""],"NotificationLevel|On mention":[""],"NotificationLevel|Participate":[""],"NotificationLevel|Watch":[""],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":[""],"Options":[""],"Owner":[""],"Pipeline":[""],"Pipeline Health":[""],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"PipelineSheduleIntervalPattern|Custom":[""],"Pipeline|with stage":[""],"Pipeline|with stages":[""],"Project '%{project_name}' queued for deletion.":[""],"Project '%{project_name}' was successfully created.":[""],"Project '%{project_name}' was successfully updated.":[""],"Project '%{project_name}' will be deleted.":[""],"Project access must be granted explicitly to each user.":[""],"Project export could not be deleted.":[""],"Project export has been deleted.":[""],"Project export link has expired. Please generate a new export from your project settings.":[""],"Project export started. A download link will be sent by email.":[""],"Project home":[""],"ProjectFeature|Disabled":[""],"ProjectFeature|Everyone with access":[""],"ProjectFeature|Only team members":[""],"ProjectFileTree|Name":[""],"ProjectLastActivity|Never":[""],"ProjectLifecycle|Stage":[""],"ProjectNetworkGraph|Graph":[""],"Read more":[""],"Readme":[""],"RefSwitcher|Branches":[""],"RefSwitcher|Tags":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Remind later":[""],"Remove project":[""],"Request Access":[""],"Revert this commit":[""],"Revert this merge request":[""],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Scheduling Pipelines":[""],"Search branches and tags":[""],"Select Archive Format":[""],"Select a timezone":[""],"Select target branch":[""],"Set a password on your account to pull or push via %{protocol}":[""],"Set up CI":[""],"Set up Koding":[""],"Set up auto deploy":[""],"SetPasswordToCloneLink|set a password":[""],"Showing %d event":["",""],"Source code":[""],"StarProject|Star":[""],"Start a %{new_merge_request} with these changes":[""],"Start a <strong>new merge request</strong> with these changes":[""],"Switch branch/tag":[""],"Tag":["",""],"Tags":[""],"Target Branch":[""],"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.":[""],"The collection of events added to the data gathered for that stage.":[""],"The fork relationship has been removed.":[""],"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.":[""],"The phase of the development lifecycle.":[""],"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.":[""],"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.":[""],"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.":[""],"The project can be accessed by any logged in user.":[""],"The project can be accessed without any authentication.":[""],"The repository for this project does not exist.":[""],"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.":[""],"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.":[""],"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.":[""],"The time taken by each data entry gathered by that stage.":[""],"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.":[""],"This means you can not push code until you create an empty repository or import existing one.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Timeago|%s days ago":[""],"Timeago|%s days remaining":[""],"Timeago|%s hours remaining":[""],"Timeago|%s minutes ago":[""],"Timeago|%s minutes remaining":[""],"Timeago|%s months ago":[""],"Timeago|%s months remaining":[""],"Timeago|%s seconds remaining":[""],"Timeago|%s weeks ago":[""],"Timeago|%s weeks remaining":[""],"Timeago|%s years ago":[""],"Timeago|%s years remaining":[""],"Timeago|1 day remaining":[""],"Timeago|1 hour remaining":[""],"Timeago|1 minute remaining":[""],"Timeago|1 month remaining":[""],"Timeago|1 week remaining":[""],"Timeago|1 year remaining":[""],"Timeago|Past due":[""],"Timeago|a day ago":[""],"Timeago|a month ago":[""],"Timeago|a week ago":[""],"Timeago|a while":[""],"Timeago|a year ago":[""],"Timeago|about %s hours ago":[""],"Timeago|about a minute ago":[""],"Timeago|about an hour ago":[""],"Timeago|in %s days":[""],"Timeago|in %s hours":[""],"Timeago|in %s minutes":[""],"Timeago|in %s months":[""],"Timeago|in %s seconds":[""],"Timeago|in %s weeks":[""],"Timeago|in %s years":[""],"Timeago|in 1 day":[""],"Timeago|in 1 hour":[""],"Timeago|in 1 minute":[""],"Timeago|in 1 month":[""],"Timeago|in 1 week":[""],"Timeago|in 1 year":[""],"Timeago|less than a minute ago":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Unstar":[""],"Upload New File":[""],"Upload file":[""],"Use your global notification setting":[""],"VisibilityLevel|Internal":[""],"VisibilityLevel|Private":[""],"VisibilityLevel|Public":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"Withdraw Access Request":[""],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":[""],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":[""],"You can only add files when you are on a branch":[""],"You must sign in to star a project":[""],"You need permission.":[""],"You will not get any notifications via email":[""],"You will only receive notifications for the events you choose":[""],"You will only receive notifications for threads you have participated in":[""],"You will receive notifications for any activity":[""],"You will receive notifications only for comments in which you were @mentioned":[""],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":[""],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":[""],"Your name":[""],"day":["",""],"new merge request":[""],"notification emails":[""],"parent":["",""]}}};
\ No newline at end of file diff --git a/app/assets/javascripts/locale/es/app.js b/app/assets/javascripts/locale/es/app.js index 6977625f4d8..f198809cc20 100644 --- a/app/assets/javascripts/locale/es/app.js +++ b/app/assets/javascripts/locale/es/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['es'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-06-07 12:29-0500","Language-Team":"Spanish","Language":"es","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"Bob Van Landuyt <bob@gitlab.com>","X-Generator":"Poedit 2.0.2","lang":"es","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"About auto deploy":["Acerca del auto despliegue"],"Activity":["Actividad"],"Add Changelog":["Agregar Changelog"],"Add Contribution guide":["Agregar guÃa de contribución"],"Add License":["Agregar Licencia"],"Add an SSH key to your profile to pull or push via SSH.":["Agregar una clave SSH a tu perfil para actualizar o enviar a través de SSH."],"Add new directory":["Agregar nuevo directorio"],"Archived project! Repository is read-only":["¡Proyecto archivado! El repositorio es de sólo lectura"],"Branch":["Rama","Ramas"],"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 rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envÃa tus cambios. %{link_to_autodeploy_doc}"],"Branches":["Ramas"],"ByAuthor|by":["por"],"CI configuration":["Configuración de CI"],"Changelog":["Changelog"],"Charts":["Gráficos"],"CiStatusLabel|canceled":["cancelado"],"CiStatusLabel|created":["creado"],"CiStatusLabel|failed":["fallado"],"CiStatusLabel|manual action":["acción manual"],"CiStatusLabel|passed":["pasó"],"CiStatusLabel|passed with warnings":["pasó con advertencias"],"CiStatusLabel|pending":["pendiente"],"CiStatusLabel|skipped":["omitido"],"CiStatusLabel|waiting for manual action":["esperando acción manual"],"CiStatusText|blocked":["bloqueado"],"CiStatusText|canceled":["cancelado"],"CiStatusText|created":["creado"],"CiStatusText|failed":["fallado"],"CiStatusText|manual":["manual"],"CiStatusText|passed":["pasó"],"CiStatusText|pending":["pendiente"],"CiStatusText|skipped":["omitido"],"CiStatus|running":["en ejecución"],"Commit":["Cambio","Cambios"],"CommitMessage|Add %{file_name}":["Agregar %{file_name}"],"Commits":["Cambios"],"Commits|History":["Historial"],"Compare":["Comparar"],"Contribution guide":["GuÃa de contribución"],"Contributors":["Contribuidores"],"Copy URL to clipboard":["Copiar URL al portapapeles"],"Copy commit SHA to clipboard":["Copiar SHA del cambio al portapapeles"],"Create New Directory":["Crear Nuevo Directorio"],"Create directory":["Crear directorio"],"Create empty bare repository":["Crear repositorio vacÃo"],"Create merge request":["Crear solicitud de fusión"],"CreateNewFork|Fork":["Bifurcar"],"Custom notification events":["Eventos de notificaciones personalizadas"],"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}.":["Los niveles de notificación personalizados son los mismos que los niveles participantes. Con los niveles de notificación personalizados, también recibirá notificaciones para eventos seleccionados. Para obtener más información, consulte %{notification_link}."],"Cycle Analytics":["Cycle Analytics"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Incidencia"],"CycleAnalyticsStage|Plan":["Planificación"],"CycleAnalyticsStage|Production":["Producción"],"CycleAnalyticsStage|Review":["Revisión"],"CycleAnalyticsStage|Staging":["Puesta en escena"],"CycleAnalyticsStage|Test":["Pruebas"],"Deploy":["Despliegue","Despliegues"],"Directory name":["Nombre del directorio"],"Don't show again":["No mostrar de nuevo"],"Download tar":["Descargar tar"],"Download tar.bz2":["Descargar tar.bz2"],"Download tar.gz":["Descargar tar.gz"],"Download zip":["Descargar zip"],"DownloadArtifacts|Download":["Descargar"],"DownloadSource|Download":["Descargar"],"Files":["Archivos"],"Find by path":["Buscar por ruta"],"Find file":["Buscar archivo"],"FirstPushedBy|First":["Primer"],"FirstPushedBy|pushed by":["enviado por"],"ForkedFromProjectPath|Forked from":["Bifurcado de"],"Forks":["Bifurcaciones"],"From issue creation until deploy to production":["Desde la creación de la incidencia hasta el despliegue a producción"],"From merge request merge until deploy to production":["Desde la integración de la solicitud de fusión hasta el despliegue a producción"],"Go to your fork":["Ir a tu bifurcación"],"GoToYourFork|Fork":["Bifurcación"],"Home":["Inicio"],"Housekeeping successfully started":["Servicio de limpieza iniciado con éxito"],"Import repository":["Importar repositorio"],"Introducing Cycle Analytics":["Introducción a Cycle Analytics"],"LFSStatus|Disabled":["Deshabilitado"],"LFSStatus|Enabled":["Habilitado"],"Last %d day":["Último %d dÃa","Últimos %d dÃas"],"Last Update":["Última actualización"],"Last commit":["Último cambio"],"Leave group":["Abandonar grupo"],"Leave project":["Abandonar proyecto"],"Limited to showing %d event at most":["Limitado a mostrar máximo %d evento","Limitado a mostrar máximo %d eventos"],"Median":["Mediana"],"MissingSSHKeyWarningLink|add an SSH key":["agregar una clave SSH"],"New Issue":["Nueva incidencia","Nuevas incidencias"],"New branch":["Nueva rama"],"New directory":["Nuevo directorio"],"New file":["Nuevo archivo"],"New issue":["Nueva incidencia"],"New merge request":["Nueva solicitud de fusión"],"New snippet":["Nuevo fragmento de código"],"New tag":["Nueva etiqueta"],"No repository":["No hay repositorio"],"Not available":["No disponible"],"Not enough data":["No hay suficientes datos"],"Notification events":["Eventos de notificación"],"NotificationEvent|Close issue":["Cerrar incidencia"],"NotificationEvent|Close merge request":["Cerrar solicitud de fusión"],"NotificationEvent|Failed pipeline":["Pipeline fallido"],"NotificationEvent|Merge merge request":["Integrar solicitud de fusión"],"NotificationEvent|New issue":["Nueva incidencia"],"NotificationEvent|New merge request":["Nueva solicitud de fusión"],"NotificationEvent|New note":["Nueva nota"],"NotificationEvent|Reassign issue":["Reasignar incidencia"],"NotificationEvent|Reassign merge request":["Reasignar solicitud de fusión"],"NotificationEvent|Reopen issue":["Reabrir incidencia"],"NotificationEvent|Successful pipeline":["Pipeline exitoso"],"NotificationLevel|Custom":["Personalizado"],"NotificationLevel|Disabled":["Deshabilitado"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["Cuando me mencionan"],"NotificationLevel|Participate":["Participación"],"NotificationLevel|Watch":["Vigilancia"],"OpenedNDaysAgo|Opened":["Abierto"],"Pipeline Health":["Estado del Pipeline"],"Project '%{project_name}' queued for deletion.":["Proyecto ‘%{project_name}’ en cola para eliminación."],"Project '%{project_name}' was successfully created.":["Proyecto ‘%{project_name}’ fue creado satisfactoriamente."],"Project '%{project_name}' was successfully updated.":["Proyecto ‘%{project_name}’ fue actualizado satisfactoriamente."],"Project '%{project_name}' will be deleted.":["Proyecto ‘%{project_name}’ será eliminado."],"Project access must be granted explicitly to each user.":["El acceso al proyecto debe concederse explÃcitamente a cada usuario."],"Project export could not be deleted.":["No se pudo eliminar la exportación del proyecto."],"Project export has been deleted.":["La exportación del proyecto ha sido eliminada."],"Project export link has expired. Please generate a new export from your project settings.":["El enlace de exportación del proyecto ha caducado. Por favor, genera una nueva exportación desde la configuración del proyecto."],"Project export started. A download link will be sent by email.":["Se inició la exportación del proyecto. Se enviará un enlace de descarga por correo electrónico."],"Project home":["Inicio del proyecto"],"ProjectFeature|Disabled":["Deshabilitada"],"ProjectFeature|Everyone with access":["Todos con acceso"],"ProjectFeature|Only team members":["Solo miembros del equipo"],"ProjectFileTree|Name":["Nombre"],"ProjectLastActivity|Never":["Nunca"],"ProjectLifecycle|Stage":["Etapa"],"ProjectNetworkGraph|Graph":["Historial gráfico"],"Read more":["Leer más"],"Readme":["Readme"],"RefSwitcher|Branches":["Ramas"],"RefSwitcher|Tags":["Etiquetas"],"Related Commits":["Cambios Relacionados"],"Related Deployed Jobs":["Trabajos Desplegados Relacionados"],"Related Issues":["Incidencias Relacionadas"],"Related Jobs":["Trabajos Relacionados"],"Related Merge Requests":["Solicitudes de fusión Relacionadas"],"Related Merged Requests":["Solicitudes de fusión Relacionadas"],"Remind later":["Recordar después"],"Remove project":["Eliminar proyecto"],"Request Access":["Solicitar acceso"],"Search branches and tags":["Buscar ramas y etiquetas"],"Select Archive Format":["Seleccionar formato de archivo"],"Set a password on your account to pull or push via %{protocol}":["Establezca una contraseña en su cuenta para actualizar o enviar a través de% {protocol}"],"Set up CI":["Configurar CI"],"Set up Koding":["Configurar Koding"],"Set up auto deploy":["Configurar auto despliegue"],"SetPasswordToCloneLink|set a password":["establecer una contraseña"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"Source code":["Código fuente"],"StarProject|Star":["Destacar"],"Switch branch/tag":["Cambiar rama/etiqueta"],"Tag":["Etiqueta","Etiquetas"],"Tags":["Etiquetas"],"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 etapa de desarrollo muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquà una vez creada tu primera solicitud de fusión."],"The collection of events added to the data gathered for that stage.":["La colección de eventos agregados a los datos recopilados para esa etapa."],"The fork relationship has been removed.":["La relación con la bifurcación se ha eliminado."],"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 etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."],"The phase of the development lifecycle.":["La etapa del ciclo de vida de desarrollo."],"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 etapa de planificación muestra el tiempo desde el paso anterior hasta el envÃo de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envÃe el primer cambio."],"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 etapa de producción muestra el tiempo total que tarda entre la creación de una incidencia y el despliegue del código a producción. Los datos se añadirán automáticamente una vez haya finalizado por completo el ciclo de idea a producción."],"The project can be accessed by any logged in user.":["El proyecto puede ser accedido por cualquier usuario conectado."],"The project can be accessed without any authentication.":["El proyecto puede accederse sin ninguna autenticación."],"The repository for this project does not exist.":["El repositorio para este proyecto no existe."],"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 etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión."],"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 etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."],"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 etapa de pruebas muestra el tiempo que GitLab CI toma para ejecutar cada pipeline para la solicitud de fusión relacionada. Los datos se añadirán automáticamente luego de que el primer pipeline termine de ejecutarse."],"The time taken by each data entry gathered by that stage.":["El tiempo utilizado por cada entrada de datos obtenido por esa etapa."],"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.":["El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Esto significa que no puede enviar código hasta que cree un repositorio vacÃo o importe uno existente."],"Time before an issue gets scheduled":["Tiempo antes de que una incidencia sea programada"],"Time before an issue starts implementation":["Tiempo antes de que empieze la implementación de una incidencia"],"Time between merge request creation and merge/close":["Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta"],"Time until first merge request":["Tiempo hasta la primera solicitud de fusión"],"Timeago|%s days ago":["hace %s dÃas"],"Timeago|%s days remaining":["%s dÃas restantes"],"Timeago|%s hours remaining":["%s horas restantes"],"Timeago|%s minutes ago":["hace %s minutos"],"Timeago|%s minutes remaining":["%s minutos restantes"],"Timeago|%s months ago":["hace %s meses"],"Timeago|%s months remaining":["%s meses restantes"],"Timeago|%s seconds remaining":["%s segundos restantes"],"Timeago|%s weeks ago":["hace %s semanas"],"Timeago|%s weeks remaining":["%s semanas restantes"],"Timeago|%s years ago":["hace %s años"],"Timeago|%s years remaining":["%s años restantes"],"Timeago|1 day remaining":["1 dÃa restante"],"Timeago|1 hour remaining":["1 hora restante"],"Timeago|1 minute remaining":["1 minuto restante"],"Timeago|1 month remaining":["1 mes restante"],"Timeago|1 week remaining":["1 semana restante"],"Timeago|1 year remaining":["1 año restante"],"Timeago|Past due":["Atrasado"],"Timeago|a day ago":["hace un dÃa"],"Timeago|a month ago":["hace 1 mes"],"Timeago|a week ago":["hace 1 semana"],"Timeago|a while":["hace un momento"],"Timeago|a year ago":["hace 1 año"],"Timeago|about %s hours ago":["hace alrededor de %s horas"],"Timeago|about a minute ago":["hace alrededor de 1 minuto"],"Timeago|about an hour ago":["hace alrededor de 1 hora"],"Timeago|in %s days":["en %s dÃas"],"Timeago|in %s hours":["en %s horas"],"Timeago|in %s minutes":["en %s minutos"],"Timeago|in %s months":["en %s meses"],"Timeago|in %s seconds":["en %s segundos"],"Timeago|in %s weeks":["en %s semanas"],"Timeago|in %s years":["en %s años"],"Timeago|in 1 day":["en 1 dÃa"],"Timeago|in 1 hour":["en 1 hora"],"Timeago|in 1 minute":["en 1 minuto"],"Timeago|in 1 month":["en 1 mes"],"Timeago|in 1 week":["en 1 semana"],"Timeago|in 1 year":["en 1 año"],"Timeago|less than a minute ago":["hace menos de 1 minuto"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tiempo Total"],"Total test time for all commits/merges":["Tiempo total de pruebas para todos los cambios o integraciones"],"Unstar":["No Destacar"],"Upload New File":["Subir nuevo archivo"],"Upload file":["Subir archivo"],"Use your global notification setting":["Utiliza tu configuración de notificación global"],"VisibilityLevel|Internal":["Interno"],"VisibilityLevel|Private":["Privado"],"VisibilityLevel|Public":["Público"],"Want to see the data? Please ask an administrator for access.":["¿Quieres ver los datos? Por favor pide acceso al administrador."],"We don't have enough data to show this stage.":["No hay suficientes datos para mostrar en esta etapa."],"Withdraw Access Request":["Retirar Solicitud de Acceso"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Va a eliminar %{project_name_with_namespace}.\\n¡El proyecto eliminado NO puede ser restaurado!\\n¿Estás TOTALMENTE seguro?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?"],"You can only add files when you are on a branch":["Sólo puede agregar archivos cuando estas en una rama"],"You must sign in to star a project":["Debes iniciar sesión para destacar un proyecto"],"You need permission.":["Necesitas permisos."],"You will not get any notifications via email":["No recibirás ninguna notificación por correo electrónico"],"You will only receive notifications for the events you choose":["Solo recibirás notificaciones de los eventos que elijas"],"You will only receive notifications for threads you have participated in":["Solo recibirás notificaciones de los temas en los que has participado"],"You will receive notifications for any activity":["Recibirás notificaciones para cualquier actividad"],"You will receive notifications only for comments in which you were @mentioned":["Recibirás notificaciones sólo para los comentarios en los que se te mencionó"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["No podrás actualizar o enviar código al proyecto a través de %{protocol} hasta que %{set_password_link} en tu cuenta"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["No podrás actualizar o enviar código al proyecto a través de SSH hasta que %{add_ssh_key_link} en su perfil"],"Your name":["Tu nombre"],"committed":["cambió"],"day":["dÃa","dÃas"],"notification emails":["correos electrónicos de notificación"]}}};
\ No newline at end of file +var locales = locales || {}; locales['es'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-06-15 21:59-0500","Language-Team":"Spanish","Language":"es","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"Bob Van Landuyt <bob@gitlab.com>","X-Generator":"Poedit 2.0.2","lang":"es","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} cambió %{commit_timeago}"],"About auto deploy":["Acerca del auto despliegue"],"Active":["Activo"],"Activity":["Actividad"],"Add Changelog":["Agregar Changelog"],"Add Contribution guide":["Agregar guÃa de contribución"],"Add License":["Agregar Licencia"],"Add an SSH key to your profile to pull or push via SSH.":["Agregar una clave SSH a tu perfil para actualizar o enviar a través de SSH."],"Add new directory":["Agregar nuevo directorio"],"Archived project! Repository is read-only":["¡Proyecto archivado! El repositorio es de solo lectura"],"Are you sure you want to delete this pipeline schedule?":["¿Estás seguro que deseas eliminar esta programación del pipeline?"],"Attach a file by drag & drop or %{upload_link}":["Adjunte un archivo arrastrando & soltando o %{upload_link}"],"Branch":["Rama","Ramas"],"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 rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envÃa tus cambios. %{link_to_autodeploy_doc}"],"Branches":["Ramas"],"Browse files":["Examinar los archivos"],"ByAuthor|by":["por"],"CI configuration":["Configuración de CI"],"Cancel":["Cancelar"],"ChangeTypeActionLabel|Pick into branch":["Escoger en la rama"],"ChangeTypeActionLabel|Revert in branch":["Revertir en la rama"],"ChangeTypeAction|Cherry-pick":["Cherry-pick"],"ChangeTypeAction|Revert":["Revertir"],"Changelog":["Changelog"],"Charts":["Gráficos"],"Cherry-pick this commit":["Escoger este cambio"],"Cherry-pick this merge request":["Escoger esta solicitud de fusión"],"CiStatusLabel|canceled":["cancelado"],"CiStatusLabel|created":["creado"],"CiStatusLabel|failed":["fallido"],"CiStatusLabel|manual action":["acción manual"],"CiStatusLabel|passed":["pasó"],"CiStatusLabel|passed with warnings":["pasó con advertencias"],"CiStatusLabel|pending":["pendiente"],"CiStatusLabel|skipped":["omitido"],"CiStatusLabel|waiting for manual action":["esperando acción manual"],"CiStatusText|blocked":["bloqueado"],"CiStatusText|canceled":["cancelado"],"CiStatusText|created":["creado"],"CiStatusText|failed":["fallado"],"CiStatusText|manual":["manual"],"CiStatusText|passed":["pasó"],"CiStatusText|pending":["pendiente"],"CiStatusText|skipped":["omitido"],"CiStatus|running":["en ejecución"],"Commit":["Cambio","Cambios"],"Commit message":["Mensaje del cambio"],"CommitBoxTitle|Commit":["Cambio"],"CommitMessage|Add %{file_name}":["Agregar %{file_name}"],"Commits":["Cambios"],"Commits|History":["Historial"],"Committed by":["Enviado por"],"Compare":["Comparar"],"Contribution guide":["GuÃa de contribución"],"Contributors":["Contribuidores"],"Copy URL to clipboard":["Copiar URL al portapapeles"],"Copy commit SHA to clipboard":["Copiar SHA del cambio al portapapeles"],"Create New Directory":["Crear Nuevo Directorio"],"Create directory":["Crear directorio"],"Create empty bare repository":["Crear repositorio vacÃo"],"Create merge request":["Crear solicitud de fusión"],"Create new...":["Crear nuevo..."],"CreateNewFork|Fork":["Bifurcar"],"CreateTag|Tag":["Etiqueta"],"Cron Timezone":["Zona horaria del Cron"],"Cron syntax":["Sintaxis de Cron"],"Custom notification events":["Eventos de notificaciones personalizadas"],"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}.":["Los niveles de notificación personalizados son los mismos que los niveles participantes. Con los niveles de notificación personalizados, también recibirá notificaciones para eventos seleccionados. Para obtener más información, consulte %{notification_link}."],"Cycle Analytics":["Cycle Analytics"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Incidencia"],"CycleAnalyticsStage|Plan":["Planificación"],"CycleAnalyticsStage|Production":["Producción"],"CycleAnalyticsStage|Review":["Revisión"],"CycleAnalyticsStage|Staging":["Puesta en escena"],"CycleAnalyticsStage|Test":["Pruebas"],"Define a custom pattern with cron syntax":["Definir un patrón personalizado con la sintaxis de cron"],"Delete":["Eliminar"],"Deploy":["Despliegue","Despliegues"],"Description":["Descripción"],"Directory name":["Nombre del directorio"],"Don't show again":["No mostrar de nuevo"],"Download":["Descargar"],"Download tar":["Descargar tar"],"Download tar.bz2":["Descargar tar.bz2"],"Download tar.gz":["Descargar tar.gz"],"Download zip":["Descargar zip"],"DownloadArtifacts|Download":["Descargar"],"DownloadCommit|Email Patches":["Parches por correo electrónico"],"DownloadCommit|Plain Diff":["Diferencias en texto plano"],"DownloadSource|Download":["Descargar"],"Edit":["Editar"],"Edit Pipeline Schedule %{id}":["Editar Programación del Pipeline %{id}"],"Every day (at 4:00am)":["Todos los dÃas (a las 4:00 am)"],"Every month (on the 1st at 4:00am)":["Todos los meses (el dÃa 1 a las 4:00 am)"],"Every week (Sundays at 4:00am)":["Todas las semanas (domingos a las 4:00 am)"],"Failed to change the owner":["Error al cambiar el propietario"],"Failed to remove the pipeline schedule":["Error al eliminar la programación del pipeline"],"Files":["Archivos"],"Find by path":["Buscar por ruta"],"Find file":["Buscar archivo"],"FirstPushedBy|First":["Primer"],"FirstPushedBy|pushed by":["enviado por"],"Fork":["Bifurcación","Bifurcaciones"],"ForkedFromProjectPath|Forked from":["Bifurcado de"],"From issue creation until deploy to production":["Desde la creación de la incidencia hasta el despliegue a producción"],"From merge request merge until deploy to production":["Desde la integración de la solicitud de fusión hasta el despliegue a producción"],"Go to your fork":["Ir a tu bifurcación"],"GoToYourFork|Fork":["Bifurcación"],"Home":["Inicio"],"Housekeeping successfully started":["Servicio de limpieza iniciado con éxito"],"Import repository":["Importar repositorio"],"Interval Pattern":["Patrón de intervalo"],"Introducing Cycle Analytics":["Introducción a Cycle Analytics"],"LFSStatus|Disabled":["Deshabilitado"],"LFSStatus|Enabled":["Habilitado"],"Last %d day":["Último %d dÃa","Últimos %d dÃas"],"Last Pipeline":["Último Pipeline"],"Last Update":["Última actualización"],"Last commit":["Último cambio"],"Learn more in the":["Más información en la"],"Learn more in the|pipeline schedules documentation":["documentación sobre la programación de pipelines"],"Leave group":["Abandonar grupo"],"Leave project":["Abandonar proyecto"],"Limited to showing %d event at most":["Limitado a mostrar máximo %d evento","Limitado a mostrar máximo %d eventos"],"Median":["Mediana"],"MissingSSHKeyWarningLink|add an SSH key":["agregar una clave SSH"],"New Issue":["Nueva incidencia","Nuevas incidencias"],"New Pipeline Schedule":["Nueva Programación del Pipeline"],"New branch":["Nueva rama"],"New directory":["Nuevo directorio"],"New file":["Nuevo archivo"],"New issue":["Nueva incidencia"],"New merge request":["Nueva solicitud de fusión"],"New schedule":["Nueva programación"],"New snippet":["Nuevo fragmento de código"],"New tag":["Nueva etiqueta"],"No repository":["No hay repositorio"],"No schedules":["No hay programaciones"],"Not available":["No disponible"],"Not enough data":["No hay suficientes datos"],"Notification events":["Eventos de notificación"],"NotificationEvent|Close issue":["Cerrar incidencia"],"NotificationEvent|Close merge request":["Cerrar solicitud de fusión"],"NotificationEvent|Failed pipeline":["Pipeline fallido"],"NotificationEvent|Merge merge request":["Integrar solicitud de fusión"],"NotificationEvent|New issue":["Nueva incidencia"],"NotificationEvent|New merge request":["Nueva solicitud de fusión"],"NotificationEvent|New note":["Nueva nota"],"NotificationEvent|Reassign issue":["Reasignar incidencia"],"NotificationEvent|Reassign merge request":["Reasignar solicitud de fusión"],"NotificationEvent|Reopen issue":["Reabrir incidencia"],"NotificationEvent|Successful pipeline":["Pipeline exitoso"],"NotificationLevel|Custom":["Personalizado"],"NotificationLevel|Disabled":["Deshabilitado"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["Cuando me mencionan"],"NotificationLevel|Participate":["Participación"],"NotificationLevel|Watch":["Vigilancia"],"OfSearchInADropdown|Filter":["Filtrar"],"OpenedNDaysAgo|Opened":["Abierto"],"Options":["Opciones"],"Owner":["Propietario"],"Pipeline":["Pipeline"],"Pipeline Health":["Estado del Pipeline"],"Pipeline Schedule":["Programación del Pipeline"],"Pipeline Schedules":["Programaciones de los Pipelines"],"PipelineSchedules|Activated":["Activado"],"PipelineSchedules|Active":["Activos"],"PipelineSchedules|All":["Todos"],"PipelineSchedules|Inactive":["Inactivos"],"PipelineSchedules|Next Run":["Próxima Ejecución"],"PipelineSchedules|None":["Ninguno"],"PipelineSchedules|Provide a short description for this pipeline":["Proporcione una breve descripción para este pipeline"],"PipelineSchedules|Take ownership":["Tomar posesión"],"PipelineSchedules|Target":["Destino"],"PipelineSheduleIntervalPattern|Custom":["Personalizado"],"Pipeline|with stage":["con etapa"],"Pipeline|with stages":["con etapas"],"Project '%{project_name}' queued for deletion.":["Proyecto ‘%{project_name}’ en cola para eliminación."],"Project '%{project_name}' was successfully created.":["Proyecto ‘%{project_name}’ fue creado satisfactoriamente."],"Project '%{project_name}' was successfully updated.":["Proyecto ‘%{project_name}’ fue actualizado satisfactoriamente."],"Project '%{project_name}' will be deleted.":["Proyecto ‘%{project_name}’ será eliminado."],"Project access must be granted explicitly to each user.":["El acceso al proyecto debe concederse explÃcitamente a cada usuario."],"Project export could not be deleted.":["No se pudo eliminar la exportación del proyecto."],"Project export has been deleted.":["La exportación del proyecto ha sido eliminada."],"Project export link has expired. Please generate a new export from your project settings.":["El enlace de exportación del proyecto ha caducado. Por favor, genera una nueva exportación desde la configuración del proyecto."],"Project export started. A download link will be sent by email.":["Se inició la exportación del proyecto. Se enviará un enlace de descarga por correo electrónico."],"Project home":["Inicio del proyecto"],"ProjectFeature|Disabled":["Deshabilitada"],"ProjectFeature|Everyone with access":["Todos con acceso"],"ProjectFeature|Only team members":["Solo miembros del equipo"],"ProjectFileTree|Name":["Nombre"],"ProjectLastActivity|Never":["Nunca"],"ProjectLifecycle|Stage":["Etapa"],"ProjectNetworkGraph|Graph":["Historial gráfico"],"Read more":["Leer más"],"Readme":["Léeme"],"RefSwitcher|Branches":["Ramas"],"RefSwitcher|Tags":["Etiquetas"],"Related Commits":["Cambios Relacionados"],"Related Deployed Jobs":["Trabajos Desplegados Relacionados"],"Related Issues":["Incidencias Relacionadas"],"Related Jobs":["Trabajos Relacionados"],"Related Merge Requests":["Solicitudes de fusión Relacionadas"],"Related Merged Requests":["Solicitudes de fusión Relacionadas"],"Remind later":["Recordar después"],"Remove project":["Eliminar proyecto"],"Request Access":["Solicitar acceso"],"Revert this commit":["Revertir este cambio"],"Revert this merge request":["Revertir esta solicitud de fusión"],"Save pipeline schedule":["Guardar programación del pipeline"],"Schedule a new pipeline":["Programar un nuevo pipeline"],"Scheduling Pipelines":["Programación de Pipelines"],"Search branches and tags":["Buscar ramas y etiquetas"],"Select Archive Format":["Seleccionar formato de archivo"],"Select a timezone":["Selecciona una zona horaria"],"Select target branch":["Selecciona una rama de destino"],"Set a password on your account to pull or push via %{protocol}":["Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}"],"Set up CI":["Configurar CI"],"Set up Koding":["Configurar Koding"],"Set up auto deploy":["Configurar auto despliegue"],"SetPasswordToCloneLink|set a password":["establecer una contraseña"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"Source code":["Código fuente"],"StarProject|Star":["Destacar"],"Start a %{new_merge_request} with these changes":["Iniciar una %{new_merge_request} con estos cambios"],"Switch branch/tag":["Cambiar rama/etiqueta"],"Tag":["Etiqueta","Etiquetas"],"Tags":["Etiquetas"],"Target Branch":["Rama de destino"],"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 etapa de desarrollo muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquà una vez creada tu primera solicitud de fusión."],"The collection of events added to the data gathered for that stage.":["La colección de eventos agregados a los datos recopilados para esa etapa."],"The fork relationship has been removed.":["La relación con la bifurcación se ha eliminado."],"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 etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."],"The phase of the development lifecycle.":["La etapa del ciclo de vida de desarrollo."],"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 programación de pipelines ejecuta pipelines en el futuro, repetidamente, para ramas o etiquetas especÃficas. Los pipelines programados heredarán acceso limitado al proyecto basado en su usuario asociado."],"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 etapa de planificación muestra el tiempo desde el paso anterior hasta el envÃo de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envÃe el primer cambio."],"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 etapa de producción muestra el tiempo total que tarda entre la creación de una incidencia y el despliegue del código a producción. Los datos se añadirán automáticamente una vez haya finalizado por completo el ciclo de idea a producción."],"The project can be accessed by any logged in user.":["El proyecto puede ser accedido por cualquier usuario conectado."],"The project can be accessed without any authentication.":["El proyecto puede accederse sin ninguna autenticación."],"The repository for this project does not exist.":["El repositorio para este proyecto no existe."],"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 etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión."],"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 etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."],"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 etapa de pruebas muestra el tiempo que GitLab CI toma para ejecutar cada pipeline para la solicitud de fusión relacionada. Los datos se añadirán automáticamente luego de que el primer pipeline termine de ejecutarse."],"The time taken by each data entry gathered by that stage.":["El tiempo utilizado por cada entrada de datos obtenido por esa etapa."],"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.":["El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Esto significa que no puede enviar código hasta que cree un repositorio vacÃo o importe uno existente."],"Time before an issue gets scheduled":["Tiempo antes de que una incidencia sea programada"],"Time before an issue starts implementation":["Tiempo antes de que empieze la implementación de una incidencia"],"Time between merge request creation and merge/close":["Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta"],"Time until first merge request":["Tiempo hasta la primera solicitud de fusión"],"Timeago|%s days ago":["hace %s dÃas"],"Timeago|%s days remaining":["%s dÃas restantes"],"Timeago|%s hours remaining":["%s horas restantes"],"Timeago|%s minutes ago":["hace %s minutos"],"Timeago|%s minutes remaining":["%s minutos restantes"],"Timeago|%s months ago":["hace %s meses"],"Timeago|%s months remaining":["%s meses restantes"],"Timeago|%s seconds remaining":["%s segundos restantes"],"Timeago|%s weeks ago":["hace %s semanas"],"Timeago|%s weeks remaining":["%s semanas restantes"],"Timeago|%s years ago":["hace %s años"],"Timeago|%s years remaining":["%s años restantes"],"Timeago|1 day remaining":["1 dÃa restante"],"Timeago|1 hour remaining":["1 hora restante"],"Timeago|1 minute remaining":["1 minuto restante"],"Timeago|1 month remaining":["1 mes restante"],"Timeago|1 week remaining":["1 semana restante"],"Timeago|1 year remaining":["1 año restante"],"Timeago|Past due":["Atrasado"],"Timeago|a day ago":["hace un dÃa"],"Timeago|a month ago":["hace un mes"],"Timeago|a week ago":["hace una semana"],"Timeago|a while":["hace un momento"],"Timeago|a year ago":["hace un año"],"Timeago|about %s hours ago":["hace alrededor de %s horas"],"Timeago|about a minute ago":["hace alrededor de 1 minuto"],"Timeago|about an hour ago":["hace alrededor de 1 hora"],"Timeago|in %s days":["en %s dÃas"],"Timeago|in %s hours":["en %s horas"],"Timeago|in %s minutes":["en %s minutos"],"Timeago|in %s months":["en %s meses"],"Timeago|in %s seconds":["en %s segundos"],"Timeago|in %s weeks":["en %s semanas"],"Timeago|in %s years":["en %s años"],"Timeago|in 1 day":["en 1 dÃa"],"Timeago|in 1 hour":["en 1 hora"],"Timeago|in 1 minute":["en 1 minuto"],"Timeago|in 1 month":["en 1 mes"],"Timeago|in 1 week":["en 1 semana"],"Timeago|in 1 year":["en 1 año"],"Timeago|less than a minute ago":["hace menos de 1 minuto"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tiempo Total"],"Total test time for all commits/merges":["Tiempo total de pruebas para todos los cambios o integraciones"],"Unstar":["No Destacar"],"Upload New File":["Subir nuevo archivo"],"Upload file":["Subir archivo"],"Use your global notification setting":["Utiliza tu configuración de notificación global"],"VisibilityLevel|Internal":["Interno"],"VisibilityLevel|Private":["Privado"],"VisibilityLevel|Public":["Público"],"Want to see the data? Please ask an administrator for access.":["¿Quieres ver los datos? Por favor pide acceso al administrador."],"We don't have enough data to show this stage.":["No hay suficientes datos para mostrar en esta etapa."],"Withdraw Access Request":["Retirar Solicitud de Acceso"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Va a eliminar %{project_name_with_namespace}.\\n¡El proyecto eliminado NO puede ser restaurado!\\n¿Estás TOTALMENTE seguro?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?"],"You can only add files when you are on a branch":["Solo puedes agregar archivos cuando estás en una rama"],"You must sign in to star a project":["Debes iniciar sesión para destacar un proyecto"],"You need permission.":["Necesitas permisos."],"You will not get any notifications via email":["No recibirás ninguna notificación por correo electrónico"],"You will only receive notifications for the events you choose":["Solo recibirás notificaciones de los eventos que elijas"],"You will only receive notifications for threads you have participated in":["Solo recibirás notificaciones de los temas en los que has participado"],"You will receive notifications for any activity":["Recibirás notificaciones por cualquier actividad"],"You will receive notifications only for comments in which you were @mentioned":["Recibirás notificaciones solo para los comentarios en los que se te mencionó"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["No podrás actualizar o enviar código al proyecto a través de %{protocol} hasta que %{set_password_link} en tu cuenta"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["No podrás actualizar o enviar código al proyecto a través de SSH hasta que %{add_ssh_key_link} en su perfil"],"Your name":["Tu nombre"],"day":["dÃa","dÃas"],"new merge request":["nueva solicitud de fusión"],"notification emails":["correos electrónicos de notificación"],"parent":["padre","padres"]}}};
\ No newline at end of file diff --git a/app/assets/javascripts/locale/fr/app.js b/app/assets/javascripts/locale/fr/app.js new file mode 100644 index 00000000000..f9904ea61ea --- /dev/null +++ b/app/assets/javascripts/locale/fr/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['fr'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 20:38+0000","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-14 04:21-0400","Last-Translator":"Dremor <egeorget@opmbx.org>","Language-Team":"French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)","Language":"fr","Plural-Forms":"nplurals=2; plural=(n > 1);","X-Generator":"Zanata 3.9.6","lang":"fr","domain":"app","plural_forms":"nplurals=2; plural=(n > 1);"},"ByAuthor|by":["par"],"Commit":["Validation","Validations"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Incident"],"CycleAnalyticsStage|Plan":["Planification"],"CycleAnalyticsStage|Production":["Production"],"CycleAnalyticsStage|Review":["Examen"],"CycleAnalyticsStage|Staging":["Pré-production"],"CycleAnalyticsStage|Test":["Test"],"Deploy":["Déploiement","Déploiements"],"FirstPushedBy|First":["En premier"],"FirstPushedBy|pushed by":["poussé par"],"From issue creation until deploy to production":["Depuis la création de l'incident jusqu'au déploiement en production"],"From merge request merge until deploy to production":["Depuis la fusion de la demande de fusion jusqu'au déploiement en production"],"Introducing Cycle Analytics":["Introduction à l'analyseur de cycle"],"Last %d day":["Le dernier %d jour","Les derniers %d jours"],"Limited to showing %d event at most":["Limiter l'affichage au plus à %d évènement","Limiter l'affichage au plus à %d évènements"],"Median":["Médian"],"New Issue":["Nouvel incident","Nouveaux incidents"],"Not available":["Indisponible"],"Not enough data":["Données insuffisantes"],"OpenedNDaysAgo|Opened":["Ouvert"],"Pipeline Health":["Santé du Pipeline"],"ProjectLifecycle|Stage":["Étape"],"Read more":["Lire plus"],"Related Commits":["Validations liés"],"Related Deployed Jobs":["Tâches de déploiement liés"],"Related Issues":["Incidents liés"],"Related Jobs":["Tâches liées"],"Related Merge Requests":["Demandes de fusion liées"],"Related Merged Requests":["Demandes fusionnées liées"],"Showing %d event":["Affichage de %d évènement","Affichage de %d évènements"],"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.":["L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."],"The collection of events added to the data gathered for that stage.":["L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."],"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.":["L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."],"The phase of the development lifecycle.":["Les étapes du cycle de développement."],"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.":["L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."],"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.":["L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."],"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.":["L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."],"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.":["L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."],"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.":["L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."],"The time taken by each data entry gathered by that stage.":["Le temps pris par chaque entrée récoltée durant cette étape."],"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 valeur située au point médian d’une série de valeur observée. C.à .d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."],"Time before an issue gets scheduled":["Temps avant qu’un incident ne soit planifié"],"Time before an issue starts implementation":["Temps avant que résolution ne débute"],"Time between merge request creation and merge/close":["Temps entre la création d'une demande de fusion et sa fusion/clôture"],"Time until first merge request":["Temps jusqu’à la première demande de fusion"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Temps total"],"Total test time for all commits/merges":["Temps total de test pour toutes les validations/fusions"],"Want to see the data? Please ask an administrator for access.":["Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."],"We don't have enough data to show this stage.":["Nous n'avons pas suffisamment de données pour afficher cette étape."],"You need permission.":["Vous avez besoin d’une autorisation."],"day":["jour","jours"],"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} a validé %{commit_timeago}"],"About auto deploy":["A propos de l'auto-déploiement"],"Active":["Actif"],"Activity":["Activité"],"Add Changelog":["Ajouter un journal des modifications"],"Add Contribution guide":["Ajouter un guide de contribution"],"Add License":["Ajouter une licence"],"Add an SSH key to your profile to pull or push via SSH.":["Ajoutez une clef SSH à votre profil pour pouvoir récupérer et pousser par SSH."],"Add new directory":["Ajouter un nouveau dossier"],"Archived project! Repository is read-only":["Projet archivé ! Le dépôt est en lecture seule"],"Are you sure you want to delete this pipeline schedule?":["Êtes-vous sûr de vouloir supprimer ce pipeline programmé"],"Attach a file by drag & drop or %{upload_link}":["Attachez un fichier par glisser & déposer ou %{upload_link}"],"Branch":["Branche","Branches"],"#~ \"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, cho\"#~ \"ose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_do\"#~ \"c}\"":["#~ \"La branche <strong>%{branch_name}</strong> a été crée. Pour mettre en place le\"#~ \" déploiement automatisé, sélectionnez un modèle de fichier Yaml pour Gitlab CI\"#~ \", et validez les modifications. %{link_to_autodeploy_doc}\""],"Branches":["Branches"],"Browse files":["Parcourir les fichiers"],"CI configuration":["Configuration du CI"],"Cancel":["Annuler"],"ChangeTypeActionLabel|Pick into branch":["Sélectionner dans la branche"],"ChangeTypeActionLabel|Revert in branch":["Annuler dans la branche"],"ChangeTypeAction|Cherry-pick":["Sélectionner"],"ChangeType|commit":["validation"],"ChangeType|merge request":["demande de fusion"],"Changelog":["Journal des modifications"],"Charts":["Graphiques"],"Cherry-pick this commit":["Sélectionner cette validation"],"Cherry-pick this merge-request":["Sélectionner cette demande de fusion"],"CiStatusLabel|canceled":["annulé"],"CiStatusLabel|created":["créé"],"CiStatusLabel|failed":["échoué"],"CiStatusLabel|manual action":["action manuelle"],"CiStatusLabel|passed":["passé"],"CiStatusLabel|passed with warnings":["passé avec des avertissements"],"CiStatusLabel|pending":["en attente"],"CiStatusLabel|skipped":["ignoré"],"CiStatusLabel|waiting for manual action":["en attente d'action manuelle"],"CiStatusText|blocked":["bloqué"],"CiStatusText|canceled":["annulé "],"CiStatusText|created":["créé"],"CiStatusText|failed":["échoué"],"CiStatusText|manual":["manuel"],"CiStatusText|passed":["passé"],"CiStatusText|pending":["en attente"],"CiStatusText|skipped":["ignoré"],"CiStatus|running":["en cours"],"Commit message":["Message de validation"],"CommitMessage|Add %{file_name}":["Ajout de %{file_name}"],"Commits":["Validations"],"Commits|History":["Historique"],"Committed by":["Validé par"],"Compare":["Comparer"],"Contribution guide":["Guilde de contribution"],"Contributors":["Contributeurs"],"Copy URL to clipboard":["Copier l'URL dans le presse-papier"],"Copy commit SHA to clipboard":["Copier le SAH de la validation"],"Create New Directory":["Créer un nouveau dossier"],"Create directory":["Créer un dossier"],"Create empty bare repository":["Créer un dépôt vide"],"Create merge request":["Créer une demande de fusion"],"Create new...":["Créer nouveau..."],"CreateNewFork|Fork":["Fork"],"CreateTag|Tag":["Étiquette"],"Cron Timezone":["Fuseau horaire de Cron"],"Cron syntax":["Syntaxe CRON"],"Custom":["Personnalisé"],"Custom notification events":["Événements de notification personnalisés"],"#~ \"Custom notification levels are the same as participating levels. With custom n\"#~ \"otification levels you will also receive notifications for select events. To f\"#~ \"ind out more, check out %{notification_link}.\"":["#~ \"Le niveau de notification Personnalisé est similaire au niveau Participation. \"#~ \"Il permet cependant également de recevoir des notifications pour des événement\"#~ \"s sélectionnés. Pour plus d’information, vous pouvez consulter %{notification_\"#~ \"link}.\""],"Cycle Analytics":["Analyseur de cycle"],"Define a custom pattern with cron syntax":["Définir un schéma personnalisé avec une syntaxe CRON"],"Delete":["Supprimer"],"Description":["Description"],"Directory name":["Nom du dossier"],"Don't show again":["Ne plus montrer"],"Download":["Télécharger"],"Download tar":["Télécharger tar"],"Download tar.bz2":["Télécharger tar.bz2"],"Download tar.gz":["Télécharger tar.gz"],"Download zip":["Télécharger zip"],"DownloadArtifacts|Download":["Télécharger"],"DownloadCommit|Email Patches":["Patch email"],"DownloadCommit|Plain Diff":["Diff simple"],"DownloadSource|Download":["Télécharger"],"Edit":["Éditer"],"Edit Pipeline Schedule %{id}":["Éditer le pipeline programmé %{id}"],"Every day (at 4:00am)":["Chaque jour (à 4:00 du matin)"],"Every month (on the 1st at 4:00am)":["Chaque mois (le 1er à 4:00 du matin)"],"Every week (Sundays at 4:00am)":["Chaque semaine (Dimanche à 4:00 du matin)"],"Failed to change the owner":["Échec du changement de propriétaire"],"Failed to remove the pipeline schedule":["Échec de la suppression du pipeline programmé"],"Files":["Fichiers"],"Find by path":["Rechercher par chemin"],"Find file":["Rechercher un fichier"],"Fork":["Fork","Forks"],"ForkedFromProjectPath|Forked from":["Forké depuis"],"Go to your fork":["Aller à votre fork"],"GoToYourFork|Fork":["Fork"],"Home":["Accueil"],"Housekeeping successfully started":["Maintenance démarrée avec succès"],"Import repository":["Importer un dépôt"],"Interval Pattern":["Schéma d’intervalle"],"LFSStatus|Disabled":["Désactivé"],"LFSStatus|Enabled":["Activé"],"Last Pipeline":["Dernier pipeline"],"Last Update":["Dernière mise à jour"],"Last commit":["Dernière validation"],"Learn more in the":["En apprendre plus dans le"],"Leave group":["Quitter le groupe"],"Leave project":["Quitter le projet"],"MissingSSHKeyWarningLink|add an SSH key":["ajouter un clef SSH"],"New Pipeline Schedule":["Nouveau pipeline programmé"],"New branch":["Nouvelle branche"],"New directory":["Nouveau dossier"],"New file":["Nouveau Fichier"],"New issue":["Nouvel incident"],"New merge request":["Nouvelle demande de fusion"],"New schedule":["Nouveau programme"],"New snippet":["Nouvel extrait de code"],"New tag":["Nouvelle étiquette"],"No repository":["Pas de dépôt"],"No schedules":["Aucun programme"],"Notification events":["Événement de notifications"],"NotificationEvent|Close issue":["Clore l'incident"],"NotificationEvent|Close merge request":["Clore la demande de fusion"],"NotificationEvent|Failed pipeline":["Pipeline échoué"],"NotificationEvent|Merge merge request":["Fusionner le demande de fusion"],"NotificationEvent|New issue":["Nouvel incident"],"NotificationEvent|New merge request":["Nouvelle demande de fusion"],"NotificationEvent|New note":["Nouvelle note"],"NotificationEvent|Reassign issue":["Réassigner l'incident"],"NotificationEvent|Reassign merge request":["Réassigner la demande de fusion"],"NotificationEvent|Reopen issue":["Ré-ouvrir l'incident"],"NotificationEvent|Successful pipeline":["Pipeline réussi"],"NotificationLevel|Custom":["Personnalisé"],"NotificationLevel|Disabled":["Désactivé"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["En cas de mention"],"NotificationLevel|Participate":["Participation"],"NotificationLevel|Watch":["Surveillé"],"OfSearchInADropdown|Filter":["Filtre"],"Options":["Options"],"Owner":["Propriétaire"],"Pipeline":["Pipeline"],"Pipeline Schedule":["Programmation de pipeline"],"Pipeline Schedules":["Programmations de pipeline"],"PipelineSchedules|Activated":["Activé"],"PipelineSchedules|Active":["Active"],"PipelineSchedules|All":["Tous"],"PipelineSchedules|Inactive":["Inactive"],"PipelineSchedules|Next Run":["Prochaine exécution"],"PipelineSchedules|None":["Aucune"],"PipelineSchedules|Provide a short description for this pipeline":["Indiquez une courte description"],"PipelineSchedules|Take ownership":["S’approprier"],"PipelineSchedules|Target":["Cible"],"Project '%{project_name}' queued for deletion.":["Projet '%{project_name}' en attente de suppression."],"Project '%{project_name}' was successfully created.":["Projet '%{project_name}' créé avec succès."],"Project '%{project_name}' was successfully updated.":["Projet '%{project_name}' mis à jour avec succès."],"Project '%{project_name}' will be deleted.":["Projet '%{project_name}' sera supprimé."],"Project access must be granted explicitly to each user.":["L’accès au projet doit être explicitement accordé à chaque utilisateur."],"Project export could not be deleted.":["L'export du projet n'a pas pu être supprimé."],"Project export has been deleted.":["L'export du projet a été supprimé."],"#~ \"Project export link has expired. Please generate a new export from your projec\"#~ \"t settings.\"":["#~ \"Le lien de l’export du projet a expiré. Merci de générer un nouvel export depu\"#~ \"is les paramètres du projet.\""],"Project export started. A download link will be sent by email.":["#~ \"L'export du projet a débuté. Un lien de téléchargement sera envoyé par courrie\"#~ \"l.\""],"Project home":["Accueil du projet"],"ProjectFeature|Disabled":["Désactivé"],"ProjectFeature|Everyone with access":["Toute personne ayant accès"],"ProjectFeature|Only team members":["Seulement les membres de l'équipe"],"ProjectFileTree|Name":["Nom"],"ProjectLastActivity|Never":["Jamais"],"ProjectNetworkGraph|Graph":["Graphique "],"Readme":["LisezMoi"],"RefSwitcher|Branches":["Branches"],"RefSwitcher|Tags":["Étiquettes"],"Remind later":["Me le rappeler ultérieurement"],"Remove project":["Supprimer le projet"],"Request Access":["Demander l'accès"],"Revert this commit":["Annuler cette validation"],"Revert this merge-request":["Annuler cette demande de fusion"],"Save pipeline schedule":["Sauvegarder le pipeline programmé"],"Schedule a new pipeline":["Programmer un nouveau pipeline"],"Scheduling Pipelines":["Programmer des pipelines"],"Search branches and tags":["Rechercher dans les branches et les étiquettes"],"Select Archive Format":["Sélectionnez le format de l'archive"],"Select a timezone":["Sélectionnez un fuseau horaire"],"Select target branch":["Sélectionnez une branche cible"],"Set a password on your account to pull or push via %{protocol}":["#~ \"Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par\"#~ \" %{protocol}\""],"Set up CI":["Mettre en place le CI"],"Set up Koding":["Mettre en place Koding"],"Set up auto deploy":["Mettre en place l’auto-déploiement "],"SetPasswordToCloneLink|set a password":["définir un mot de passe"],"Source code":["Code source"],"StarProject|Star":["S'abonner"],"Start a <strong>new merge request</strong> with these changes":["Créer une <strong>nouvelle demande de fusion</strong> avec ces changements"],"Switch branch/tag":["Changer de branche / d'étiquette"],"Tag":["Étiquette","Étiquettes"],"Tags":["Étiquettes"],"Target Branch":["Branche cible"],"The fork relationship has been removed.":["La relation de fork a été supprimée."],"#~ \"The pipelines schedule runs pipelines in the future, repeatedly, for specific \"#~ \"branches or tags. Those scheduled pipelines will inherit limited project acces\"#~ \"s based on their associated user.\"":["#~ \"Les pipelines programmés exécutent des pipelines dans le futur, de façon répét\"#~ \"ée, pour les branches et étiquettes spécifiées. Ces pipelines programmés hérit\"#~ \"ent d’un accès partiel au projet basé sur l’utilisateur que leurs est associé.\""],"The project can be accessed by any logged in user.":["Votre projet peut être accédé par n’importe quel utilisateur authentifié"],"The project can be accessed without any authentication.":["Votre projet peut être accédé sans aucune authentification."],"The repository for this project does not exist.":["Le dépôt pour ce projet n'existe pas."],"#~ \"This means you can not push code until you create an empty repository or impor\"#~ \"t existing one.\"":["#~ \"Cela signifie que vous ne pouvez pas pousser du code tant que vous ne créez pa\"#~ \"s un dépôt vide, ou importez une dépôt existant.\""],"Timeago|%s days ago":["Il y a %s jours"],"Timeago|%s days remaining":["Il reste %s jours"],"Timeago|%s hours remaining":["Il reste %s heures"],"Timeago|%s minutes ago":["Il y a %s minutes"],"Timeago|%s minutes remaining":["Il reste %s minutes"],"Timeago|%s months ago":["Il y a %s mois"],"Timeago|%s months remaining":["Il reste %s mois"],"Timeago|%s seconds remaining":["Il reste %s secondes"],"Timeago|%s weeks ago":["Il y a %s semaines"],"Timeago|%s weeks remaining":["Il reste %s semaines"],"Timeago|%s years ago":["Il y a %s ans"],"Timeago|%s years remaining":["Il reste %s ans"],"Timeago|1 day remaining":["Il reste un jour"],"Timeago|1 hour remaining":["Il reste une heure"],"Timeago|1 minute remaining":["Il reste une minute"],"Timeago|1 month remaining":["Il reste un mois"],"Timeago|1 week remaining":["Il reste une semaine"],"Timeago|1 year remaining":["Il reste un an"],"Timeago|Past due":["En retard"],"Timeago|a day ago":["Il y a un jour"],"Timeago|a month ago":["Il y a un mois"],"Timeago|a week ago":["Il y a une semaine"],"Timeago|a while":["Il y a un moment"],"Timeago|a year ago":["Il y a un an"],"Timeago|about %s hours ago":["Il y a environ %s heures"],"Timeago|about a minute ago":["Il y a environ une minute"],"Timeago|about an hour ago":["Il y a environ une heure"],"Timeago|in %s days":["Dans %s jours"],"Timeago|in %s hours":["Dans %s heures"],"Timeago|in %s minutes":["Dans %s minutes"],"Timeago|in %s months":["Dans %s mois"],"Timeago|in %s seconds":["Dans %s secondes"],"Timeago|in %s weeks":["Dans %s semaines"],"Timeago|in %s years":["Dans %s années"],"Timeago|in 1 day":["Dans 1 jour"],"Timeago|in 1 hour":["Dans 1 heure"],"Timeago|in 1 minute":["Dans 1 minute"],"Timeago|in 1 month":["Dans 1 mois"],"Timeago|in 1 week":["Dans 1 semaine"],"Timeago|in 1 year":["Dans 1 an"],"Timeago|less than a minute ago":["il y a moins d'une minute"],"Unstar":["Se désabonner"],"Upload New File":["Téléverser un nouveau fichier"],"Upload file":["Téléverser un fichier"],"Use your global notification setting":["Utiliser vos paramètres de notification globaux"],"VisibilityLevel|Internal":["Interne"],"VisibilityLevel|Private":["Privé"],"VisibilityLevel|Public":["Publique"],"Withdraw Access Request":["Retirer la demande d'accès"],"#~ \"You are going to remove %{project_name_with_namespace}.\\n\"#~ \"Removed project CANNOT be restored!\\n\"#~ \"Are you ABSOLUTELY sure?\"":["#~ \"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\\n\"#~ \"Les projets supprimés NE PEUVENT PAS être restaurés !\\n\"#~ \"Êtes vous ABSOLUMENT sûr ? \""],"#~ \"You are going to remove the fork relationship to source project %{forked_from_\"#~ \"project}. Are you ABSOLUTELY sure?\"":["#~ \"Vous allez supprimer la relation de fork avec le projet source %{forked_from_p\"#~ \"roject}. Êtes-vous VRAIMENT sûr.\""],"#~ \"You are going to transfer %{project_name_with_namespace} to another owner. Are\"#~ \" you ABSOLUTELY sure?\"":["#~ \"Vous allez transférer %{project_name_with_namespace} à un nouveau propriétaire\"#~ \". Êtes vous VRAIMENT sûr ?\""],"You can only add files when you are on a branch":["Vous ne pouvez ajouter de fichier que dans une branche"],"You must sign in to star a project":["Vous devez vous identifier pour vous abonner à un projet"],"You will not get any notifications via email":["Vous ne recevrez aucune notification par courriel"],"You will only receive notifications for the events you choose":["#~ \"Vous ne recevrez de notification que pour les évènements que vous aurez choisi\"#~ \"s\""],"You will only receive notifications for threads you have participated in":["#~ \"Vous ne recevrez de notification que pour les sujets auxquels vous avez partic\"#~ \"ipé\""],"You will receive notifications for any activity":["Vous recevrez des notifications pour n’importe quelles activités"],"You will receive notifications only for comments in which you were @mentioned":["#~ \"Vous ne recevrez de notifications que pour les commentaires où vous êtes @ment\"#~ \"ionné\""],"#~ \"You won't be able to pull or push project code via %{protocol} until you %{set\"#~ \"_password_link} on your account\"":["#~ \"Vous ne pourrez pas récupérer ou pousser de code par %{protocol} tant que vo\"#~ \"us n'aurez pas %{set_password_link} pour votre compte\""],"#~ \"You won't be able to pull or push project code via SSH until you %{add_ssh_key\"#~ \"_link} to your profile\"":["#~ \"Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous n’aur\"#~ \"ez pas %{add_ssh_key_link} dans votre profil\""],"Your name":["Votre nom"],"notification emails":["courriels de notification"],"parent":["parent","parents"],"pipeline schedules documentation":["documentation des pipeline programmés"],"with stage":["avec l'étape","avec les étapes"]}}};
\ No newline at end of file diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index c87cab5423f..5b6e56fea4a 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 14:57+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-15 10:34-0400","Last-Translator":"Huang Tao <htve@outlook.com>","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["ç”± %{commit_author_link} æ交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["æ·»åŠ æ›´æ–°æ—¥èªŒ"],"Add Contribution guide":["æ·»åŠ è²¢ç»æŒ‡å—"],"Add License":["æ·»åŠ è¨±å¯è‰"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推é€æˆ–拉å–çš„ SSH 秘鑰到賬號ä¸ã€‚"],"Add new directory":["æ·»åŠ æ–°ç›®éŒ„"],"Archived project! Repository is read-only":["æ¸æª”é …ç›®ï¼å˜å„²åº«ç‚ºåªè®€"],"Are you sure you want to delete this pipeline schedule?":["確定è¦åˆªé™¤æ¤æµæ°´ç·šè¨ˆåŠƒå—Žï¼Ÿ"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到æ¤è™•æˆ–者 %{upload_link}"],"Branch":["分支"],"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}":["分支 <strong>%{branch_name}</strong> 已創建。如需è¨ç½®è‡ªå‹•éƒ¨ç½²ï¼Œ è«‹é¸æ“‡åˆé©çš„ GitLab CI Yaml 模æ¿ä½µæ交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["ç€è¦½æ–‡ä»¶"],"ByAuthor|by":["作者:"],"CI configuration":["CI é…ç½®"],"Cancel":["å–消"],"ChangeTypeActionLabel|Pick into branch":["挑é¸åˆ°åˆ†æ”¯"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優é¸"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優é¸æ¤æ交"],"Cherry-pick this merge request":["優é¸æ¤åˆä½µè«‹æ±‚"],"CiStatusLabel|canceled":["å·²å–消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動æ“作"],"CiStatusLabel|passed":["已通éŽ"],"CiStatusLabel|passed with warnings":["已通éŽä½†æœ‰è¦å‘Š"],"CiStatusLabel|pending":["ç‰å¾…ä¸"],"CiStatusLabel|skipped":["已跳éŽ"],"CiStatusLabel|waiting for manual action":["ç‰å¾…手動æ“作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["å·²å–消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通éŽ"],"CiStatusText|pending":["ç‰å¾…ä¸"],"CiStatusText|skipped":["已跳éŽ"],"CiStatus|running":["é‹è¡Œä¸"],"Commit":["æ交"],"Commit message":["æ交信æ¯"],"CommitBoxTitle|Commit":["æ交"],"CommitMessage|Add %{file_name}":["æ·»åŠ %{file_name}"],"Commits":["æ交"],"Commits|History":["æ·å²"],"Committed by":["æ交於"],"Compare":["比較"],"Contribution guide":["è²¢ç»æŒ‡å—"],"Contributors":["è²¢ç»è€…"],"Copy URL to clipboard":["複製URL到剪貼æ¿"],"Copy commit SHA to clipboard":["複製æ交 SHA 到剪貼æ¿"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的å˜å„²åº«"],"Create merge request":["創建åˆä½µè«‹æ±‚"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時å€"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"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}.":["自定義通知級別繼承自åƒèˆ‡ç´šåˆ¥ã€‚使用自定義通知級別,您會收到åƒèˆ‡ç´šåˆ¥åŠé¸å®šäº‹ä»¶çš„通知。想了解更多信æ¯ï¼Œè«‹æŸ¥çœ‹ %{notification_link}."],"Cycle Analytics":["週期分æž"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分æžæ¦‚è¿°äº†é …ç›®å¾žæƒ³æ³•åˆ°ç”¢å“實ç¾çš„å„階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["è°é¡Œ"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["é 發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模å¼"],"Delete":["刪除"],"Deploy":["部署"],"Description":["æè¿°"],"Directory name":["目錄å稱"],"Don't show again":["ä¸å†é¡¯ç¤º"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["é›»å郵件補ä¸"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} æµæ°´ç·šè¨ˆåŠƒ"],"Every day (at 4:00am)":["æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨4點)"],"Every month (on the 1st at 4:00am)":["æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆ1日淩晨4點)"],"Every week (Sundays at 4:00am)":["æ¯é€±åŸ·è¡Œï¼ˆå‘¨æ—¥æ·©æ™¨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除æµæ°´ç·šè¨ˆåŠƒ"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推é€"],"FirstPushedBy|pushed by":["推é€è€…:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建è°é¡Œåˆ°éƒ¨ç½²åˆ°ç”Ÿç”¢ç’°å¢ƒ"],"From merge request merge until deploy to production":["從åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²è‡³ç”Ÿç”¢ç’°å¢ƒ"],"Go to your fork":["è·³è½‰åˆ°æ´¾ç”Ÿé …ç›®"],"GoToYourFork|Fork":["è·³è½‰åˆ°æ´¾ç”Ÿé …ç›®"],"Home":["首é "],"Housekeeping successfully started":["已開始ç¶è·"],"Import repository":["å°Žå…¥å˜å„²åº«"],"Interval Pattern":["é‡è¤‡é€±æœŸ"],"Introducing Cycle Analytics":["週期分æžç°¡ä»‹"],"LFSStatus|Disabled":["åœç”¨"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新æµæ°´ç·š"],"Last Update":["最後更新"],"Last commit":["最後æ交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["æµæ°´ç·šè¨ˆåŠƒæ–‡æª”"],"Leave group":["退出群組"],"Leave project":["é€€å‡ºé …ç›®"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["ä¸ä½æ•¸"],"MissingSSHKeyWarningLink|add an SSH key":["æ·»åŠ å£¹å€‹ SSH 公鑰"],"New Issue":["新建è°é¡Œ"],"New Pipeline Schedule":["創建æµæ°´ç·šè¨ˆåŠƒ"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["æ–°è°é¡Œ"],"New merge request":["新增åˆä½µè«‹æ±‚"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有å˜å„²åº«"],"No schedules":["沒有計劃"],"Not available":["ä¸å¯ç”¨"],"Not enough data":["數據ä¸è¶³"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉è°é¡Œ"],"NotificationEvent|Close merge request":["關閉åˆä½µè«‹æ±‚"],"NotificationEvent|Failed pipeline":["æµæ°´ç·šå¤±æ•—"],"NotificationEvent|Merge merge request":["åˆä½µè«‹æ±‚被åˆä½µ"],"NotificationEvent|New issue":["新增è°é¡Œ"],"NotificationEvent|New merge request":["æ–°åˆä½µè«‹æ±‚"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["é‡æ–°æŒ‡æ´¾è°é¡Œ"],"NotificationEvent|Reassign merge request":["é‡æ–°æŒ‡æ´¾åˆä½µè«‹æ±‚"],"NotificationEvent|Reopen issue":["é‡æ–°æ‰“é–‹è°é¡Œ"],"NotificationEvent|Successful pipeline":["æµæ°´ç·šæˆåŠŸå®Œæˆ"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["åœç”¨"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["æåŠ"],"NotificationLevel|Participate":["åƒèˆ‡"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩é¸"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["æ“作"],"Owner":["所有者"],"Pipeline":["æµæ°´ç·š"],"Pipeline Health":["æµæ°´ç·šå¥åº·æŒ‡æ¨™"],"Pipeline Schedule":["æµæ°´ç·šè¨ˆåŠƒ"],"Pipeline Schedules":["æµæ°´ç·šè¨ˆåŠƒ"],"PipelineSchedules|Activated":["是å¦å•Ÿç”¨"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次é‹è¡Œæ™‚é–“"],"PipelineSchedules|None":["ç„¡"],"PipelineSchedules|Provide a short description for this pipeline":["為æ¤æµæ°´ç·šæ供簡çŸæè¿°"],"PipelineSchedules|Take ownership":["å–得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["é …ç›® '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["é …ç›® '%{project_name}' 已創建æˆåŠŸã€‚"],"Project '%{project_name}' was successfully updated.":["é …ç›® '%{project_name}' 已更新完æˆã€‚"],"Project '%{project_name}' will be deleted.":["é …ç›® '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["é …ç›®è¨ªå•æ¬Šé™å¿…é ˆæ˜Žç¢ºæŽˆæ¬Šçµ¦æ¯å€‹ç”¨æˆ¶ã€‚"],"Project export could not be deleted.":["ç„¡æ³•åˆªé™¤é …ç›®å°Žå‡ºã€‚"],"Project export has been deleted.":["é …ç›®å°Žå‡ºå·²è¢«åˆªé™¤ã€‚"],"Project export link has expired. Please generate a new export from your project settings.":["é …ç›®å°Žå‡ºéˆæŽ¥å·²éŽæœŸã€‚è«‹å¾žé …ç›®è¨ç½®ä¸é‡æ–°ç”Ÿæˆé …目導出。"],"Project export started. A download link will be sent by email.":["é …ç›®å°Žå‡ºå·²é–‹å§‹ã€‚ä¸‹è¼‰éˆæŽ¥å°‡é€šéŽé›»å郵件發é€ã€‚"],"Project home":["é …ç›®é¦–é "],"ProjectFeature|Disabled":["åœç”¨"],"ProjectFeature|Everyone with access":["任何人都å¯è¨ªå•"],"ProjectFeature|Only team members":["åªé™åœ˜éšŠæˆå“¡"],"ProjectFileTree|Name":["å稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的æ交"],"Related Deployed Jobs":["相關的部署作æ¥"],"Related Issues":["相關的è°é¡Œ"],"Related Jobs":["相關的作æ¥"],"Related Merge Requests":["相關的åˆä½µè«‹æ±‚"],"Related Merged Requests":["相關已åˆä½µçš„åˆä½µè«‹æ±‚"],"Remind later":["ç¨å¾Œæ醒"],"Remove project":["åˆªé™¤é …ç›®"],"Request Access":["申請訪å•"],"Revert this commit":["還原æ¤æ交"],"Revert this merge request":["還原æ¤åˆä½µè«‹æ±‚"],"Save pipeline schedule":["ä¿å˜æµæ°´ç·šè¨ˆåŠƒ"],"Schedule a new pipeline":["新建æµæ°´ç·šè¨ˆåŠƒ"],"Scheduling Pipelines":["æµæ°´ç·šè¨ˆåŠƒ"],"Search branches and tags":["æœç´¢åˆ†æ”¯å’Œæ¨™ç±¤"],"Select Archive Format":["é¸æ“‡ä¸‹è¼‰æ ¼å¼"],"Select a timezone":["é¸æ“‡æ™‚å€"],"Select target branch":["é¸æ“‡ç›®æ¨™åˆ†æ”¯"],"Set a password on your account to pull or push via %{protocol}":["ç‚ºè³¬è™Ÿæ·»åŠ å£¹å€‹ç”¨æ–¼æŽ¨é€æˆ–拉å–çš„ %{protocol} 密碼。"],"Set up CI":["è¨ç½® CI"],"Set up Koding":["è¨ç½® Koding"],"Set up auto deploy":["è¨ç½®è‡ªå‹•éƒ¨ç½²"],"SetPasswordToCloneLink|set a password":["è¨ç½®å¯†ç¢¼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["æºä»£ç¢¼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["ç”±æ¤æ›´æ”¹ %{new_merge_request}"],"Start a <strong>new merge request</strong> with these changes":["ç”±æ¤æ›´æ”¹å‰µå»º<strong>æ–°åˆä½µè«‹æ±‚</strong>"],"Switch branch/tag":["切æ›åˆ†æ”¯/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"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.":["編碼階段概述了從第壹次æ交到創建åˆä½µè«‹æ±‚的時間。創建第壹個åˆä½µè«‹æ±‚å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"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.":["è°é¡ŒéšŽæ®µæ¦‚述了從創建è°é¡Œåˆ°å°‡è°é¡Œæ·»åŠ 到è£ç¨‹ç¢‘或è°é¡Œçœ‹æ¿çš„時間。創建第壹個è°é¡Œå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•.。"],"The phase of the development lifecycle.":["é …ç›®ç”Ÿå‘½é€±æœŸä¸çš„å„個階段。"],"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.":["æµæ°´ç·šè¨ˆåŠƒæœƒé€±æœŸæ€§é‡è¤‡é‹è¡ŒæŒ‡å®šåˆ†æ”¯æˆ–標籤的æµæ°´ç·šã€‚這些æµæ°´ç·šå°‡æ ¹æ“šå…¶é—œè¯ç”¨æˆ¶ç¹¼æ‰¿æœ‰é™çš„é …ç›®è¨ªå•æ¬Šé™ã€‚"],"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.":["計劃階段概述了從è°é¡Œæ·»åŠ 到日程到推é€é¦–次æ交的時間。當首次推é€æäº¤å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"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.":["生產階段概述了從創建è°é¡Œåˆ°å°‡ä»£ç¢¼éƒ¨ç½²åˆ°ç”Ÿç”¢ç’°å¢ƒçš„時間。當完æˆå®Œæ•´çš„æƒ³æ³•åˆ°éƒ¨ç½²ç”Ÿç”¢ï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"The project can be accessed by any logged in user.":["è©²é …ç›®å…許已登錄的用戶訪å•ã€‚"],"The project can be accessed without any authentication.":["è©²é …ç›®å…許任何人訪å•ã€‚"],"The repository for this project does not exist.":["æ¤é …目的å˜å„²åº«ä¸å˜åœ¨ã€‚"],"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.":["評審階段概述了從創建åˆä½µè«‹æ±‚到åˆä½µçš„時間。當創建第壹個åˆä½µè«‹æ±‚å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"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.":["é 發布階段概述了åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²ä»£ç¢¼åˆ°ç”Ÿç”¢ç’°å¢ƒçš„ç¸½æ™‚é–“ã€‚ç•¶é¦–æ¬¡éƒ¨ç½²åˆ°ç”Ÿç”¢ç’°å¢ƒå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"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.":["測試階段概述了 GitLab CI 為相關åˆä½µè«‹æ±‚é‹è¡Œæ¯å€‹æµæ°´ç·šæ‰€éœ€çš„時間。當第壹個æµæ°´ç·šé‹è¡Œå®Œæˆå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•ã€‚"],"The time taken by each data entry gathered by that stage.":["該階段æ¯æ¢æ•¸æ“šæ‰€èŠ±çš„時間"],"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.":["ä¸ä½æ•¸æ˜¯å£¹å€‹æ•¸åˆ—ä¸æœ€ä¸é–“的值。例如在 3ã€5ã€9 之間,ä¸ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,ä¸ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的å˜å„²åº«æˆ–å°Žå…¥ç¾æœ‰å˜å„²åº«ä¹‹å‰ï¼Œæ‚¨å°‡ç„¡æ³•æŽ¨é€ä»£ç¢¼ã€‚"],"Time before an issue gets scheduled":["è°é¡Œè¢«åˆ—入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼å‰çš„時間"],"Time between merge request creation and merge/close":["從創建åˆä½µè«‹æ±‚到被åˆä½µæˆ–關閉的時間"],"Time until first merge request":["創建第壹個åˆä½µè«‹æ±‚之å‰çš„時間"],"Timeago|%s days ago":["%s 天å‰"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s å°æ™‚"],"Timeago|%s minutes ago":["%s 分é˜å‰"],"Timeago|%s minutes remaining":["剩餘 %s 分é˜"],"Timeago|%s months ago":["%s 個月å‰"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期å‰"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s å¹´å‰"],"Timeago|%s years remaining":["剩餘 %s å¹´"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 å°æ™‚"],"Timeago|1 minute remaining":["剩餘 1 分é˜"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 å¹´"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天å‰"],"Timeago|a month ago":["1 個月å‰"],"Timeago|a week ago":["1 星期å‰"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 å¹´å‰"],"Timeago|about %s hours ago":["ç´„ %s å°æ™‚å‰"],"Timeago|about a minute ago":["ç´„ 1 分é˜å‰"],"Timeago|about an hour ago":["ç´„ 1 å°æ™‚å‰"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s å°æ™‚後"],"Timeago|in %s minutes":[" %s 分é˜å¾Œ"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 å°æ™‚後"],"Timeago|in 1 minute":[" 1 分é˜å¾Œ"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["ä¸åˆ° 1 分é˜å‰"],"Time|hr":["å°æ™‚"],"Time|min":["分é˜"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有æ交和åˆä½µçš„總測試時間"],"Unstar":["å–消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知è¨ç½®"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["ç§æœ‰"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權é™ä¸è¶³ã€‚如需查看相關數據,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚"],"We don't have enough data to show this stage.":["該階段的數據ä¸è¶³ï¼Œç„¡æ³•é¡¯ç¤ºã€‚"],"Withdraw Access Request":["å–消訪å•è«‹æ±‚"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。\\nå·²åˆªé™¤çš„é …ç›®ç„¡æ³•æ¢è¤‡ï¼\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["å³å°‡åˆªé™¤èˆ‡æºé …ç›® %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["å³å°‡ %{project_name_with_namespace} 轉義給å¦å£¹å€‹æ‰€æœ‰è€…。確定繼續嗎?"],"You can only add files when you are on a branch":["åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ 文件"],"You must sign in to star a project":["å¿…é ˆç™»éŒ„æ‰èƒ½å°é …ç›®åŠ æ˜Ÿæ¨™"],"You need permission.":["需è¦ç›¸é—œçš„權é™ã€‚"],"You will not get any notifications via email":["ä¸æœƒæ”¶åˆ°ä»»ä½•é€šçŸ¥éƒµä»¶"],"You will only receive notifications for the events you choose":["åªæŽ¥æ”¶æ‚¨é¸æ“‡çš„事件通知"],"You will only receive notifications for threads you have participated in":["åªæŽ¥æ”¶æ‚¨åƒèˆ‡çš„主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["åªæŽ¥æ”¶è©•è«–ä¸æåŠ(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之å‰å°‡ç„¡æ³•é€šéŽ %{protocol} 拉å–或推é€ä»£ç¢¼ã€‚"],"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} 之å‰å°‡ç„¡æ³•é€šéŽ SSH 拉å–或推é€ä»£ç¢¼ã€‚"],"Your name":["您的åå—"],"day":["天"],"new merge request":["新建åˆä½µè«‹æ±‚"],"notification emails":["通知郵件"],"parent":["父級"]}}};
\ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"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-19 09:57-0400","Last-Translator":"Huang Tao <htve@outlook.com>","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=1; plural=0","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0"},"%{commit_author_link} committed %{commit_timeago}":["ç”± %{commit_author_link} æ交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["æ·»åŠ æ›´æ–°æ—¥èªŒ"],"Add Contribution guide":["æ·»åŠ è²¢ç»æŒ‡å—"],"Add License":["æ·»åŠ è¨±å¯è‰"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推é€æˆ–拉å–çš„ SSH 秘鑰到賬號ä¸ã€‚"],"Add new directory":["æ·»åŠ æ–°ç›®éŒ„"],"Archived project! Repository is read-only":["æ¸æª”é …ç›®ï¼å˜å„²åº«ç‚ºåªè®€"],"Are you sure you want to delete this pipeline schedule?":["確定è¦åˆªé™¤æ¤æµæ°´ç·šè¨ˆåŠƒå—Žï¼Ÿ"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到æ¤è™•æˆ–者 %{upload_link}"],"Branch":["分支"],"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}":["分支 <strong>%{branch_name}</strong> 已創建。如需è¨ç½®è‡ªå‹•éƒ¨ç½²ï¼Œ è«‹é¸æ“‡åˆé©çš„ GitLab CI Yaml 模æ¿ä½µæ交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["ç€è¦½æ–‡ä»¶"],"ByAuthor|by":["作者:"],"CI configuration":["CI é…ç½®"],"Cancel":["å–消"],"ChangeTypeActionLabel|Pick into branch":["挑é¸åˆ°åˆ†æ”¯"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優é¸"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優é¸æ¤æ交"],"Cherry-pick this merge request":["優é¸æ¤åˆä½µè«‹æ±‚"],"CiStatusLabel|canceled":["å·²å–消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動æ“作"],"CiStatusLabel|passed":["已通éŽ"],"CiStatusLabel|passed with warnings":["已通éŽä½†æœ‰è¦å‘Š"],"CiStatusLabel|pending":["ç‰å¾…ä¸"],"CiStatusLabel|skipped":["已跳éŽ"],"CiStatusLabel|waiting for manual action":["ç‰å¾…手動æ“作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["å·²å–消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通éŽ"],"CiStatusText|pending":["ç‰å¾…ä¸"],"CiStatusText|skipped":["已跳éŽ"],"CiStatus|running":["é‹è¡Œä¸"],"Commit":["æ交"],"Commit message":["æ交信æ¯"],"CommitBoxTitle|Commit":["æ交"],"CommitMessage|Add %{file_name}":["æ·»åŠ %{file_name}"],"Commits":["æ交"],"Commits|History":["æ·å²"],"Committed by":["æ交於"],"Compare":["比較"],"Contribution guide":["è²¢ç»æŒ‡å—"],"Contributors":["è²¢ç»è€…"],"Copy URL to clipboard":["複製URL到剪貼æ¿"],"Copy commit SHA to clipboard":["複製æ交 SHA 到剪貼æ¿"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的å˜å„²åº«"],"Create merge request":["創建åˆä½µè«‹æ±‚"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時å€"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"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}.":["自定義通知級別繼承自åƒèˆ‡ç´šåˆ¥ã€‚使用自定義通知級別,您會收到åƒèˆ‡ç´šåˆ¥åŠé¸å®šäº‹ä»¶çš„通知。想了解更多信æ¯ï¼Œè«‹æŸ¥çœ‹ %{notification_link}."],"Cycle Analytics":["週期分æž"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分æžæ¦‚è¿°äº†é …ç›®å¾žæƒ³æ³•åˆ°ç”¢å“實ç¾çš„å„階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["è°é¡Œ"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["é 發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模å¼"],"Delete":["刪除"],"Deploy":["部署"],"Description":["æè¿°"],"Directory name":["目錄å稱"],"Don't show again":["ä¸å†é¡¯ç¤º"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["é›»å郵件補ä¸"],"DownloadCommit|Plain Diff":["差異文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} æµæ°´ç·šè¨ˆåŠƒ"],"Every day (at 4:00am)":["æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨ 4 點)"],"Every month (on the 1st at 4:00am)":["æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆ 1 日淩晨 4 點)"],"Every week (Sundays at 4:00am)":["æ¯é€±åŸ·è¡Œï¼ˆå‘¨æ—¥æ·©æ™¨ 4 點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除æµæ°´ç·šè¨ˆåŠƒ"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推é€"],"FirstPushedBy|pushed by":["推é€è€…:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建è°é¡Œåˆ°éƒ¨ç½²åˆ°ç”Ÿç”¢ç’°å¢ƒ"],"From merge request merge until deploy to production":["從åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²è‡³ç”Ÿç”¢ç’°å¢ƒ"],"Go to your fork":["è·³è½‰åˆ°æ´¾ç”Ÿé …ç›®"],"GoToYourFork|Fork":["è·³è½‰åˆ°æ´¾ç”Ÿé …ç›®"],"Home":["首é "],"Housekeeping successfully started":["已開始ç¶è·"],"Import repository":["å°Žå…¥å˜å„²åº«"],"Interval Pattern":["循環週期"],"Introducing Cycle Analytics":["週期分æžç°¡ä»‹"],"LFSStatus|Disabled":["åœç”¨"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新æµæ°´ç·š"],"Last Update":["最後更新"],"Last commit":["最後æ交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["æµæ°´ç·šè¨ˆåŠƒæ–‡æª”"],"Leave group":["退出群組"],"Leave project":["é€€å‡ºé …ç›®"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["ä¸ä½æ•¸"],"MissingSSHKeyWarningLink|add an SSH key":["æ·»åŠ å£¹å€‹ SSH 公鑰"],"New Issue":["新建è°é¡Œ"],"New Pipeline Schedule":["創建æµæ°´ç·šè¨ˆåŠƒ"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["æ–°è°é¡Œ"],"New merge request":["新增åˆä½µè«‹æ±‚"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有å˜å„²åº«"],"No schedules":["沒有計劃"],"Not available":["ä¸å¯ç”¨"],"Not enough data":["數據ä¸è¶³"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉è°é¡Œ"],"NotificationEvent|Close merge request":["關閉åˆä½µè«‹æ±‚"],"NotificationEvent|Failed pipeline":["æµæ°´ç·šå¤±æ•—"],"NotificationEvent|Merge merge request":["åˆä½µè«‹æ±‚被åˆä½µ"],"NotificationEvent|New issue":["新增è°é¡Œ"],"NotificationEvent|New merge request":["æ–°åˆä½µè«‹æ±‚"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["é‡æ–°æŒ‡æ´¾è°é¡Œ"],"NotificationEvent|Reassign merge request":["é‡æ–°æŒ‡æ´¾åˆä½µè«‹æ±‚"],"NotificationEvent|Reopen issue":["é‡å•Ÿè°é¡Œ"],"NotificationEvent|Successful pipeline":["æµæ°´ç·šæˆåŠŸå®Œæˆ"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["åœç”¨"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["æåŠ"],"NotificationLevel|Participate":["åƒèˆ‡"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩é¸"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["æ“作"],"Owner":["所有者"],"Pipeline":["æµæ°´ç·š"],"Pipeline Health":["æµæ°´ç·šå¥åº·æŒ‡æ¨™"],"Pipeline Schedule":["æµæ°´ç·šè¨ˆåŠƒ"],"Pipeline Schedules":["æµæ°´ç·šè¨ˆåŠƒ"],"PipelineSchedules|Activated":["是å¦å•Ÿç”¨"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次é‹è¡Œæ™‚é–“"],"PipelineSchedules|None":["ç„¡"],"PipelineSchedules|Provide a short description for this pipeline":["為æ¤æµæ°´ç·šæ供簡çŸæè¿°"],"PipelineSchedules|Take ownership":["å–得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["é …ç›® '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["é …ç›® '%{project_name}' 已創建æˆåŠŸã€‚"],"Project '%{project_name}' was successfully updated.":["é …ç›® '%{project_name}' 已更新完æˆã€‚"],"Project '%{project_name}' will be deleted.":["é …ç›® '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["é …ç›®è¨ªå•æ¬Šé™å¿…é ˆæ˜Žç¢ºæŽˆæ¬Šçµ¦æ¯å€‹ç”¨æˆ¶ã€‚"],"Project export could not be deleted.":["ç„¡æ³•åˆªé™¤é …ç›®å°Žå‡ºã€‚"],"Project export has been deleted.":["é …ç›®å°Žå‡ºå·²è¢«åˆªé™¤ã€‚"],"Project export link has expired. Please generate a new export from your project settings.":["é …ç›®å°Žå‡ºéˆæŽ¥å·²éŽæœŸã€‚è«‹å¾žé …ç›®è¨ç½®ä¸é‡æ–°ç”Ÿæˆé …目導出。"],"Project export started. A download link will be sent by email.":["é …ç›®å°Žå‡ºå·²é–‹å§‹ã€‚ä¸‹è¼‰éˆæŽ¥å°‡é€šéŽé›»å郵件發é€ã€‚"],"Project home":["é …ç›®é¦–é "],"ProjectFeature|Disabled":["åœç”¨"],"ProjectFeature|Everyone with access":["任何人都å¯è¨ªå•"],"ProjectFeature|Only team members":["åªé™åœ˜éšŠæˆå“¡"],"ProjectFileTree|Name":["å稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的æ交"],"Related Deployed Jobs":["相關的部署作æ¥"],"Related Issues":["相關的è°é¡Œ"],"Related Jobs":["相關的作æ¥"],"Related Merge Requests":["相關的åˆä½µè«‹æ±‚"],"Related Merged Requests":["相關已åˆä½µçš„åˆä½µè«‹æ±‚"],"Remind later":["ç¨å¾Œæ醒"],"Remove project":["åˆªé™¤é …ç›®"],"Request Access":["申請權é™"],"Revert this commit":["還原æ¤æ交"],"Revert this merge request":["還原æ¤åˆä½µè«‹æ±‚"],"Save pipeline schedule":["ä¿å˜æµæ°´ç·šè¨ˆåŠƒ"],"Schedule a new pipeline":["新建æµæ°´ç·šè¨ˆåŠƒ"],"Scheduling Pipelines":["æµæ°´ç·šè¨ˆåŠƒ"],"Search branches and tags":["æœç´¢åˆ†æ”¯å’Œæ¨™ç±¤"],"Select Archive Format":["é¸æ“‡ä¸‹è¼‰æ ¼å¼"],"Select a timezone":["é¸æ“‡æ™‚å€"],"Select target branch":["é¸æ“‡ç›®æ¨™åˆ†æ”¯"],"Set a password on your account to pull or push via %{protocol}":["ç‚ºè³¬è™Ÿæ·»åŠ å£¹å€‹ç”¨æ–¼æŽ¨é€æˆ–拉å–çš„ %{protocol} 密碼。"],"Set up CI":["è¨ç½® CI"],"Set up Koding":["è¨ç½® Koding"],"Set up auto deploy":["è¨ç½®è‡ªå‹•éƒ¨ç½²"],"SetPasswordToCloneLink|set a password":["è¨ç½®å¯†ç¢¼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["æºä»£ç¢¼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["ç”±æ¤æ›´æ”¹ %{new_merge_request}"],"Switch branch/tag":["切æ›åˆ†æ”¯/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"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.":["編碼階段概述了從第壹次æ交到創建åˆä½µè«‹æ±‚的時間。創建第壹個åˆä½µè«‹æ±‚å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"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.":["è°é¡ŒéšŽæ®µæ¦‚述了從創建è°é¡Œåˆ°å°‡è°é¡Œæ·»åŠ 到è£ç¨‹ç¢‘或è°é¡Œçœ‹æ¿æ‰€èŠ±è²»çš„時間。創建第壹個è°é¡Œå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•.。"],"The phase of the development lifecycle.":["é …ç›®ç”Ÿå‘½é€±æœŸä¸çš„å„個階段。"],"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.":["æµæ°´ç·šè¨ˆåŠƒæœƒé€±æœŸæ€§é‡è¤‡é‹è¡ŒæŒ‡å®šåˆ†æ”¯æˆ–標籤的æµæ°´ç·šã€‚這些æµæ°´ç·šå°‡æ ¹æ“šå…¶é—œè¯ç”¨æˆ¶ç¹¼æ‰¿æœ‰é™çš„é …ç›®è¨ªå•æ¬Šé™ã€‚"],"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.":["計劃階段概述了從è°é¡Œæ·»åŠ 到日程到推é€é¦–次æ交的時間。當首次推é€æäº¤å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"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.":["生產階段概述了從創建è°é¡Œåˆ°å°‡ä»£ç¢¼éƒ¨ç½²åˆ°ç”Ÿç”¢ç’°å¢ƒçš„時間。當完æˆå®Œæ•´çš„æƒ³æ³•åˆ°éƒ¨ç½²ç”Ÿç”¢ï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"The project can be accessed by any logged in user.":["è©²é …ç›®å…許已登錄的用戶訪å•ã€‚"],"The project can be accessed without any authentication.":["è©²é …ç›®å…許任何人訪å•ã€‚"],"The repository for this project does not exist.":["æ¤é …目的å˜å„²åº«ä¸å˜åœ¨ã€‚"],"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.":["評審階段概述了從創建åˆä½µè«‹æ±‚到åˆä½µçš„時間。當創建第壹個åˆä½µè«‹æ±‚å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"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.":["é 發布階段概述了åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²ä»£ç¢¼åˆ°ç”Ÿç”¢ç’°å¢ƒçš„ç¸½æ™‚é–“ã€‚ç•¶é¦–æ¬¡éƒ¨ç½²åˆ°ç”Ÿç”¢ç’°å¢ƒå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚"],"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.":["測試階段概述了 GitLab CI 為相關åˆä½µè«‹æ±‚é‹è¡Œæ¯å€‹æµæ°´ç·šæ‰€éœ€çš„時間。當第壹個æµæ°´ç·šé‹è¡Œå®Œæˆå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•ã€‚"],"The time taken by each data entry gathered by that stage.":["該階段æ¯æ¢æ•¸æ“šæ‰€èŠ±çš„時間"],"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.":["ä¸ä½æ•¸æ˜¯å£¹å€‹æ•¸åˆ—ä¸æœ€ä¸é–“的值。例如在 3ã€5ã€9 之間,ä¸ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,ä¸ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的å˜å„²åº«æˆ–å°Žå…¥ç¾æœ‰å˜å„²åº«ä¹‹å‰ï¼Œæ‚¨å°‡ç„¡æ³•æŽ¨é€ä»£ç¢¼ã€‚"],"Time before an issue gets scheduled":["è°é¡Œè¢«åˆ—入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼å‰çš„時間"],"Time between merge request creation and merge/close":["從創建åˆä½µè«‹æ±‚到被åˆä½µæˆ–關閉的時間"],"Time until first merge request":["創建第壹個åˆä½µè«‹æ±‚之å‰çš„時間"],"Timeago|%s days ago":[" %s 天å‰"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s å°æ™‚"],"Timeago|%s minutes ago":[" %s 分é˜å‰"],"Timeago|%s minutes remaining":["剩餘 %s 分é˜"],"Timeago|%s months ago":[" %s 個月å‰"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":[" %s 星期å‰"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":[" %s å¹´å‰"],"Timeago|%s years remaining":["剩餘 %s å¹´"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 å°æ™‚"],"Timeago|1 minute remaining":["剩餘 1 分é˜"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 å¹´"],"Timeago|Past due":["逾期"],"Timeago|a day ago":[" 1 天å‰"],"Timeago|a month ago":[" 1 個月å‰"],"Timeago|a week ago":[" 1 星期å‰"],"Timeago|a while":[" 剛剛"],"Timeago|a year ago":[" 1 å¹´å‰"],"Timeago|about %s hours ago":["ç´„ %s å°æ™‚å‰"],"Timeago|about a minute ago":["ç´„ 1 分é˜å‰"],"Timeago|about an hour ago":["ç´„ 1 å°æ™‚å‰"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s å°æ™‚後"],"Timeago|in %s minutes":[" %s 分é˜å¾Œ"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 å°æ™‚後"],"Timeago|in 1 minute":[" 1 分é˜å¾Œ"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["ä¸åˆ° 1 分é˜å‰"],"Time|hr":["å°æ™‚"],"Time|min":["分é˜"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有æ交和åˆä½µçš„總測試時間"],"Unstar":["å–消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知è¨ç½®"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["ç§æœ‰"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權é™ä¸è¶³ã€‚如需查看相關數據,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚"],"We don't have enough data to show this stage.":["該階段的數據ä¸è¶³ï¼Œç„¡æ³•é¡¯ç¤ºã€‚"],"Withdraw Access Request":["å–消權é™ç”³è¯·"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。\\nå·²åˆªé™¤çš„é …ç›®ç„¡æ³•æ¢è¤‡ï¼\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["å³å°‡åˆªé™¤èˆ‡æºé …ç›® %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["å³å°‡ %{project_name_with_namespace} 轉義給å¦å£¹å€‹æ‰€æœ‰è€…。確定繼續嗎?"],"You can only add files when you are on a branch":["åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ 文件"],"You have reached your project limit":["您已é”åˆ°é …ç›®æ•¸é‡é™åˆ¶"],"You must sign in to star a project":["å¿…é ˆç™»éŒ„æ‰èƒ½å°é …ç›®åŠ æ˜Ÿæ¨™"],"You need permission.":["需è¦ç›¸é—œçš„權é™ã€‚"],"You will not get any notifications via email":["ä¸æœƒæ”¶åˆ°ä»»ä½•é€šçŸ¥éƒµä»¶"],"You will only receive notifications for the events you choose":["åªæŽ¥æ”¶æ‚¨é¸æ“‡çš„事件通知"],"You will only receive notifications for threads you have participated in":["åªæŽ¥æ”¶æ‚¨åƒèˆ‡çš„主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["åªæŽ¥æ”¶è©•è«–ä¸æåŠ(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之å‰å°‡ç„¡æ³•é€šéŽ %{protocol} 拉å–或推é€ä»£ç¢¼ã€‚"],"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} 之å‰å°‡ç„¡æ³•é€šéŽ SSH 拉å–或推é€ä»£ç¢¼ã€‚"],"Your name":["您的åå—"],"day":["天"],"new merge request":["新建åˆä½µè«‹æ±‚"],"notification emails":["通知郵件"],"parent":["父級"]}}};
\ No newline at end of file diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index 841b24a60a3..3e07ec4d0aa 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -4,83 +4,7 @@ (function() { this.Milestone = (function() { - Milestone.updateIssue = function(li, issue_url, data) { - return $.ajax({ - type: "PUT", - url: issue_url, - data: data, - success: function(_data) { - return Milestone.successCallback(_data, li); - }, - error: function(data) { - return new Flash("Issue update failed", 'alert'); - }, - dataType: "json" - }); - }; - - Milestone.sortIssues = function(url, data) { - return $.ajax({ - type: "PUT", - url, - data: data, - success: function(_data) { - return Milestone.successCallback(_data); - }, - error: function() { - return new Flash("Issues update failed", 'alert'); - }, - dataType: "json" - }); - }; - - Milestone.sortMergeRequests = function(url, data) { - return $.ajax({ - type: "PUT", - url, - data: data, - success: function(_data) { - return Milestone.successCallback(_data); - }, - error: function(data) { - return new Flash("Issue update failed", 'alert'); - }, - dataType: "json" - }); - }; - - Milestone.updateMergeRequest = function(li, merge_request_url, data) { - return $.ajax({ - type: "PUT", - url: merge_request_url, - data: data, - success: function(_data) { - return Milestone.successCallback(_data, li); - }, - error: function(data) { - return new Flash("Issue update failed", 'alert'); - }, - dataType: "json" - }); - }; - - Milestone.successCallback = function(data, element) { - var img_tag; - if (data.assignee) { - img_tag = $('<img/>'); - img_tag.attr('src', data.assignee.avatar_url); - img_tag.addClass('avatar s16'); - $(element).find('.assignee-icon img').replaceWith(img_tag); - } else { - $(element).find('.assignee-icon').empty(); - } - }; - function Milestone() { - this.issuesSortEndpoint = $('#tab-issues').data('sort-endpoint'); - this.mergeRequestsSortEndpoint = $('#tab-merge-requests').data('sort-endpoint'); - - this.bindIssuesSorting(); this.bindTabsSwitching(); // Load merge request tab if it is active @@ -90,22 +14,6 @@ this.loadInitialTab(); } - Milestone.prototype.bindIssuesSorting = function() { - if (!this.issuesSortEndpoint) return; - - $('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) { - this.createSortable(el, { - group: 'issue-list', - listEls: $('.issues-sortable-list'), - fieldName: 'issue', - sortCallback: (data) => { - Milestone.sortIssues(this.issuesSortEndpoint, data); - }, - updateCallback: Milestone.updateIssue, - }); - }.bind(this)); - }; - Milestone.prototype.bindTabsSwitching = function() { return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => { const $target = $(e.target); @@ -115,69 +23,6 @@ }); }; - Milestone.prototype.bindMergeRequestSorting = function() { - if (!this.mergeRequestsSortEndpoint) return; - - $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) { - this.createSortable(el, { - group: 'merge-request-list', - listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"), - fieldName: 'merge_request', - sortCallback: (data) => { - Milestone.sortMergeRequests(this.mergeRequestsSortEndpoint, data); - }, - updateCallback: Milestone.updateMergeRequest, - }); - }.bind(this)); - }; - - Milestone.prototype.createSortable = function(el, opts) { - return Sortable.create(el, { - group: opts.group, - filter: '.is-disabled', - forceFallback: true, - onStart: function(e) { - opts.listEls.css('min-height', e.item.offsetHeight); - }, - onEnd: function () { - opts.listEls.css("min-height", "0px"); - }, - onUpdate: function(e) { - var ids = this.toArray(), - data; - - if (ids.length) { - data = ids.map(function(id) { - return 'sortable_' + opts.fieldName + '[]=' + id; - }).join('&'); - - opts.sortCallback(data); - } - }, - onAdd: function (e) { - var data, issuableId, issuableUrl, newState; - newState = e.to.dataset.state; - issuableUrl = e.item.dataset.url; - data = (function() { - switch (newState) { - case 'ongoing': - return opts.fieldName + '[assignee_id]=' + gon.current_user_id; - case 'unassigned': - return opts.fieldName + '[assignee_id]='; - case 'closed': - return opts.fieldName + '[state_event]=close'; - } - })(); - if (e.from.dataset.state === 'closed') { - data += '&' + opts.fieldName + '[state_event]=reopen'; - } - - opts.updateCallback(e.item, issuableUrl, data); - this.options.onUpdate.call(this, e); - } - }); - }; - Milestone.prototype.loadInitialTab = function() { const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`); @@ -199,10 +44,6 @@ .done((data) => { $(tabElId).html(data.html); $target.addClass('is-loaded'); - - if (tabElId === '#tab-merge-requests') { - this.bindMergeRequestSorting(); - } }); } }; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index b0143b12cfe..f12a35f0485 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -32,7 +32,7 @@ const normalizeNewlines = function(str) { (function() { this.Notes = (function() { const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; - const REGEX_SLASH_COMMANDS = /^\/\w+.*$/gm; + const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; Notes.interval = null; @@ -56,6 +56,7 @@ const normalizeNewlines = function(str) { this.toggleCommitList = this.toggleCommitList.bind(this); this.postComment = this.postComment.bind(this); this.clearFlashWrapper = this.clearFlash.bind(this); + this.onHashChange = this.onHashChange.bind(this); this.notes_url = notes_url; this.note_ids = note_ids; @@ -127,7 +128,9 @@ const normalizeNewlines = function(str) { $(document).on('ajax:success', '.js-main-target-form', this.resetMainTargetForm); $(document).on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton); // when a key is clicked on the notes - return $(document).on('keydown', '.js-note-text', this.keydownNoteText); + $(document).on('keydown', '.js-note-text', this.keydownNoteText); + // When the URL fragment/hash has changed, `#note_xxx` + return $(window).on('hashchange', this.onHashChange); }; Notes.prototype.cleanBinding = function() { @@ -148,6 +151,7 @@ const normalizeNewlines = function(str) { $(document).off('ajax:success', '.js-main-target-form'); $(document).off('ajax:success', '.js-discussion-note-form'); $(document).off('ajax:complete', '.js-main-target-form'); + $(window).off('hashchange', this.onHashChange); }; Notes.initCommentTypeToggle = function (form) { @@ -280,7 +284,7 @@ const normalizeNewlines = function(str) { return this.initRefresh(); }; - Notes.prototype.handleSlashCommands = function(noteEntity) { + Notes.prototype.handleQuickActions = function(noteEntity) { var votesBlock; if (noteEntity.commands_changes) { if ('merge' in noteEntity.commands_changes) { @@ -298,8 +302,27 @@ const normalizeNewlines = function(str) { Notes.prototype.setupNewNote = function($note) { // Update datetime format on the recent note gl.utils.localTimeAgo($note.find('.js-timeago'), false); + this.collapseLongCommitList(); this.taskList.init(); + + // This stops the note highlight, #note_xxx`, from being removed after real time update + // The `:target` selector does not re-evaluate after we replace element in the DOM + Notes.updateNoteTargetSelector($note); + this.$noteToCleanHighlight = $note; + }; + + Notes.prototype.onHashChange = function() { + if (this.$noteToCleanHighlight) { + Notes.updateNoteTargetSelector(this.$noteToCleanHighlight); + } + + this.$noteToCleanHighlight = null; + }; + + Notes.updateNoteTargetSelector = function($note) { + const hash = gl.utils.getLocationHash(); + $note.toggleClass('target', hash && $note.filter(`#${hash}`).length > 0); }; /* @@ -597,13 +620,12 @@ const normalizeNewlines = function(str) { $noteEntityEl = $(noteEntity.html); $noteEntityEl.addClass('fade-in-full'); this.revertNoteEditForm($targetNote); - gl.utils.localTimeAgo($('.js-timeago', $noteEntityEl)); $noteEntityEl.renderGFM(); - $noteEntityEl.find('.js-task-list-container').taskList('enable'); // Find the note's `li` element by ID and replace it with the updated HTML $note_li = $('.note-row-' + noteEntity.id); $note_li.replaceWith($noteEntityEl); + this.setupNewNote($noteEntityEl); if (typeof gl.diffNotesCompileComponents !== 'undefined') { gl.diffNotesCompileComponents(); @@ -1060,7 +1082,7 @@ const normalizeNewlines = function(str) { var targetId = $originalContentEl.data('target-id'); var targetType = $originalContentEl.data('target-type'); - new gl.GLForm($editForm.find('form')); + new gl.GLForm($editForm.find('form'), this.enableGFM); $editForm.find('form') .attr('action', postUrl) @@ -1198,27 +1220,27 @@ const normalizeNewlines = function(str) { }; /** - * Identify if comment has any slash commands + * Identify if comment has any quick actions */ - Notes.prototype.hasSlashCommands = function(formContent) { - return REGEX_SLASH_COMMANDS.test(formContent); + Notes.prototype.hasQuickActions = function(formContent) { + return REGEX_QUICK_ACTIONS.test(formContent); }; /** - * Remove slash commands and leave comment with pure message + * Remove quick actions and leave comment with pure message */ - Notes.prototype.stripSlashCommands = function(formContent) { - return formContent.replace(REGEX_SLASH_COMMANDS, '').trim(); + Notes.prototype.stripQuickActions = function(formContent) { + return formContent.replace(REGEX_QUICK_ACTIONS, '').trim(); }; /** - * Gets appropriate description from slash commands found in provided `formContent` + * Gets appropriate description from quick actions found in provided `formContent` */ - Notes.prototype.getSlashCommandDescription = function (formContent, availableSlashCommands = []) { + Notes.prototype.getQuickActionDescription = function (formContent, availableQuickActions = []) { let tempFormContent; - // Identify executed slash commands from `formContent` - const executedCommands = availableSlashCommands.filter((command, index) => { + // Identify executed quick actions from `formContent` + const executedCommands = availableQuickActions.filter((command, index) => { const commandRegex = new RegExp(`/${command.name}`); return commandRegex.test(formContent); }); @@ -1276,7 +1298,7 @@ const normalizeNewlines = function(str) { }; /** - * Create Placeholder System Note DOM element populated with slash command description + * Create Placeholder System Note DOM element populated with quick action description */ Notes.prototype.createPlaceholderSystemNote = function ({ formContent, uniqueId }) { const $tempNote = $( @@ -1325,7 +1347,7 @@ const normalizeNewlines = function(str) { const { formData, formContent, formAction } = this.getFormData($form); let noteUniqueId; let systemNoteUniqueId; - let hasSlashCommands = false; + let hasQuickActions = false; let $notesContainer; let tempFormContent; @@ -1344,9 +1366,9 @@ const normalizeNewlines = function(str) { } tempFormContent = formContent; - if (this.hasSlashCommands(formContent)) { - tempFormContent = this.stripSlashCommands(formContent); - hasSlashCommands = true; + if (this.hasQuickActions(formContent)) { + tempFormContent = this.stripQuickActions(formContent); + hasQuickActions = true; } // Show placeholder note @@ -1363,10 +1385,10 @@ const normalizeNewlines = function(str) { } // Show placeholder system note - if (hasSlashCommands) { + if (hasQuickActions) { systemNoteUniqueId = _.uniqueId('tempSystemNote_'); $notesContainer.append(this.createPlaceholderSystemNote({ - formContent: this.getSlashCommandDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)), + formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)), uniqueId: systemNoteUniqueId, })); } @@ -1388,7 +1410,7 @@ const normalizeNewlines = function(str) { $notesContainer.find(`#${noteUniqueId}`).remove(); // Reset cached commands list when command is applied - if (hasSlashCommands) { + if (hasQuickActions) { $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho'); } @@ -1422,7 +1444,7 @@ const normalizeNewlines = function(str) { } if (note.commands_changes) { - this.handleSlashCommands(note); + this.handleQuickActions(note); } $form.trigger('ajax:success', [note]); @@ -1430,7 +1452,7 @@ const normalizeNewlines = function(str) { // Submission failed, remove placeholder note and show Flash error message $notesContainer.find(`#${noteUniqueId}`).remove(); - if (hasSlashCommands) { + if (hasQuickActions) { $notesContainer.find(`#${systemNoteUniqueId}`).remove(); } diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js index 4d623763ca7..901adbe9fce 100644 --- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js +++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js @@ -1,4 +1,7 @@ import Vue from 'vue'; +import Translate from '../../vue_shared/translate'; + +Vue.use(Translate); const inputNameAttribute = 'schedule[cron]'; @@ -72,11 +75,11 @@ export default { /> <label for="custom"> - Custom + {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label> <span class="cron-syntax-link-wrap"> - (<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>) + (<a :href="cronSyntaxUrl" target="_blank">{{ __('Cron syntax') }}</a>) </span> </div> @@ -92,7 +95,7 @@ export default { /> <label class="label-light" for="every-day"> - Every day (at 4:00am) + {{ __('Every day (at 4:00am)') }} </label> </div> @@ -108,7 +111,7 @@ export default { /> <label class="label-light" for="every-week"> - Every week (Sundays at 4:00am) + {{ __('Every week (Sundays at 4:00am)') }} </label> </div> @@ -124,7 +127,7 @@ export default { /> <label class="label-light" for="every-month"> - Every month (on the 1st at 4:00am) + {{ __('Every month (on the 1st at 4:00am)') }} </label> </div> @@ -133,7 +136,7 @@ export default { id="schedule_cron" class="form-control inline cron-interval-input" type="text" - placeholder="Define a custom pattern with cron syntax" + :placeholder="__('Define a custom pattern with cron syntax')" required="true" v-model="cronInterval" :name="inputNameAttribute" diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js index 5109b110b31..c827b7402dc 100644 --- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js +++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js @@ -1,6 +1,10 @@ +import Vue from 'vue'; import Cookies from 'js-cookie'; +import Translate from '../../vue_shared/translate'; import illustrationSvg from '../icons/intro_illustration.svg'; +Vue.use(Translate); + const cookieKey = 'pipeline_schedules_callout_dismissed'; export default { @@ -29,20 +33,18 @@ export default { </button> <div class="svg-container" v-html="illustrationSvg"></div> <div class="user-callout-copy"> - <h4>Scheduling Pipelines</h4> + <h4>{{ __('Scheduling Pipelines') }}</h4> <p> - 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. + {{ __('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.') }} </p> - <p> Learn more in the + <p> {{ __('Learn more in the') }} <a :href="docsUrl" target="_blank" - rel="nofollow">pipeline schedules documentation</a>. <!-- oneline to prevent extra space before period --> + rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period --> </p> </div> </div> </div> `, }; - diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 4781a8ff1da..8333ec0fbc3 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -23,7 +23,7 @@ export default { }; </script> <template> - <td> + <div class="table-section section-15 hidden-xs hidden-sm"> <a :href="pipeline.path" class="js-pipeline-url-link"> @@ -42,24 +42,26 @@ export default { class="js-pipeline-url-api api"> API </span> - <span - v-if="pipeline.flags.latest" - class="js-pipeline-url-lastest label label-success" - title="Latest pipeline for this branch" - ref="tooltip"> - latest - </span> - <span - v-if="pipeline.flags.yaml_errors" - class="js-pipeline-url-yaml label label-danger" - :title="pipeline.yaml_errors" - ref="tooltip"> - yaml invalid - </span> - <span - v-if="pipeline.flags.stuck" - class="js-pipeline-url-stuck label label-warning"> - stuck - </span> - </td> + <div class="label-container"> + <span + v-if="pipeline.flags.latest" + class="js-pipeline-url-latest label label-success" + title="Latest pipeline for this branch" + ref="tooltip"> + latest + </span> + <span + v-if="pipeline.flags.yaml_errors" + class="js-pipeline-url-yaml label label-danger" + :title="pipeline.yaml_errors" + ref="tooltip"> + yaml invalid + </span> + <span + v-if="pipeline.flags.stuck" + class="js-pipeline-url-stuck label label-warning"> + stuck + </span> + </div> + </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue new file mode 100644 index 00000000000..fed42d23112 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -0,0 +1,289 @@ +<script> + import Visibility from 'visibilityjs'; + import PipelinesService from '../services/pipelines_service'; + import eventHub from '../event_hub'; + import pipelinesTableComponent from '../../vue_shared/components/pipelines_table.vue'; + import tablePagination from '../../vue_shared/components/table_pagination.vue'; + import emptyState from './empty_state.vue'; + import errorState from './error_state.vue'; + import navigationTabs from './navigation_tabs.vue'; + import navigationControls from './nav_controls.vue'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import Poll from '../../lib/utils/poll'; + + export default { + props: { + store: { + type: Object, + required: true, + }, + }, + components: { + tablePagination, + pipelinesTableComponent, + emptyState, + errorState, + navigationTabs, + navigationControls, + loadingIcon, + }, + data() { + const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; + + return { + endpoint: pipelinesData.endpoint, + cssClass: pipelinesData.cssClass, + helpPagePath: pipelinesData.helpPagePath, + newPipelinePath: pipelinesData.newPipelinePath, + canCreatePipeline: pipelinesData.canCreatePipeline, + allPath: pipelinesData.allPath, + pendingPath: pipelinesData.pendingPath, + runningPath: pipelinesData.runningPath, + finishedPath: pipelinesData.finishedPath, + branchesPath: pipelinesData.branchesPath, + tagsPath: pipelinesData.tagsPath, + hasCi: pipelinesData.hasCi, + ciLintPath: pipelinesData.ciLintPath, + state: this.store.state, + apiScope: 'all', + pagenum: 1, + isLoading: false, + hasError: false, + isMakingRequest: false, + updateGraphDropdown: false, + hasMadeRequest: false, + }; + }, + computed: { + canCreatePipelineParsed() { + return gl.utils.convertPermissionToBoolean(this.canCreatePipeline); + }, + scope() { + const scope = gl.utils.getParameterByName('scope'); + return scope === null ? 'all' : scope; + }, + shouldRenderErrorState() { + return this.hasError && !this.isLoading; + }, + + /** + * The empty state should only be rendered when the request is made to fetch all pipelines + * and none is returned. + * + * @return {Boolean} + */ + shouldRenderEmptyState() { + return !this.isLoading && + !this.hasError && + this.hasMadeRequest && + !this.state.pipelines.length && + (this.scope === 'all' || this.scope === null); + }, + /** + * When a specific scope does not have pipelines we render a message. + * + * @return {Boolean} + */ + shouldRenderNoPipelinesMessage() { + return !this.isLoading && + !this.hasError && + !this.state.pipelines.length && + this.scope !== 'all' && + this.scope !== null; + }, + + shouldRenderTable() { + return !this.hasError && + !this.isLoading && this.state.pipelines.length; + }, + /** + * Pagination should only be rendered when there is more than one page. + * + * @return {Boolean} + */ + shouldRenderPagination() { + return !this.isLoading && + this.state.pipelines.length && + this.state.pageInfo.total > this.state.pageInfo.perPage; + }, + + hasCiEnabled() { + return this.hasCi !== undefined; + }, + paths() { + return { + allPath: this.allPath, + pendingPath: this.pendingPath, + finishedPath: this.finishedPath, + runningPath: this.runningPath, + branchesPath: this.branchesPath, + tagsPath: this.tagsPath, + }; + }, + pageParameter() { + return gl.utils.getParameterByName('page') || this.pagenum; + }, + scopeParameter() { + return gl.utils.getParameterByName('scope') || this.apiScope; + }, + }, + created() { + this.service = new PipelinesService(this.endpoint); + + const poll = new Poll({ + resource: this.service, + method: 'getPipelines', + data: { page: this.pageParameter, scope: this.scopeParameter }, + successCallback: this.successCallback, + errorCallback: this.errorCallback, + notificationCallback: this.setIsMakingRequest, + }); + + if (!Visibility.hidden()) { + this.isLoading = true; + poll.makeRequest(); + } else { + // If tab is not visible we need to make the first request so we don't show the empty + // state without knowing if there are any pipelines + this.fetchPipelines(); + } + + Visibility.change(() => { + if (!Visibility.hidden()) { + poll.restart(); + } else { + poll.stop(); + } + }); + + eventHub.$on('refreshPipelines', this.fetchPipelines); + }, + beforeDestroy() { + eventHub.$off('refreshPipelines'); + }, + methods: { + /** + * Will change the page number and update the URL. + * + * @param {Number} pageNumber desired page to go to. + */ + change(pageNumber) { + const param = gl.utils.setParamInURL('page', pageNumber); + + gl.utils.visitUrl(param); + return param; + }, + + fetchPipelines() { + if (!this.isMakingRequest) { + this.isLoading = true; + + this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter }) + .then(response => this.successCallback(response)) + .catch(() => this.errorCallback()); + } + }, + successCallback(resp) { + const response = { + headers: resp.headers, + body: resp.json(), + }; + + this.store.storeCount(response.body.count); + this.store.storePipelines(response.body.pipelines); + this.store.storePagination(response.headers); + + this.isLoading = false; + this.updateGraphDropdown = true; + this.hasMadeRequest = true; + }, + + errorCallback() { + this.hasError = true; + this.isLoading = false; + this.updateGraphDropdown = false; + }, + + setIsMakingRequest(isMakingRequest) { + this.isMakingRequest = isMakingRequest; + + if (isMakingRequest) { + this.updateGraphDropdown = false; + } + }, + }, + }; +</script> +<template> + <div :class="cssClass"> + + <div + class="top-area scrolling-tabs-container inner-page-scroll-tabs" + v-if="!isLoading && !shouldRenderEmptyState"> + <div class="fade-left"> + <i + class="fa fa-angle-left" + aria-hidden="true"> + </i> + </div> + <div class="fade-right"> + <i + class="fa fa-angle-right" + aria-hidden="true"> + </i> + </div> + <navigation-tabs + :scope="scope" + :count="state.count" + :paths="paths" + /> + + <navigation-controls + :new-pipeline-path="newPipelinePath" + :has-ci-enabled="hasCiEnabled" + :help-page-path="helpPagePath" + :ciLintPath="ciLintPath" + :can-create-pipeline="canCreatePipelineParsed " + /> + </div> + + <div class="content-list pipelines"> + + <loading-icon + label="Loading Pipelines" + size="3" + v-if="isLoading" + /> + + <empty-state + v-if="shouldRenderEmptyState" + :help-page-path="helpPagePath" + /> + + <error-state v-if="shouldRenderErrorState" /> + + <div + class="blank-state blank-state-no-icon" + v-if="shouldRenderNoPipelinesMessage"> + <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2> + </div> + + <div + class="table-holder" + v-if="shouldRenderTable"> + + <pipelines-table-component + :pipelines="state.pipelines" + :service="service" + :update-graph-dropdown="updateGraphDropdown" + /> + </div> + + <table-pagination + v-if="shouldRenderPagination" + :change="change" + :pageInfo="state.pageInfo" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.js b/app/assets/javascripts/pipelines/components/pipelines_actions.js deleted file mode 100644 index b9e066c5db1..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.js +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable no-new */ -/* global Flash */ -import '~/flash'; -import playIconSvg from 'icons/_icon_play.svg'; -import eventHub from '../event_hub'; -import loadingIconComponent from '../../vue_shared/components/loading_icon.vue'; - -export default { - props: { - actions: { - type: Array, - required: true, - }, - - service: { - type: Object, - required: true, - }, - }, - - components: { - loadingIconComponent, - }, - - data() { - return { - playIconSvg, - isLoading: false, - }; - }, - - methods: { - onClickAction(endpoint) { - this.isLoading = true; - - $(this.$refs.tooltip).tooltip('destroy'); - - this.service.postAction(endpoint) - .then(() => { - this.isLoading = false; - eventHub.$emit('refreshPipelines'); - }) - .catch(() => { - this.isLoading = false; - new Flash('An error occured while making the request.'); - }); - }, - - isActionDisabled(action) { - if (action.playable === undefined) { - return false; - } - - return !action.playable; - }, - }, - - template: ` - <div class="btn-group" v-if="actions"> - <button - type="button" - class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" - title="Manual job" - data-toggle="dropdown" - data-placement="top" - aria-label="Manual job" - ref="tooltip" - :disabled="isLoading"> - ${playIconSvg} - <i - class="fa fa-caret-down" - aria-hidden="true" /> - <loading-icon v-if="isLoading" /> - </button> - - <ul class="dropdown-menu dropdown-menu-align-right"> - <li v-for="action in actions"> - <button - type="button" - class="js-pipeline-action-link no-btn btn" - @click="onClickAction(action.path)" - :class="{ 'disabled': isActionDisabled(action) }" - :disabled="isActionDisabled(action)"> - ${playIconSvg} - <span>{{action.name}}</span> - </button> - </li> - </ul> - </div> - `, -}; diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue new file mode 100644 index 00000000000..97b4de26214 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -0,0 +1,88 @@ +<script> + /* global Flash */ + import '~/flash'; + import playIconSvg from 'icons/_icon_play.svg'; + import eventHub from '../event_hub'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + + export default { + props: { + actions: { + type: Array, + required: true, + }, + service: { + type: Object, + required: true, + }, + }, + components: { + loadingIcon, + }, + data() { + return { + playIconSvg, + isLoading: false, + }; + }, + methods: { + onClickAction(endpoint) { + this.isLoading = true; + + $(this.$refs.tooltip).tooltip('destroy'); + + this.service.postAction(endpoint) + .then(() => { + this.isLoading = false; + eventHub.$emit('refreshPipelines'); + }) + .catch(() => { + this.isLoading = false; + // eslint-disable-next-line no-new + new Flash('An error occured while making the request.'); + }); + }, + isActionDisabled(action) { + if (action.playable === undefined) { + return false; + } + + return !action.playable; + }, + }, + }; +</script> +<template> + <div class="btn-group"> + <button + type="button" + class="dropdown-new btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" + title="Manual job" + data-toggle="dropdown" + data-placement="top" + aria-label="Manual job" + ref="tooltip" + :disabled="isLoading"> + <span v-html="playIconSvg"></span> + <i + class="fa fa-caret-down" + aria-hidden="true"> + </i> + <loading-icon v-if="isLoading" /> + </button> + + <ul class="dropdown-menu dropdown-menu-align-right"> + <li v-for="action in actions"> + <button + type="button" + class="js-pipeline-action-link no-btn btn" + @click="onClickAction(action.path)" + :class="{ disabled: isActionDisabled(action) }" + :disabled="isActionDisabled(action)"> + <span v-html="playIconSvg"></span> + <span>{{action.name}}</span> + </button> + </li> + </ul> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.js b/app/assets/javascripts/pipelines/components/pipelines_artifacts.js deleted file mode 100644 index f18e2dfadaf..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.js +++ /dev/null @@ -1,33 +0,0 @@ -export default { - props: { - artifacts: { - type: Array, - required: true, - }, - }, - - template: ` - <div class="btn-group" role="group"> - <button - class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download" - title="Artifacts" - data-placement="top" - data-toggle="dropdown" - aria-label="Artifacts"> - <i class="fa fa-download" aria-hidden="true"></i> - <i class="fa fa-caret-down" aria-hidden="true"></i> - </button> - <ul class="dropdown-menu dropdown-menu-align-right"> - <li v-for="artifact in artifacts"> - <a - rel="nofollow" - download - :href="artifact.path"> - <i class="fa fa-download" aria-hidden="true"></i> - <span>Download {{artifact.name}} artifacts</span> - </a> - </li> - </ul> - </div> - `, -}; diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue new file mode 100644 index 00000000000..b4520481cdc --- /dev/null +++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue @@ -0,0 +1,51 @@ +<script> + import tooltipMixin from '../../vue_shared/mixins/tooltip'; + + export default { + props: { + artifacts: { + type: Array, + required: true, + }, + }, + mixins: [ + tooltipMixin, + ], + }; +</script> +<template> + <div + class="btn-group" + role="group"> + <button + class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" + title="Artifacts" + data-placement="top" + data-toggle="dropdown" + aria-label="Artifacts" + ref="tooltip"> + <i + class="fa fa-download" + aria-hidden="true"> + </i> + <i + class="fa fa-caret-down" + aria-hidden="true"> + </i> + </button> + <ul class="dropdown-menu dropdown-menu-align-right"> + <li v-for="artifact in artifacts"> + <a + rel="nofollow" + download + :href="artifact.path"> + <i + class="fa fa-download" + aria-hidden="true"> + </i> + <span>Download {{artifact.name}} artifacts</span> + </a> + </li> + </ul> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/time_ago.js b/app/assets/javascripts/pipelines/components/time_ago.js deleted file mode 100644 index 188f74cc705..00000000000 --- a/app/assets/javascripts/pipelines/components/time_ago.js +++ /dev/null @@ -1,98 +0,0 @@ -import iconTimerSvg from 'icons/_icon_timer.svg'; -import '../../lib/utils/datetime_utility'; - -export default { - props: { - finishedTime: { - type: String, - required: true, - }, - - duration: { - type: Number, - required: true, - }, - }, - - data() { - return { - iconTimerSvg, - }; - }, - - updated() { - $(this.$refs.tooltip).tooltip('fixTitle'); - }, - - computed: { - hasDuration() { - return this.duration > 0; - }, - - hasFinishedTime() { - return this.finishedTime !== ''; - }, - - localTimeFinished() { - return gl.utils.formatDate(this.finishedTime); - }, - - durationFormated() { - const date = new Date(this.duration * 1000); - - let hh = date.getUTCHours(); - let mm = date.getUTCMinutes(); - let ss = date.getSeconds(); - - // left pad - if (hh < 10) { - hh = `0${hh}`; - } - if (mm < 10) { - mm = `0${mm}`; - } - if (ss < 10) { - ss = `0${ss}`; - } - - return `${hh}:${mm}:${ss}`; - }, - - finishedTimeFormated() { - const timeAgo = gl.utils.getTimeago(); - - return timeAgo.format(this.finishedTime); - }, - }, - - template: ` - <td class="pipelines-time-ago"> - <p - class="duration" - v-if="hasDuration"> - <span - v-html="iconTimerSvg"> - </span> - {{durationFormated}} - </p> - - <p - class="finished-at" - v-if="hasFinishedTime"> - - <i - class="fa fa-calendar" - aria-hidden="true" /> - - <time - ref="tooltip" - data-toggle="tooltip" - data-placement="top" - data-container="body" - :title="localTimeFinished"> - {{finishedTimeFormated}} - </time> - </p> - </td> - `, -}; diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue new file mode 100644 index 00000000000..be3f32afa09 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/time_ago.vue @@ -0,0 +1,93 @@ +<script> + import iconTimerSvg from 'icons/_icon_timer.svg'; + import '../../lib/utils/datetime_utility'; + import tooltipMixin from '../../vue_shared/mixins/tooltip'; + import timeagoMixin from '../../vue_shared/mixins/timeago'; + + export default { + props: { + finishedTime: { + type: String, + required: true, + }, + duration: { + type: Number, + required: true, + }, + }, + mixins: [ + tooltipMixin, + timeagoMixin, + ], + data() { + return { + iconTimerSvg, + }; + }, + computed: { + hasDuration() { + return this.duration > 0; + }, + hasFinishedTime() { + return this.finishedTime !== ''; + }, + durationFormated() { + const date = new Date(this.duration * 1000); + + let hh = date.getUTCHours(); + let mm = date.getUTCMinutes(); + let ss = date.getSeconds(); + + // left pad + if (hh < 10) { + hh = `0${hh}`; + } + if (mm < 10) { + mm = `0${mm}`; + } + if (ss < 10) { + ss = `0${ss}`; + } + + return `${hh}:${mm}:${ss}`; + }, + }, + }; +</script> +<template> + <div class="table-section section-15 pipelines-time-ago"> + <div + class="table-mobile-header" + role="rowheader"> + Duration + </div> + <div class="table-mobile-content"> + <p + class="duration" + v-if="hasDuration"> + <span + v-html="iconTimerSvg"> + </span> + {{durationFormated}} + </p> + + <p + class="finished-at hidden-xs hidden-sm" + v-if="hasFinishedTime"> + + <i + class="fa fa-calendar" + aria-hidden="true"> + </i> + + <time + ref="tooltip" + data-placement="top" + data-container="body" + :title="tooltipTitle(finishedTime)"> + {{timeFormated(finishedTime)}} + </time> + </p> + </div> + </div> +</script> diff --git a/app/assets/javascripts/pipelines/index.js b/app/assets/javascripts/pipelines/index.js deleted file mode 100644 index 48f9181a8d9..00000000000 --- a/app/assets/javascripts/pipelines/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import Vue from 'vue'; -import PipelinesStore from './stores/pipelines_store'; -import PipelinesComponent from './pipelines'; -import '../vue_shared/vue_resource_interceptor'; - -$(() => new Vue({ - el: document.querySelector('#pipelines-list-vue'), - - data() { - const store = new PipelinesStore(); - - return { - store, - }; - }, - components: { - 'vue-pipelines': PipelinesComponent, - }, - template: ` - <vue-pipelines :store="store" /> - `, -})); diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js deleted file mode 100644 index b530461837c..00000000000 --- a/app/assets/javascripts/pipelines/pipelines.js +++ /dev/null @@ -1,293 +0,0 @@ -import Visibility from 'visibilityjs'; -import PipelinesService from './services/pipelines_service'; -import eventHub from './event_hub'; -import pipelinesTableComponent from '../vue_shared/components/pipelines_table'; -import tablePagination from '../vue_shared/components/table_pagination.vue'; -import emptyState from './components/empty_state.vue'; -import errorState from './components/error_state.vue'; -import navigationTabs from './components/navigation_tabs.vue'; -import navigationControls from './components/nav_controls.vue'; -import loadingIcon from '../vue_shared/components/loading_icon.vue'; -import Poll from '../lib/utils/poll'; - -export default { - props: { - store: { - type: Object, - required: true, - }, - }, - - components: { - tablePagination, - pipelinesTableComponent, - emptyState, - errorState, - navigationTabs, - navigationControls, - loadingIcon, - }, - - data() { - const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; - - return { - endpoint: pipelinesData.endpoint, - cssClass: pipelinesData.cssClass, - helpPagePath: pipelinesData.helpPagePath, - newPipelinePath: pipelinesData.newPipelinePath, - canCreatePipeline: pipelinesData.canCreatePipeline, - allPath: pipelinesData.allPath, - pendingPath: pipelinesData.pendingPath, - runningPath: pipelinesData.runningPath, - finishedPath: pipelinesData.finishedPath, - branchesPath: pipelinesData.branchesPath, - tagsPath: pipelinesData.tagsPath, - hasCi: pipelinesData.hasCi, - ciLintPath: pipelinesData.ciLintPath, - state: this.store.state, - apiScope: 'all', - pagenum: 1, - isLoading: false, - hasError: false, - isMakingRequest: false, - updateGraphDropdown: false, - hasMadeRequest: false, - }; - }, - - computed: { - canCreatePipelineParsed() { - return gl.utils.convertPermissionToBoolean(this.canCreatePipeline); - }, - - scope() { - const scope = gl.utils.getParameterByName('scope'); - return scope === null ? 'all' : scope; - }, - - shouldRenderErrorState() { - return this.hasError && !this.isLoading; - }, - - /** - * The empty state should only be rendered when the request is made to fetch all pipelines - * and none is returned. - * - * @return {Boolean} - */ - shouldRenderEmptyState() { - return !this.isLoading && - !this.hasError && - this.hasMadeRequest && - !this.state.pipelines.length && - (this.scope === 'all' || this.scope === null); - }, - - /** - * When a specific scope does not have pipelines we render a message. - * - * @return {Boolean} - */ - shouldRenderNoPipelinesMessage() { - return !this.isLoading && - !this.hasError && - !this.state.pipelines.length && - this.scope !== 'all' && - this.scope !== null; - }, - - shouldRenderTable() { - return !this.hasError && - !this.isLoading && this.state.pipelines.length; - }, - - /** - * Pagination should only be rendered when there is more than one page. - * - * @return {Boolean} - */ - shouldRenderPagination() { - return !this.isLoading && - this.state.pipelines.length && - this.state.pageInfo.total > this.state.pageInfo.perPage; - }, - - hasCiEnabled() { - return this.hasCi !== undefined; - }, - - paths() { - return { - allPath: this.allPath, - pendingPath: this.pendingPath, - finishedPath: this.finishedPath, - runningPath: this.runningPath, - branchesPath: this.branchesPath, - tagsPath: this.tagsPath, - }; - }, - - pageParameter() { - return gl.utils.getParameterByName('page') || this.pagenum; - }, - - scopeParameter() { - return gl.utils.getParameterByName('scope') || this.apiScope; - }, - }, - - created() { - this.service = new PipelinesService(this.endpoint); - - const poll = new Poll({ - resource: this.service, - method: 'getPipelines', - data: { page: this.pageParameter, scope: this.scopeParameter }, - successCallback: this.successCallback, - errorCallback: this.errorCallback, - notificationCallback: this.setIsMakingRequest, - }); - - if (!Visibility.hidden()) { - this.isLoading = true; - poll.makeRequest(); - } else { - // If tab is not visible we need to make the first request so we don't show the empty - // state without knowing if there are any pipelines - this.fetchPipelines(); - } - - Visibility.change(() => { - if (!Visibility.hidden()) { - poll.restart(); - } else { - poll.stop(); - } - }); - - eventHub.$on('refreshPipelines', this.fetchPipelines); - }, - - beforeDestroy() { - eventHub.$off('refreshPipelines'); - }, - - methods: { - /** - * Will change the page number and update the URL. - * - * @param {Number} pageNumber desired page to go to. - */ - change(pageNumber) { - const param = gl.utils.setParamInURL('page', pageNumber); - - gl.utils.visitUrl(param); - return param; - }, - - fetchPipelines() { - if (!this.isMakingRequest) { - this.isLoading = true; - - this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter }) - .then(response => this.successCallback(response)) - .catch(() => this.errorCallback()); - } - }, - - successCallback(resp) { - const response = { - headers: resp.headers, - body: resp.json(), - }; - - this.store.storeCount(response.body.count); - this.store.storePipelines(response.body.pipelines); - this.store.storePagination(response.headers); - - this.isLoading = false; - this.updateGraphDropdown = true; - this.hasMadeRequest = true; - }, - - errorCallback() { - this.hasError = true; - this.isLoading = false; - this.updateGraphDropdown = false; - }, - - setIsMakingRequest(isMakingRequest) { - this.isMakingRequest = isMakingRequest; - - if (isMakingRequest) { - this.updateGraphDropdown = false; - } - }, - }, - - template: ` - <div :class="cssClass"> - - <div - class="top-area scrolling-tabs-container inner-page-scroll-tabs" - v-if="!isLoading && !shouldRenderEmptyState"> - <div class="fade-left"> - <i class="fa fa-angle-left" aria-hidden="true"></i> - </div> - <div class="fade-right"> - <i class="fa fa-angle-right" aria-hidden="true"></i> - </div> - <navigation-tabs - :scope="scope" - :count="state.count" - :paths="paths" /> - - <navigation-controls - :new-pipeline-path="newPipelinePath" - :has-ci-enabled="hasCiEnabled" - :help-page-path="helpPagePath" - :ciLintPath="ciLintPath" - :can-create-pipeline="canCreatePipelineParsed " /> - </div> - - <div class="content-list pipelines"> - - <loading-icon - label="Loading Pipelines" - size="3" - v-if="isLoading" - /> - - <empty-state - v-if="shouldRenderEmptyState" - :help-page-path="helpPagePath" /> - - <error-state v-if="shouldRenderErrorState" /> - - <div - class="blank-state blank-state-no-icon" - v-if="shouldRenderNoPipelinesMessage"> - <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2> - </div> - - <div - class="table-holder" - v-if="shouldRenderTable"> - - <pipelines-table-component - :pipelines="state.pipelines" - :service="service" - :update-graph-dropdown="updateGraphDropdown" - /> - </div> - - <table-pagination - v-if="shouldRenderPagination" - :change="change" - :pageInfo="state.pageInfo" - /> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/pipelines/pipelines_bundle.js b/app/assets/javascripts/pipelines/pipelines_bundle.js new file mode 100644 index 00000000000..923d9bfb248 --- /dev/null +++ b/app/assets/javascripts/pipelines/pipelines_bundle.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import PipelinesStore from './stores/pipelines_store'; +import pipelinesComponent from './components/pipelines.vue'; + +document.addEventListener('DOMContentLoaded', () => new Vue({ + el: '#pipelines-list-vue', + data() { + const store = new PipelinesStore(); + + return { + store, + }; + }, + components: { + pipelinesComponent, + }, + render(createElement) { + return createElement('pipelines-component', { + props: { + store: this.store, + }, + }); + }, +})); diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js index 4a3df2fd465..141333b2b4d 100644 --- a/app/assets/javascripts/preview_markdown.js +++ b/app/assets/javascripts/preview_markdown.js @@ -3,7 +3,7 @@ // MarkdownPreview // // Handles toggling the "Write" and "Preview" tab clicks, rendering the preview -// (including the explanation of slash commands), and showing a warning when +// (including the explanation of quick actions), and showing a warning when // more than `x` users are referenced. // (function () { diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js index b2a77462fe0..142ad437509 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js @@ -15,10 +15,10 @@ export default { <div class="time-tracking-help-state"> <div class="time-tracking-info"> <h4> - Track time with slash commands + Track time with quick actions </h4> <p> - Slash commands can be used in the issues description and comment boxes. + Quick actions can be used in the issues description and comment boxes. </p> <p> <code> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js index 244b67b3ad9..650e935b116 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js @@ -16,10 +16,10 @@ export default { 'issuable-time-tracker': timeTracker, }, methods: { - listenForSlashCommands() { - $(document).on('ajax:success', '.gfm-form', this.slashCommandListened); + listenForQuickActions() { + $(document).on('ajax:success', '.gfm-form', this.quickActionListened); }, - slashCommandListened(e, data) { + quickActionListened(e, data) { const subscribedCommands = ['spend_time', 'time_estimate']; let changedCommands; if (data !== undefined) { @@ -35,7 +35,7 @@ export default { }, }, mounted() { - this.listenForSlashCommands(); + this.listenForQuickActions(); }, template: ` <div class="block"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js index 8155218681c..76cb71b6c12 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js @@ -1,5 +1,5 @@ -import statusCodes from '~/lib/utils/http_status'; -import { bytesToMiB } from '~/lib/utils/number_utils'; +import statusCodes from '../../lib/utils/http_status'; +import { bytesToMiB } from '../../lib/utils/number_utils'; import MemoryGraph from '../../vue_shared/components/memory_graph'; import MRWidgetService from '../services/mr_widget_service'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js index 205804670fa..686cb38cbb1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js @@ -1,42 +1,63 @@ export default { name: 'MRWidgetRelatedLinks', props: { + isMerged: { type: Boolean, required: true }, relatedLinks: { type: Object, required: true }, }, computed: { + // TODO: the following should be handled by i18n + closingText() { + if (this.isMerged) { + return `Closed ${this.issueLabel('closing')}`; + } + + return `Closes ${this.issueLabel('closing')}`; + }, hasLinks() { const { closing, mentioned, assignToMe } = this.relatedLinks; return closing || mentioned || assignToMe; }, + // TODO: the following should be handled by i18n + mentionedText() { + if (this.isMerged) { + if (this.hasMultipleIssues(this.relatedLinks.mentioned)) { + return 'are mentioned but were not closed'; + } + + return 'is mentioned but was not closed'; + } + + if (this.hasMultipleIssues(this.relatedLinks.mentioned)) { + return 'are mentioned but will not be closed'; + } + + return 'is mentioned but will not be closed'; + }, }, methods: { hasMultipleIssues(text) { - return !text ? false : text.match(/<\/a> and <a/); + return /<\/a>,? and <a/.test(text); }, + // TODO: the following should be handled by i18n issueLabel(field) { return this.hasMultipleIssues(this.relatedLinks[field]) ? 'issues' : 'issue'; }, - verbLabel(field) { - return this.hasMultipleIssues(this.relatedLinks[field]) ? 'are' : 'is'; - }, }, template: ` - <section - v-if="hasLinks" - class="mr-info-list mr-links"> + <div v-if="hasLinks"> <div class="legend"></div> <p v-if="relatedLinks.closing"> - Closes {{issueLabel('closing')}} + {{closingText}} <span v-html="relatedLinks.closing"></span>. </p> <p v-if="relatedLinks.mentioned"> <span class="capitalize">{{issueLabel('mentioned')}}</span> <span v-html="relatedLinks.mentioned"></span> - {{verbLabel('mentioned')}} mentioned but will not be closed. + {{mentionedText}} </p> <p v-if="relatedLinks.assignToMe"> <span v-html="relatedLinks.assignToMe"></span> </p> - </section> + </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js index c7d32d18141..9b8eed9016d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js @@ -1,7 +1,9 @@ /* global Flash */ import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; +import mrWidgetRelatedLinks from '../../components/mr_widget_related_links'; import eventHub from '../../event_hub'; +import '../../../flash'; export default { name: 'MRWidgetMerged', @@ -11,6 +13,7 @@ export default { }, components: { 'mr-widget-author-and-time': mrWidgetAuthorTime, + 'mr-widget-related-links': mrWidgetRelatedLinks, }, data() { return { @@ -18,6 +21,9 @@ export default { }; }, computed: { + shouldRenderRelatedLinks() { + return this.mr.relatedLinks && this.mr.isMerged; + }, shouldShowRemoveSourceBranch() { const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr; @@ -86,6 +92,10 @@ export default { aria-hidden="true" /> The source branch is being removed. </p> + <mr-widget-related-links + v-if="shouldRenderRelatedLinks" + :is-merged="mr.isMerged()" + :related-links="mr.relatedLinks" /> </section> <div v-if="shouldShowMergedButtons" diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 2339a00ddd0..222d0b7f79e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -48,7 +48,7 @@ export default { return stateMaps.stateToComponentMap[this.mr.state]; }, shouldRenderMergeHelp() { - return stateMaps.statesToShowHelpWidget.indexOf(this.mr.state) > -1; + return !this.mr.isMerged; }, shouldRenderPipelines() { return Object.keys(this.mr.pipeline).length || this.mr.hasCI; @@ -238,9 +238,14 @@ export default { :is="componentName" :mr="mr" :service="service" /> - <mr-widget-related-links + <section v-if="shouldRenderRelatedLinks" - :related-links="mr.relatedLinks" /> + class="mr-info-list mr-links"> + <div class="legend"></div> + <mr-widget-related-links + :is-merged="mr.isMerged" + :related-links="mr.relatedLinks" /> + </section> <mr-widget-merge-help v-if="shouldRenderMergeHelp" /> </div> `, diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 69bc1436284..ad73efb37e1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -1,6 +1,18 @@ import Timeago from 'timeago.js'; import { getStateKey } from '../dependencies'; +const unmergedStates = [ + 'locked', + 'conflicts', + 'workInProgress', + 'readyToMerge', + 'checking', + 'unresolvedDiscussions', + 'pipelineFailed', + 'pipelineBlocked', + 'autoMergeFailed', +]; + export default class MergeRequestStore { constructor(data) { @@ -65,6 +77,7 @@ export default class MergeRequestStore { this.mergeActionsContentPath = data.commit_change_content_path; this.isRemovingSourceBranch = this.isRemovingSourceBranch || false; this.isOpen = data.state === 'opened' || data.state === 'reopened' || false; + this.isMerged = unmergedStates.indexOf(data.state) === -1; this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false; this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; this.canMerge = !!data.merge_path; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 605dd3a1ff4..dd939d98d0f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -19,19 +19,6 @@ const stateToComponentMap = { shaMismatch: 'mr-widget-sha-mismatch', }; -const statesToShowHelpWidget = [ - 'locked', - 'conflicts', - 'workInProgress', - 'readyToMerge', - 'checking', - 'unresolvedDiscussions', - 'pipelineFailed', - 'pipelineBlocked', - 'autoMergeFailed', -]; - export default { stateToComponentMap, - statesToShowHelpWidget, }; diff --git a/app/assets/javascripts/vue_shared/components/commit.js b/app/assets/javascripts/vue_shared/components/commit.js deleted file mode 100644 index ff5ae28e062..00000000000 --- a/app/assets/javascripts/vue_shared/components/commit.js +++ /dev/null @@ -1,159 +0,0 @@ -import commitIconSvg from 'icons/_icon_commit.svg'; -import userAvatarLink from './user_avatar/user_avatar_link.vue'; - -export default { - props: { - /** - * Indicates the existance of a tag. - * Used to render the correct icon, if true will render `fa-tag` icon, - * if false will render `fa-code-fork` icon. - */ - tag: { - type: Boolean, - required: false, - default: false, - }, - - /** - * If provided is used to render the branch name and url. - * Should contain the following properties: - * name - * ref_url - */ - commitRef: { - type: Object, - required: false, - default: () => ({}), - }, - - /** - * Used to link to the commit sha. - */ - commitUrl: { - type: String, - required: false, - default: '', - }, - - /** - * Used to show the commit short sha that links to the commit url. - */ - shortSha: { - type: String, - required: false, - default: '', - }, - - /** - * If provided shows the commit tile. - */ - title: { - type: String, - required: false, - default: '', - }, - - /** - * If provided renders information about the author of the commit. - * When provided should include: - * `avatar_url` to render the avatar icon - * `web_url` to link to user profile - * `username` to render alt and title tags - */ - author: { - type: Object, - required: false, - default: () => ({}), - }, - }, - - computed: { - /** - * Used to verify if all the properties needed to render the commit - * ref section were provided. - * - * TODO: Improve this! Use lodash _.has when we have it. - * - * @returns {Boolean} - */ - hasCommitRef() { - return this.commitRef && this.commitRef.name && this.commitRef.ref_url; - }, - - /** - * Used to verify if all the properties needed to render the commit - * author section were provided. - * - * TODO: Improve this! Use lodash _.has when we have it. - * - * @returns {Boolean} - */ - hasAuthor() { - return this.author && - this.author.avatar_url && - this.author.path && - this.author.username; - }, - - /** - * If information about the author is provided will return a string - * to be rendered as the alt attribute of the img tag. - * - * @returns {String} - */ - userImageAltDescription() { - return this.author && - this.author.username ? `${this.author.username}'s avatar` : null; - }, - }, - - data() { - return { commitIconSvg }; - }, - - components: { - userAvatarLink, - }, - template: ` - <div class="branch-commit"> - - <div v-if="hasCommitRef" class="icon-container"> - <i v-if="tag" class="fa fa-tag"></i> - <i v-if="!tag" class="fa fa-code-fork"></i> - </div> - - <a v-if="hasCommitRef" - class="ref-name" - :href="commitRef.ref_url"> - {{commitRef.name}} - </a> - - <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div> - - <a class="commit-sha" - :href="commitUrl"> - {{shortSha}} - </a> - - <div class="commit-title flex-truncate-parent"> - <span v-if="title" class="flex-truncate-child"> - <user-avatar-link - v-if="hasAuthor" - class="avatar-image-container" - :link-href="author.path" - :img-src="author.avatar_url" - :img-alt="userImageAltDescription" - :tooltip-text="author.username" - /> - <a class="commit-row-message" - :href="commitUrl"> - {{title}} - </a> - </span> - <span v-else> - Cant find HEAD commit for this branch - </span> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue new file mode 100644 index 00000000000..262584769e0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/commit.vue @@ -0,0 +1,166 @@ +<script> + import commitIconSvg from 'icons/_icon_commit.svg'; + import userAvatarLink from './user_avatar/user_avatar_link.vue'; + + export default { + props: { + /** + * Indicates the existance of a tag. + * Used to render the correct icon, if true will render `fa-tag` icon, + * if false will render `fa-code-fork` icon. + */ + tag: { + type: Boolean, + required: false, + default: false, + }, + /** + * If provided is used to render the branch name and url. + * Should contain the following properties: + * name + * ref_url + */ + commitRef: { + type: Object, + required: false, + default: () => ({}), + }, + /** + * Used to link to the commit sha. + */ + commitUrl: { + type: String, + required: false, + default: '', + }, + + /** + * Used to show the commit short sha that links to the commit url. + */ + shortSha: { + type: String, + required: false, + default: '', + }, + /** + * If provided shows the commit tile. + */ + title: { + type: String, + required: false, + default: '', + }, + /** + * If provided renders information about the author of the commit. + * When provided should include: + * `avatar_url` to render the avatar icon + * `web_url` to link to user profile + * `username` to render alt and title tags + */ + author: { + type: Object, + required: false, + default: () => ({}), + }, + }, + computed: { + /** + * Used to verify if all the properties needed to render the commit + * ref section were provided. + * + * TODO: Improve this! Use lodash _.has when we have it. + * + * @returns {Boolean} + */ + hasCommitRef() { + return this.commitRef && this.commitRef.name && this.commitRef.ref_url; + }, + /** + * Used to verify if all the properties needed to render the commit + * author section were provided. + * + * TODO: Improve this! Use lodash _.has when we have it. + * + * @returns {Boolean} + */ + hasAuthor() { + return this.author && + this.author.avatar_url && + this.author.path && + this.author.username; + }, + /** + * If information about the author is provided will return a string + * to be rendered as the alt attribute of the img tag. + * + * @returns {String} + */ + userImageAltDescription() { + return this.author && + this.author.username ? `${this.author.username}'s avatar` : null; + }, + }, + data() { + return { commitIconSvg }; + }, + components: { + userAvatarLink, + }, + }; +</script> +<template> + <div class="branch-commit"> + <div v-if="hasCommitRef" class="icon-container hidden-xs"> + <i + v-if="tag" + class="fa fa-tag" + aria-hidden="true"> + </i> + <i + v-if="!tag" + class="fa fa-code-fork" + aria-hidden="true"> + </i> + </div> + + <a + v-if="hasCommitRef" + class="ref-name hidden-xs" + :href="commitRef.ref_url"> + {{commitRef.name}} + </a> + + <div + v-html="commitIconSvg" + class="commit-icon js-commit-icon"> + </div> + + <a + class="commit-sha" + :href="commitUrl"> + {{shortSha}} + </a> + + <div class="commit-title flex-truncate-parent"> + <span + v-if="title" + class="flex-truncate-child"> + <user-avatar-link + v-if="hasAuthor" + class="avatar-image-container" + :link-href="author.path" + :img-src="author.avatar_url" + :img-alt="userImageAltDescription" + :tooltip-text="author.username" + /> + <a class="commit-row-message" + :href="commitUrl"> + {{title}} + </a> + </span> + <span v-else> + Cant find HEAD commit for this branch + </span> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js b/app/assets/javascripts/vue_shared/components/pipelines_table.js deleted file mode 100644 index 48a39f18112..00000000000 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js +++ /dev/null @@ -1,55 +0,0 @@ -import PipelinesTableRowComponent from './pipelines_table_row'; - -/** - * Pipelines Table Component. - * - * Given an array of objects, renders a table. - */ -export default { - props: { - pipelines: { - type: Array, - required: true, - }, - - service: { - type: Object, - required: true, - }, - - updateGraphDropdown: { - type: Boolean, - required: false, - default: false, - }, - }, - - components: { - 'pipelines-table-row-component': PipelinesTableRowComponent, - }, - - template: ` - <table class="table ci-table"> - <thead> - <tr> - <th class="js-pipeline-status pipeline-status">Status</th> - <th class="js-pipeline-info pipeline-info">Pipeline</th> - <th class="js-pipeline-commit pipeline-commit">Commit</th> - <th class="js-pipeline-stages pipeline-stages">Stages</th> - <th class="js-pipeline-date pipeline-date"></th> - <th class="js-pipeline-actions pipeline-actions"></th> - </tr> - </thead> - <tbody> - <template v-for="model in pipelines" - v-bind:model="model"> - <tr is="pipelines-table-row-component" - :pipeline="model" - :service="service" - :update-graph-dropdown="updateGraphDropdown" - /> - </template> - </tbody> - </table> - `, -}; diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.vue b/app/assets/javascripts/vue_shared/components/pipelines_table.vue new file mode 100644 index 00000000000..884f1ce9689 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.vue @@ -0,0 +1,64 @@ +<script> + import pipelinesTableRowComponent from './pipelines_table_row.vue'; + + /** + * Pipelines Table Component. + * + * Given an array of objects, renders a table. + */ + export default { + props: { + pipelines: { + type: Array, + required: true, + }, + service: { + type: Object, + required: true, + }, + updateGraphDropdown: { + type: Boolean, + required: false, + default: false, + }, + }, + components: { + pipelinesTableRowComponent, + }, + }; +</script> +<template> + <div class="ci-table"> + <div + class="gl-responsive-table-row table-row-header" + role="row"> + <div + class="table-section section-10 js-pipeline-status pipeline-status" + role="rowheader"> + Status + </div> + <div + class="table-section section-15 js-pipeline-info pipeline-info" + role="rowheader"> + Pipeline + </div> + <div + class="table-section section-25 js-pipeline-commit pipeline-commit" + role="rowheader"> + Commit + </div> + <div + class="table-section section-15 js-pipeline-stages pipeline-stages" + role="rowheader"> + Stages + </div> + </div> + <pipelines-table-row-component + v-for="model in pipelines" + :key="model.id" + :pipeline="model" + :service="service" + :update-graph-dropdown="updateGraphDropdown" + /> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.vue index f60f8eeb43d..4d5ebe2e9ed 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.vue @@ -1,12 +1,13 @@ +<script> /* eslint-disable no-param-reassign */ -import AsyncButtonComponent from '../../pipelines/components/async_button.vue'; -import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions'; -import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts'; +import asyncButtonComponent from '../../pipelines/components/async_button.vue'; +import pipelinesActionsComponent from '../../pipelines/components/pipelines_actions.vue'; +import pipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts.vue'; import ciBadge from './ci_badge_link.vue'; -import PipelinesStageComponent from '../../pipelines/components/stage.vue'; -import PipelinesUrlComponent from '../../pipelines/components/pipeline_url.vue'; -import PipelinesTimeagoComponent from '../../pipelines/components/time_ago'; -import CommitComponent from './commit'; +import pipelineStage from '../../pipelines/components/stage.vue'; +import pipelineUrl from '../../pipelines/components/pipeline_url.vue'; +import pipelinesTimeago from '../../pipelines/components/time_ago.vue'; +import commitComponent from './commit.vue'; /** * Pipeline table row. @@ -19,30 +20,26 @@ export default { type: Object, required: true, }, - service: { type: Object, required: true, }, - updateGraphDropdown: { type: Boolean, required: false, default: false, }, }, - components: { - 'async-button-component': AsyncButtonComponent, - 'pipelines-actions-component': PipelinesActionsComponent, - 'pipelines-artifacts-component': PipelinesArtifactsComponent, - 'commit-component': CommitComponent, - 'dropdown-stage': PipelinesStageComponent, - 'pipeline-url': PipelinesUrlComponent, + asyncButtonComponent, + pipelinesActionsComponent, + pipelinesArtifactsComponent, + commitComponent, + pipelineStage, + pipelineUrl, ciBadge, - 'time-ago': PipelinesTimeagoComponent, + pipelinesTimeago, }, - computed: { /** * If provided, returns the commit tag. @@ -203,17 +200,37 @@ export default { } return {}; }, - }, - template: ` - <tr class="commit"> - <td class="commit-link"> + displayPipelineActions() { + return this.pipeline.flags.retryable || + this.pipeline.flags.cancelable || + this.pipeline.details.manual_actions.length || + this.pipeline.details.artifacts.length; + }, + }, +}; +</script> +<template> + <div class="commit gl-responsive-table-row"> + <div class="table-section section-10 commit-link"> + <div class="table-mobile-header" + role="rowheader"> + Status + </div> + <div class="table-mobile-content"> <ci-badge :status="pipelineStatus"/> - </td> + </div> + </div> - <pipeline-url :pipeline="pipeline"></pipeline-url> + <pipeline-url :pipeline="pipeline" /> - <td> + <div class="table-section section-25"> + <div + class="table-mobile-header" + role="rowheader"> + Commit + </div> + <div class="table-mobile-content"> <commit-component :tag="commitTag" :commit-ref="commitRef" @@ -221,52 +238,67 @@ export default { :short-sha="commitShortSha" :title="commitTitle" :author="commitAuthor"/> - </td> + </div> + </div> - <td class="stage-cell"> + <div class="table-section section-wrap section-15 stage-cell"> + <div + class="table-mobile-header" + role="rowheader"> + Stages + </div> + <div class="table-mobile-content"> <div class="stage-container dropdown js-mini-pipeline-graph" v-if="pipeline.details.stages.length > 0" v-for="stage in pipeline.details.stages"> - - <dropdown-stage + <pipeline-stage :stage="stage" - :update-dropdown="updateGraphDropdown"/> + :update-dropdown="updateGraphDropdown" + /> </div> - </td> + </div> + </div> - <time-ago - :duration="pipelineDuration" - :finished-time="pipelineFinishedAt" /> + <pipelines-timeago + :duration="pipelineDuration" + :finished-time="pipelineFinishedAt" + /> - <td class="pipeline-actions"> - <div class="pull-right btn-group"> - <pipelines-actions-component - v-if="pipeline.details.manual_actions.length" - :actions="pipeline.details.manual_actions" - :service="service" /> + <div + v-if="displayPipelineActions" + class="table-section section-20 table-button-footer pipeline-actions"> + <div class="btn-group table-action-buttons"> + <pipelines-actions-component + v-if="pipeline.details.manual_actions.length" + :actions="pipeline.details.manual_actions" + :service="service" + /> - <pipelines-artifacts-component - v-if="pipeline.details.artifacts.length" - :artifacts="pipeline.details.artifacts" /> + <pipelines-artifacts-component + v-if="pipeline.details.artifacts.length" + class="hidden-xs hidden-sm" + :artifacts="pipeline.details.artifacts" + /> - <async-button-component - v-if="pipeline.flags.retryable" - :service="service" - :endpoint="pipeline.retry_path" - css-class="js-pipelines-retry-button btn-default btn-retry" - title="Retry" - icon="repeat" /> + <async-button-component + v-if="pipeline.flags.retryable" + :service="service" + :endpoint="pipeline.retry_path" + css-class="js-pipelines-retry-button btn-default btn-retry" + title="Retry" + icon="repeat" + /> - <async-button-component - v-if="pipeline.flags.cancelable" - :service="service" - :endpoint="pipeline.cancel_path" - css-class="js-pipelines-cancel-button btn-remove" - title="Cancel" - icon="remove" - confirm-action-message="Are you sure you want to cancel this pipeline?" /> - </div> - </td> - </tr> - `, -}; + <async-button-component + v-if="pipeline.flags.cancelable" + :service="service" + :endpoint="pipeline.cancel_path" + css-class="js-pipelines-cancel-button btn-remove" + title="Cancel" + icon="remove" + confirm-action-message="Are you sure you want to cancel this pipeline?" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue index af2b4c6786e..1c6ef071a6d 100644 --- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue +++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue @@ -20,12 +20,6 @@ export default { default: 'top', }, - shortFormat: { - type: Boolean, - required: false, - default: false, - }, - cssClass: { type: String, required: false, @@ -37,18 +31,12 @@ export default { tooltipMixin, timeagoMixin, ], - - computed: { - timeagoCssClass() { - return this.shortFormat ? 'js-short-timeago' : 'js-timeago'; - }, - }, }; </script> <template> <time - :class="[timeagoCssClass, cssClass]" - class="js-timeago js-timeago-render" + :class="cssClass" + class="js-vue-timeago" :title="tooltipTitle(time)" :data-placement="tooltipPlacement" data-container="body" diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index d08df05fd6c..da03e4f5b5e 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -59,6 +59,44 @@ } } + .file-blame-legend { + background-color: $gray-light; + text-align: right; + padding: 8px $gl-padding; + border-bottom: 1px solid $border-color; + + @media (max-width: $screen-xs-max) { + text-align: left; + } + + .left-label { + padding-right: 5px; + } + + .right-label { + padding-left: 5px; + } + + .legend-box { + display: inline-block; + width: 10px; + height: 10px; + padding: 0 2px; + } + + @for $i from 0 through 5 { + .legend-box-#{$i} { + background-color: mix($blame-cyan, $blame-blue, $i / 5.0 * 100%); + } + } + + @for $i from 1 through 4 { + .legend-box-#{$i + 5} { + background-color: mix($blame-gray, $blame-cyan, $i / 4.0 * 100%); + } + } + } + .file-content { background: $white-light; @@ -118,6 +156,19 @@ padding: 5px 10px; min-width: 400px; background: $gray-light; + border-left: 3px solid; + } + + @for $i from 0 through 5 { + td.blame-commit-age-#{$i} { + border-left-color: mix($blame-cyan, $blame-blue, $i / 5.0 * 100%); + } + } + + @for $i from 1 through 4 { + td.blame-commit-age-#{$i + 5} { + border-left-color: mix($blame-gray, $blame-cyan, $i / 4.0 * 100%); + } } td.line-numbers { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index cfbaaaa04c7..880ab52fa1b 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -152,7 +152,7 @@ } .value-container { - background-color: $filter-value-selected-color; + box-shadow: inset 0 0 0 100px $filtered-search-term-shadow-color; } } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 432024779fd..61e3897f369 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -125,10 +125,11 @@ label { .select-wrapper { position: relative; - .fa-caret-down { + .fa-chevron-down { position: absolute; + font-size: 10px; right: 10px; - top: 10px; + top: 12px; color: $gray-darkest; pointer-events: none; } @@ -138,6 +139,12 @@ label { padding-left: 10px; padding-right: 10px; -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + &::-ms-expand { + display: none; + } } .form-control-inline { @@ -148,7 +155,8 @@ label { margin-top: 35px; } -.form-group .control-label { +.form-group .control-label, +.form-group .control-label-full-width { font-weight: normal; } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 9e8acf4e73c..49bff23452d 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -51,6 +51,10 @@ body { &.limit-container-width { max-width: $limited-layout-width; } + + &.limit-container-width-sm { + max-width: 790px; + } } .alert-wrapper { diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 80691a234f8..b21bcc22a87 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -174,3 +174,14 @@ white-space: nowrap; } } + +@media(max-width: $screen-xs-max) { + .atwho-view-ul { + width: 350px; + } + + .atwho-view ul li { + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss index 5f4211147f3..f1ecd050a0a 100644 --- a/app/assets/stylesheets/framework/page-header.scss +++ b/app/assets/stylesheets/framework/page-header.scss @@ -59,4 +59,8 @@ margin: 0 2px 0 3px; } } + + .ci-status { + margin-right: 10px; + } } diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index 9d8d08dff88..e8d69e62194 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -1,50 +1,60 @@ .panel { margin-bottom: $gl-padding; +} + +.panel-slim { + @extend .panel; + margin-bottom: $gl-vert-padding; +} + + +.panel-heading { + padding: $gl-vert-padding $gl-padding; + line-height: 36px; + + .controls { + margin-top: -2px; + float: right; + } + + .dropdown-menu-toggle { + line-height: 20px; + } - .panel-heading { - padding: $gl-vert-padding $gl-padding; - line-height: 36px; - - .controls { - margin-top: -2px; - float: right; - } - - .dropdown-menu-toggle { - line-height: 20px; - } - - .badge { - margin-top: -2px; - margin-left: 5px; - } - - &.split { - display: flex; - align-items: center; - } - - .left { - flex: 1 1 auto; - } - - .right { - flex: 0 0 auto; - text-align: right; - } + .badge { + margin-top: -2px; + margin-left: 5px; } - .panel-body { - padding: $gl-padding; + &.split { + display: flex; + align-items: center; + } - .form-actions { - margin: -$gl-padding; - margin-top: $gl-padding; - } + .left { + flex: 1 1 auto; } - .panel-title { - font-size: inherit; - line-height: inherit; + .right { + flex: 0 0 auto; + text-align: right; } } + +.panel-empty-heading { + border-bottom: 0; +} + +.panel-body { + padding: $gl-padding; + + .form-actions { + margin: -$gl-padding; + margin-top: $gl-padding; + } +} + +.panel-title { + font-size: inherit; + line-height: inherit; +} diff --git a/app/assets/stylesheets/framework/responsive-tables.scss b/app/assets/stylesheets/framework/responsive-tables.scss index f0a4c66aa1a..d2c90908baa 100644 --- a/app/assets/stylesheets/framework/responsive-tables.scss +++ b/app/assets/stylesheets/framework/responsive-tables.scss @@ -36,13 +36,58 @@ align-self: stretch; padding: 10px; align-items: center; - height: 62px; + min-height: 62px; &:not(:first-of-type) { border-top: 1px solid $white-normal; } } } + + &.section-wrap { + white-space: normal; + + @media (max-width: $screen-sm-max) { + flex-wrap: wrap; + } + } + } +} + + +.table-button-footer { + @media (min-width: $screen-md-min) { + text-align: right; + } + + @media (max-width: $screen-sm-max) { + background-color: $gray-normal; + align-self: stretch; + border-top: 1px solid $border-color; + + .table-action-buttons { + padding: 10px 5px; + display: flex; + + .btn { + border-radius: 3px; + } + + > .btn-group, + > .external-url, + > .btn { + flex: 1 1 28px; + margin: 0 5px; + } + + .dropdown-new { + width: 100%; + } + + .dropdown-menu { + min-width: initial; + } + } } } @@ -56,6 +101,7 @@ .table-mobile-header { color: $gl-text-color-secondary; + text-align: left; @include flex-max-width(40); @media (min-width: $screen-md-min) { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 5ae833cd5f6..40e654f4838 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -18,19 +18,28 @@ background-image: none; background-color: transparent; border: none; - padding-top: 6px; - padding-right: 10px; + padding-top: 12px; + padding-right: 20px; + font-size: 10px; b { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 5px dashed; - border-right: 5px solid transparent; - border-left: 5px solid transparent; + display: none; + } + + &::after { + content: "\f078"; + position: absolute; + z-index: 1; + text-align: center; + pointer-events: none; + box-sizing: border-box; color: $gray-darkest; + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } } @@ -109,10 +118,12 @@ line-height: 15px; background-color: $gray-light; background-image: none; + padding: 3px 18px 3px 5px; .select2-search-choice-close { - top: 4px; - left: 3px; + top: 5px; + left: initial; + right: 3px; } &.select2-search-choice-focus { diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index c9f345d24be..b666223b120 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -74,9 +74,9 @@ $pagination-hover-color: $gl-text-color; $pagination-hover-bg: $row-hover; $pagination-hover-border: $border-color; -$pagination-active-color: $blue-600; -$pagination-active-bg: $white-light; -$pagination-active-border: $border-color; +$pagination-active-color: $white-light; +$pagination-active-bg: $gl-link-color; +$pagination-active-border: $gl-link-color; $pagination-disabled-color: #cdcdcd; $pagination-disabled-bg: $gray-light; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 4114a050d9a..71513199d84 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -282,6 +282,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%); /* * Filtered Search */ +$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09); $dropdown-hover-color: $blue-400; /* @@ -365,6 +366,13 @@ $avatar-border: rgba(0, 0, 0, .1); $gl-avatar-size: 40px; /* +* Blame +*/ +$blame-gray: #ededed; +$blame-cyan: #acd5f2; +$blame-blue: #254e77; + +/* * Builds */ $builds-trace-bg: #111; diff --git a/app/assets/stylesheets/mailers/devise.scss b/app/assets/stylesheets/mailers/devise.scss deleted file mode 100644 index 9f613710cf4..00000000000 --- a/app/assets/stylesheets/mailers/devise.scss +++ /dev/null @@ -1,140 +0,0 @@ -@import "framework/variables"; - -// NOTE: This stylesheet is for the exclusive use of the `devise_mailer` layout -// used for Devise email templates, and _should not_ be included in any -// application stylesheets. -// -// Styles defined here are embedded directly into the resulting email HTML via -// the `premailer` gem. - -$body-background-color: #363636; -$message-background-color: #fafafa; - -$header-color: #6b4fbb; -$body-color: #444; -$cta-color: #e14329; -$footer-link-color: #7e7e7e; - -$font-family: Helvetica, Arial, sans-serif; - -body { - background-color: $body-background-color; - font-family: $font-family; - margin: 0; - padding: 0; -} - -table { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - - border: 0; - border-collapse: separate; - - &#wrapper { - background-color: $body-background-color; - width: 100%; - } - - &#header { - margin: 0 auto; - text-align: left; - width: 600px; - - & > td { - text-align: center; - } - } - - &#body { - background-color: $message-background-color; - border: 1px solid $black; - border-radius: 4px; - margin: 0 auto; - width: 600px; - } - - &#footer { - color: $footer-link-color; - font-size: 14px; - text-align: center; - width: 100%; - } - - td { - &#body-container { - padding: 20px 40px; - } - } -} - -.center { - text-align: center; -} - -#logo { - border: none; - outline: none; - min-height: 88px; - width: 134px; -} - -#content { - h2 { - color: $header-color; - font-size: 30px; - font-weight: 400; - line-height: 34px; - margin-top: 0; - } - - p { - color: $body-color; - font-size: 17px; - line-height: 24px; - margin-bottom: 0; - } -} - -#cta { - border: 1px solid $cta-color; - border-radius: 3px; - display: inline-block; - margin: 20px 0; - padding: 12px 24px; - - a { - background-color: $message-background-color; - color: $cta-color; - display: inline-block; - text-decoration: none; - } -} - -#tanuki { - padding: 40px 0 0; - - img { - border: none; - outline: none; - width: 37px; - min-height: 36px; - } -} - -#tagline { - font-size: 22px; - font-weight: 100; - padding: 4px 0 40px; -} - -#social { - padding: 0 10px 20px; - width: 600px; - word-spacing: 20px; - - a { - color: $footer-link-color; - text-decoration: none; - } -} diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 740e383dbb5..85109fec91a 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -1,3 +1,5 @@ +@import "./issues/issue_count_badge"; + [v-cloak] { display: none; } @@ -133,7 +135,7 @@ } .board-list-component, - .board-issue-count-holder { + .issue-count-badge { display: none; } } @@ -429,30 +431,6 @@ margin: 5px; } -.board-issue-count-holder { - margin-top: -3px; - - .btn { - line-height: 12px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } -} - -.board-issue-count { - padding-right: 10px; - padding-left: 10px; - line-height: 21px; - border-radius: $border-radius-base; - border: 1px solid $border-color; - - &.has-btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-width: 1px 0 1px 1px; - } -} - .page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar { &.right-sidebar { top: 0; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 203fd6d07e4..7eee0a71c66 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -72,7 +72,7 @@ display: flex; justify-content: flex-end; background: $gray-light; - border: 1px solid $gray-normal; + border: 1px solid $border-color; color: $gl-text-color; .truncated-info { @@ -150,6 +150,7 @@ overflow-y: scroll; overflow-x: hidden; padding: 10px 20px 20px 5px; + white-space: pre; } .environment-information { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index b24803678ea..89bd437b362 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -124,43 +124,6 @@ } .gl-responsive-table-row { - .environments-actions { - @media (min-width: $screen-md-min) { - text-align: right; - } - - @media (max-width: $screen-sm-max) { - background-color: $gray-normal; - align-self: stretch; - border-top: 1px solid $border-color; - - .environment-action-buttons { - padding: 10px 5px; - display: flex; - - .btn { - border-radius: 3px; - } - - > .btn-group, - > .external-url, - > .btn { - flex: 1; - flex-basis: 28px; - margin: 0 5px; - } - - .dropdown-new { - width: 100%; - } - - .dropdown-menu { - min-width: initial; - } - } - } - } - .branch-commit { max-width: 100%; } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 5b723f7c722..4c3fa1fb8d4 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -89,7 +89,6 @@ background: $gray-light; border-radius: 0; color: $events-pre-color; - margin: 0 20px; overflow: hidden; } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index f923a1104a9..8cdb3f34ae5 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -1,3 +1,5 @@ +@import "./issues/issue_count_badge"; + .issues-list { .issue { padding: 10px 0 10px $gl-padding; diff --git a/app/assets/stylesheets/pages/issues/issue_count_badge.scss b/app/assets/stylesheets/pages/issues/issue_count_badge.scss new file mode 100644 index 00000000000..ccb62bfed18 --- /dev/null +++ b/app/assets/stylesheets/pages/issues/issue_count_badge.scss @@ -0,0 +1,29 @@ +.issue-count-badge { + display: inline-flex; + align-items: stretch; + height: 24px; +} + +.issue-count-badge-count { + display: flex; + align-items: center; + padding-right: 10px; + padding-left: 10px; + border: 1px solid $border-color; + border-radius: $border-radius-base; + line-height: 1; + + &.has-btn { + border-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +} + +.issue-count-badge-add-button { + display: flex; + align-items: center; + border: 1px solid $border-color; + border-radius: 0 $border-radius-base $border-radius-base 0; + line-height: 1; +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 2dc7f73a295..c0bd045f1fc 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -372,6 +372,10 @@ margin-left: 12px; } + &.mr-state-locked + .mr-info-list.mr-links { + margin-top: -16px; + } + &.empty-state { .artwork { margin-bottom: $gl-padding; diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 335e587b8f4..55e0ee1936e 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -111,8 +111,8 @@ } } -.issues-sortable-list, -.merge_requests-sortable-list { +.milestone-issues-list, +.milestone-merge_requests-list { .issuable-detail { display: block; margin-top: 7px; @@ -197,6 +197,4 @@ .issuable-row { background-color: $white-light; - cursor: -webkit-grab; - cursor: grab; } diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss index ab417948931..595eb40fec7 100644 --- a/app/assets/stylesheets/pages/pipeline_schedules.scss +++ b/app/assets/stylesheets/pages/pipeline_schedules.scss @@ -12,7 +12,7 @@ .interval-pattern-form-group { label { margin-right: 10px; - font-size: 12px; + font-weight: normal; &[for='custom'] { margin-right: 0; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index cd9382e8de5..a85ba3a5955 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -10,17 +10,13 @@ .table-holder { width: 100%; - - @media (max-width: $screen-sm-max) { - overflow: auto; - } } .commit-title { margin: 0; } - .table.ci-table { + .ci-table { .label { margin-bottom: 3px; @@ -30,11 +26,6 @@ color: $black; } - .stage-cell { - min-width: 130px; // Guarantees we show at least 4 stages in line - width: 20%; - } - .pipelines-time-ago { text-align: right; } @@ -108,39 +99,7 @@ } } -.table.ci-table { - - &.builds-page tbody tr { - height: 71px; - } - - tr { - th { - padding: 16px 8px; - border: none; - } - - td { - padding: 10px 8px; - } - - td.environments-actions { - padding-right: 0; - } - - td.stage-cell { - padding: 10px 0; - } - - .commit-link { - padding: 9px 8px 10px 2px; - } - } - - tbody { - border-top-width: 1px; - } - +.ci-table { .build.retried { background-color: $gray-lightest; } @@ -194,13 +153,6 @@ color: $gl-link-color; } - .commit-title { - max-width: 225px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .label { margin-right: 4px; } @@ -253,11 +205,7 @@ } .stage-cell { - font-size: 0; - padding: 0 4px; - - > .stage-container > div > button > span > svg, - > .stage-container > button > svg { + .mini-pipeline-graph-dropdown-toggle svg { height: 22px; width: 22px; position: absolute; @@ -631,6 +579,23 @@ font-weight: normal; } +@mixin mini-pipeline-graph-color($color-light, $color-main, $color-dark) { + border-color: $color-main; + color: $color-main; + + &:hover, + &:focus, + &:active { + background-color: $color-light; + border-color: $color-dark; + color: $color-dark; + + svg { + fill: $color-dark; + } + } +} + // Dropdown button in mini pipeline graph .mini-pipeline-graph-dropdown-toggle { border-radius: 100px; @@ -670,100 +635,32 @@ // Dropdown button animation in mini pipeline graph &.ci-status-icon-success { - border-color: $green-500; - color: $green-500; - - &:hover, - &:focus, - &:active { - background-color: $green-50; - border-color: $green-600; - color: $green-600; - - svg { - fill: $green-600; - } - } + @include mini-pipeline-graph-color($green-50, $green-500, $green-600); } &.ci-status-icon-failed { - border-color: $red-500; - color: $red-500; - - &:hover, - &:focus, - &:active { - background-color: $red-50; - border-color: $red-600; - color: $red-600; - - svg { - fill: $red-600; - } - } + @include mini-pipeline-graph-color($red-50, $red-500, $red-600); } &.ci-status-icon-pending, &.ci-status-icon-success_with_warnings { - border-color: $orange-500; - color: $orange-500; - - &:hover, - &:focus, - &:active { - background-color: $orange-50; - border-color: $orange-600; - color: $orange-600; - - svg { - fill: $orange-600; - } - } + @include mini-pipeline-graph-color($orange-50, $orange-500, $orange-600); } &.ci-status-icon-running { - border-color: $blue-400; - color: $blue-400; - - &:hover, - &:focus, - &:active { - background-color: $blue-50; - border-color: $blue-600; - color: $blue-600; - - svg { - fill: $blue-600; - } - } + @include mini-pipeline-graph-color($blue-50, $blue-400, $blue-600); } &.ci-status-icon-canceled, &.ci-status-icon-disabled, &.ci-status-icon-not-found, &.ci-status-icon-manual { - border-color: $gl-text-color; - color: $gl-text-color; - - &:hover, - &:focus, - &:active { - background-color: rgba($gl-text-color, 0.1); - border-color: $gl-text-color; - } + @include mini-pipeline-graph-color(rgba($gl-text-color, 0.1), $gl-text-color, $gl-text-color); } &.ci-status-icon-created, &.ci-status-icon-skipped { - border-color: $gray-darkest; - color: $gray-darkest; - - &:hover, - &:focus, - &:active { - background-color: rgba($gray-darkest, 0.1); - border-color: $gray-darkest; - } + @include mini-pipeline-graph-color(rgba($gray-darkest, 0.1), $gray-darkest, $gray-darkest); } } @@ -842,6 +739,10 @@ top: 1px; vertical-align: text-bottom; position: relative; + + @media (max-width: $screen-xs-max) { + max-width: 60%; + } } // status icon on the left @@ -929,8 +830,14 @@ border-color: transparent; border-style: solid; top: -6px; - left: 2px; + left: 50%; + transform: translate(-50%, 0); border-width: 0 5px 6px; + + @media (max-width: $screen-sm-max) { + left: 100%; + margin-left: -12px; + } } &::before { @@ -945,6 +852,20 @@ } /** + * Center dropdown menu in mini graph + */ +.mini-pipeline-graph-dropdown-menu.dropdown-menu { + transform: translate(-80%, 0); + min-width: 150px; + + @media(min-width: $screen-md-min) { + transform: translate(-50%, 0); + right: auto; + left: 50%; + min-width: 240px; + } +} +/** * Terminal */ .terminal-icon { diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 4ed8617b6a3..67ad1ae60af 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,142 +1,82 @@ -.container-fluid { - .ci-status { - padding: 2px 7px 4px; - margin-right: 10px; - border: 1px solid $gray-darker; - white-space: nowrap; - border-radius: 4px; - - &:hover, - &:focus { - text-decoration: none; - } - - svg { - height: 13px; - width: 13px; - position: relative; - top: 2px; - overflow: visible; - } +@mixin status-color($color-light, $color-main, $color-dark) { + color: $color-main; + border-color: $color-main; - &.ci-failed { - color: $red-500; - border-color: $red-500; + &:not(span):hover { + background-color: $color-light; + color: $color-dark; + border-color: $color-dark; - &:not(span):hover { - background-color: $red-50; - color: $red-600; - border-color: $red-600; - - svg { - fill: $red-600; - } - } - - svg { - fill: $red-500; - } + svg { + fill: $color-dark; } + } - &.ci-success { - color: $green-600; - border-color: $green-500; + svg { + fill: $color-main; + } +} - &:not(span):hover { - background-color: $green-50; - color: $green-700; - border-color: $green-600; +.ci-status { + padding: 2px 7px 4px; + border: 1px solid $gray-darker; + white-space: nowrap; + border-radius: 4px; - svg { - fill: $green-600; - } - } + &:hover, + &:focus { + text-decoration: none; + } - svg { - fill: $green-500; - } - } + svg { + height: 13px; + width: 13px; + position: relative; + top: 2px; + overflow: visible; + } - &.ci-canceled, - &.ci-disabled { - color: $gl-text-color; - border-color: $gl-text-color; + &.ci-failed { + @include status-color($red-50, $red-500, $red-600); + } - &:not(span):hover { - background-color: rgba($gl-text-color, .07); - } + &.ci-success { + @include status-color($green-50, $green-500, $green-700); + } - svg { - fill: $gl-text-color; - } - } + &.ci-canceled, + &.ci-disabled, + &.ci-manual { + color: $gl-text-color; + border-color: $gl-text-color; - &.ci-pending, - &.ci-success_with_warnings, - &.ci-failed_with_warnings { - color: $orange-600; - border-color: $orange-500; - - &:not(span):hover { - background-color: $orange-50; - color: $orange-700; - border-color: $orange-600; - - svg { - fill: $orange-600; - } - } - - svg { - fill: $orange-500; - } + &:not(span):hover { + background-color: rgba($gl-text-color, .07); } + } - &.ci-info, - &.ci-running { - color: $blue-500; - border-color: $blue-500; - - &:not(span):hover { - background-color: $blue-50; - color: $blue-600; - border-color: $blue-600; - - svg { - fill: $blue-600; - } - } - - svg { - fill: $blue-500; - } - } + &.ci-pending, + &.ci-failed_with_warnings, + &.ci-success_with_warnings { + @include status-color($orange-50, $orange-500, $orange-700); + } - &.ci-created, - &.ci-skipped { - color: $gl-text-color-secondary; - border-color: $gl-text-color-secondary; + &.ci-info, + &.ci-running { + @include status-color($blue-50, $blue-500, $blue-600); + } - &:not(span):hover { - background-color: rgba($gl-text-color-secondary, .07); - } + &.ci-created, + &.ci-skipped { + color: $gl-text-color-secondary; + border-color: $gl-text-color-secondary; - svg { - fill: $gl-text-color-secondary; - } + &:not(span):hover { + background-color: rgba($gl-text-color-secondary, .07); } - &.ci-manual { - color: $gl-text-color; - border-color: $gl-text-color; - - &:not(span):hover { - background-color: rgba($gl-text-color, .07); - } - - svg { - fill: $gl-text-color; - } + svg { + fill: $gl-text-color-secondary; } } } diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index b64b89485f7..94d0a39f397 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -42,9 +42,7 @@ } .git-access-header { - padding: 16px 40px 11px 0; - line-height: 28px; - font-size: 18px; + padding: $gl-padding 0 $gl-padding-top; } .git-clone-holder { @@ -66,6 +64,7 @@ .git-clone-holder { width: 480px; + padding-bottom: $gl-padding; } .nav-controls { @@ -89,9 +88,9 @@ margin: $gl-padding 0; h3 { - font-size: 22px; + font-size: 19px; font-weight: normal; - margin-top: 1.4em; + margin: $gl-padding 0; } } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 75fb19e815f..4d4b8a8425f 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -100,6 +100,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :enabled_git_access_protocol, :gravatar_enabled, :help_page_text, + :help_page_hide_commercial_content, + :help_page_support_url, :home_page_url, :housekeeping_bitmaps_enabled, :housekeeping_enabled, diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb index b2536a1c949..1ff785ac2ca 100644 --- a/app/controllers/concerns/milestone_actions.rb +++ b/app/controllers/concerns/milestone_actions.rb @@ -6,7 +6,7 @@ module MilestoneActions format.html { redirect_to milestone_redirect_path } format.json do render json: tabs_json("shared/milestones/_merge_requests_tab", { - merge_requests: @milestone.merge_requests, + merge_requests: @milestone.sorted_merge_requests, show_project_name: true }) end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index cb4bd0ad5f5..603a51266da 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -80,10 +80,6 @@ class Projects::ApplicationController < ApplicationController cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present? end - def builds_enabled - return render_404 unless @project.feature_available?(:builds, current_user) - end - def require_pages_enabled! not_found unless Gitlab.config.pages.enabled end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 928f17e6a8e..7d0e2b3e2ef 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :authentication_result + attr_reader :authentication_result, :redirected_path delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true @@ -14,7 +14,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController skip_before_action :verify_authenticity_token skip_before_action :repository before_action :authenticate_user - before_action :ensure_project_found! private @@ -68,38 +67,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController headers['Www-Authenticate'] = challenges.join("\n") if challenges.any? end - def ensure_project_found! - render_not_found if project.blank? - end - def project - return @project if defined?(@project) - - project_id, _ = project_id_with_suffix - @project = - if project_id.blank? - nil - else - Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}") - end - end + parse_repo_path unless defined?(@project) - # This method returns two values so that we can parse - # params[:project_id] (untrusted input!) in exactly one place. - def project_id_with_suffix - id = params[:project_id] || '' - - %w[.wiki.git .git].each do |suffix| - if id.end_with?(suffix) - # Be careful to only remove the suffix from the end of 'id'. - # Accidentally removing it from the middle is how security - # vulnerabilities happen! - return [id.slice(0, id.length - suffix.length), suffix] - end - end + @project + end - # Something is wrong with params[:project_id]; do not pass it on. - [nil, nil] + def parse_repo_path + @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}") end def render_missing_personal_token @@ -114,14 +89,9 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def wiki? - return @wiki if defined?(@wiki) - - _, suffix = project_id_with_suffix - @wiki = suffix == '.wiki.git' - end + parse_repo_path unless defined?(@wiki) - def render_not_found - render plain: 'Not Found', status: :not_found + @wiki end def handle_basic_authentication(login, password) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index b6b62da7b60..71ae60cb8cd 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -56,7 +56,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access - @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities) + @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path) end def access_actor diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 43fc0c39801..df5221fe95f 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -5,7 +5,6 @@ class Projects::GraphsController < Projects::ApplicationController before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_download_code! - before_action :builds_enabled, only: :ci def show respond_to do |format| diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ebb163bf9dc..56f76e752d0 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -10,11 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :module_enabled - before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, - :related_branches, :can_create_branch, :realtime_changes, :create_merge_request] - - # Allow read any issue - before_action :authorize_read_issue!, only: [:show, :realtime_changes] + before_action :issue, except: [:index, :new, :create, :bulk_update] # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] @@ -229,18 +225,19 @@ class Projects::IssuesController < Projects::ApplicationController protected def issue + return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! + + return render_404 unless can?(current_user, :read_issue, @issue) + + @issue end alias_method :subscribable_resource, :issue alias_method :issuable, :issue alias_method :awardable, :issue alias_method :spammable, :issue - def authorize_read_issue! - return render_404 unless can?(current_user, :read_issue, @issue) - end - def authorize_update_issue! return render_404 unless can?(current_user, :update_issue, @issue) end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index ae16f69955a..62410d7f57f 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -2,7 +2,7 @@ class Projects::MilestonesController < Projects::ApplicationController include MilestoneActions before_action :module_enabled - before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests, :merge_requests, :participants, :labels] + before_action :milestone, only: [:edit, :update, :destroy, :show, :merge_requests, :participants, :labels] # Allow read any milestone before_action :authorize_read_milestone! @@ -85,22 +85,6 @@ class Projects::MilestonesController < Projects::ApplicationController end end - def sort_issues - @milestone.sort_issues(params['sortable_issue'].map(&:to_i)) - - render json: { saved: true } - end - - def sort_merge_requests - @merge_requests = @milestone.merge_requests.where(id: params['sortable_merge_request']) - @merge_requests.each do |merge_request| - merge_request.position = params['sortable_merge_request'].index(merge_request.id.to_s) + 1 - merge_request.save - end - - render json: { saved: true } - end - protected def milestone diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 6223e7943f8..8effb792689 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -4,7 +4,6 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] - before_action :builds_enabled, only: :charts wrap_parameters Ci::Pipeline diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index f68610e197c..e6fb112e7f2 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -5,8 +5,10 @@ class GroupsFinder < UnionFinder end def execute - groups = find_union(all_groups, Group).with_route.order_id_desc - by_parent(groups) + items = all_groups.map do |item| + by_parent(item) + end + find_union(items, Group).with_route.order_id_desc end private @@ -16,12 +18,22 @@ class GroupsFinder < UnionFinder def all_groups groups = [] - groups << current_user.authorized_groups if current_user + if current_user + groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups + end groups << Group.unscoped.public_to_user(current_user) groups end + def groups_for_ancestors + current_user.authorized_groups + end + + def groups_for_descendants + current_user.groups + end + def by_parent(groups) return groups unless params[:parent] diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 957ad875858..558f8b5e2e5 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -41,6 +41,7 @@ class IssuableFinder items = by_iids(items) items = by_milestone(items) items = by_label(items) + items = by_created_at(items) # Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far items = by_project(items) @@ -402,6 +403,18 @@ class IssuableFinder params[:non_archived].present? ? items.non_archived : items end + def by_created_at(items) + if params[:created_after].present? + items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after])) + end + + if params[:created_before].present? + items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before])) + end + + items + end + def current_user_related? params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 71154da7ec5..06bbed777d5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -167,9 +167,9 @@ module ApplicationHelper css_classes = short_format ? 'js-short-timeago' : 'js-timeago' css_classes << " #{html_class}" unless html_class.blank? - element = content_tag :time, time.strftime("%b %d, %Y"), + element = content_tag :time, l(time, format: "%b %d, %Y"), class: css_classes, - title: time.to_time.in_time_zone.to_s(:medium), + title: l(time.to_time.in_time_zone, format: :timeago_tooltip), datetime: time.to_time.getutc.iso8601, data: { toggle: 'tooltip', @@ -204,6 +204,10 @@ module ApplicationHelper 'https://' + promo_host end + def support_url + current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/' + end + def page_filter_path(options = {}) without = options.delete(:without) add_label = options.delete(:label) diff --git a/app/helpers/blame_helper.rb b/app/helpers/blame_helper.rb new file mode 100644 index 00000000000..d1dc4d94560 --- /dev/null +++ b/app/helpers/blame_helper.rb @@ -0,0 +1,21 @@ +module BlameHelper + def age_map_duration(blame_groups, project) + now = Time.zone.now + start_date = blame_groups.map { |blame_group| blame_group[:commit].committed_date } + .append(project.created_at).min + + { + now: now, + started_days_ago: (now - start_date).to_i / 1.day + } + end + + def age_map_class(commit_date, duration) + commit_date_days_ago = (duration[:now] - commit_date).to_i / 1.day + # Numbers 0 to 10 come from this calculation, but only commits on the oldest + # day get number 10 (all other numbers can be multiple days), so the range + # is normalized to 0-9 + age_group = [(10 * commit_date_days_ago) / duration[:started_days_ago], 9].min + "blame-commit-age-#{age_group}" + end +end diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb index eb03ced67eb..0a15c29cfb5 100644 --- a/app/helpers/broadcast_messages_helper.rb +++ b/app/helpers/broadcast_messages_helper.rb @@ -1,5 +1,5 @@ module BroadcastMessagesHelper - def broadcast_message(message = BroadcastMessage.current) + def broadcast_message(message) return unless message.present? content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 2ae3a616933..16a99addd0b 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -66,12 +66,12 @@ module DiffHelper discussions_left = discussions_right = nil - if left && (left.unchanged? || left.discussable?) + if left && left.discussable? && (left.unchanged? || left.removed?) line_code = diff_file.line_code(left) discussions_left = @grouped_diff_discussions[line_code] end - if right&.discussable? + if right && right.discussable? && right.added? line_code = diff_file.line_code(right) discussions_right = @grouped_diff_discussions[line_code] end @@ -124,6 +124,30 @@ module DiffHelper !diff_file.deleted_file? && @merge_request && @merge_request.source_project end + def diff_render_error_reason(viewer) + case viewer.render_error + when :too_large + "it is too large" + when :server_side_but_stored_externally + case viewer.diff_file.external_storage + when :lfs + 'it is stored in LFS' + else + 'it is stored externally' + end + end + end + + def diff_render_error_options(viewer) + diff_file = viewer.diff_file + options = [] + + blob_url = namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path)) + options << link_to('view the blob', blob_url) + + options + end + private def diff_btn(title, name, selected) diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 3b24f183785..fdbca789d21 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -66,4 +66,17 @@ module EmailsHelper ) end end + + def email_default_heading(text) + content_tag :h1, text, style: [ + "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif", + 'color:#333333', + 'font-size:18px', + 'font-weight:400', + 'line-height:1.4', + 'padding:0', + 'margin:0', + 'text-align:center' + ].join(';') + end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index c59d8dafc83..64ad7b280cb 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -10,8 +10,8 @@ module NotesHelper Ability.can_edit_note?(current_user, note) end - def note_supports_slash_commands?(note) - Notes::SlashCommandsService.supported?(note, current_user) + def note_supports_quick_actions?(note) + Notes::QuickActionsService.supported?(note, current_user) end def noteable_json(noteable) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7441b58fddb..0d0459f5a70 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -80,7 +80,7 @@ module ProjectsHelper end def remove_fork_project_message(project) - _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") % + _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") % { forked_from_project: @project.forked_from_project.name_with_namespace } end @@ -151,14 +151,21 @@ module ProjectsHelper disabled: disabled_option ) - content_tag( - :select, - options, - name: "project[project_feature_attributes][#{field}]", - id: "project_project_feature_attributes_#{field}", - class: "pull-right form-control #{repo_children_classes(field)}", - data: { field: field } - ).html_safe + content_tag :div, class: "select-wrapper" do + concat( + content_tag( + :select, + options, + name: "project[project_feature_attributes][#{field}]", + id: "project_project_feature_attributes_#{field}", + class: "pull-right form-control select-control #{repo_children_classes(field)} ", + data: { field: field } + ) + ) + concat( + icon('chevron-down') + ) + end.html_safe end def link_to_autodeploy_doc @@ -218,6 +225,10 @@ module ProjectsHelper nav_tabs << :container_registry end + if project.builds_enabled? && can?(current_user, :read_pipeline, project) + nav_tabs << :pipelines + end + tab_ability_map.each do |tab, ability| if can?(current_user, ability, project) nav_tabs << tab @@ -231,7 +242,6 @@ module ProjectsHelper { environments: :read_environment, milestones: :read_milestone, - pipelines: :read_pipeline, snippets: :read_project_snippet, settings: :admin_project, builds: :read_build, diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index f7ed61625f4..962570a0efd 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -2,7 +2,9 @@ class DeviseMailer < Devise::Mailer default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" default reply_to: Gitlab.config.gitlab.email_reply_to - layout 'devise_mailer' + layout 'mailer/devise' + + helper EmailsHelper protected diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 2192f76499d..668caef0d2c 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -37,7 +37,12 @@ class ApplicationSetting < ActiveRecord::Base validates :home_page_url, allow_blank: true, url: true, - if: :home_page_url_column_exist + if: :home_page_url_column_exists? + + validates :help_page_support_url, + allow_blank: true, + url: true, + if: :help_page_support_url_column_exists? validates :after_sign_out_path, allow_blank: true, @@ -215,6 +220,7 @@ class ApplicationSetting < ActiveRecord::Base domain_whitelist: Settings.gitlab['domain_whitelist'], gravatar_enabled: Settings.gravatar['enabled'], help_page_text: nil, + help_page_hide_commercial_content: false, unique_ips_limit_per_user: 10, unique_ips_limit_time_window: 3600, unique_ips_limit_enabled: false, @@ -263,10 +269,14 @@ class ApplicationSetting < ActiveRecord::Base end end - def home_page_url_column_exist + def home_page_url_column_exists? ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) end + def help_page_support_url_column_exists? + ActiveRecord::Base.connection.column_exists?(:application_settings, :help_page_support_url) + end + def sidekiq_throttling_column_exists? ActiveRecord::Base.connection.column_exists?(:application_settings, :sidekiq_throttling_enabled) end diff --git a/app/models/blob_viewer/server_side.rb b/app/models/blob_viewer/server_side.rb index e6bcacf7f70..fbc1b520c01 100644 --- a/app/models/blob_viewer/server_side.rb +++ b/app/models/blob_viewer/server_side.rb @@ -13,14 +13,12 @@ module BlobViewer end def render_error - if blob.stored_externally? - # Files that are not stored in the repository, like LFS files and - # build artifacts, can only be rendered using a client-side viewer, - # since we do not want to read large amounts of data into memory on the - # server side. Client-side viewers use JS and can fetch the file from - # `blob_raw_url` using AJAX. - return :server_side_but_stored_externally - end + # Files that are not stored in the repository, like LFS files and + # build artifacts, can only be rendered using a client-side viewer, + # since we do not want to read large amounts of data into memory on the + # server side. Client-side viewers use JS and can fetch the file from + # `blob_raw_url` using AJAX. + return :server_side_but_stored_externally if blob.stored_externally? super end diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index cb40f33932a..944725d91c3 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -16,7 +16,7 @@ class BroadcastMessage < ActiveRecord::Base def self.current Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do - where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last + where('ends_at > :now AND starts_at <= :now', now: Time.zone.now).order([:created_at, :id]).to_a end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cec1ca89a6a..58758f7ca8a 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -33,7 +33,7 @@ module Ci scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } - scope :manual_actions, ->() { where(when: :manual).relevant } + scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader @@ -109,7 +109,7 @@ module Ci end def playable? - action? && manual? + action? && (manual? || complete?) end def action? diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index cb425706a9e..07cec63b939 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -112,7 +112,7 @@ class CommitStatus < ActiveRecord::Base end def group_name - name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip + name.to_s.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip end def failed_but_allowed? @@ -156,7 +156,7 @@ class CommitStatus < ActiveRecord::Base end def sortable_name - name.split(/(\d+)/).map do |v| + name.to_s.split(/(\d+)/).map do |v| v =~ /\d+/ ? v.to_i : v end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index ea10d004c9c..8e367576c9d 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -67,7 +67,6 @@ module Issuable scope :authored, ->(user) { where(author_id: user) } scope :recent, -> { reorder(id: :desc) } - scope :order_position_asc, -> { reorder(position: :asc) } scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_milestones, ->(ids) { where(milestone_id: ids) } scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) } @@ -139,7 +138,6 @@ module Issuable when 'upvotes_desc' then order_upvotes_desc when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels) when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels) - when 'position_asc' then order_position_asc else order_by(method) end diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index a3472af5c55..01599ce49c6 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -40,10 +40,18 @@ module Milestoneish def issues_visible_to_user(user) memoize_per_user(user, :issues_visible_to_user) do IssuesFinder.new(user, issues_finder_params) - .execute.includes(:assignees).where(milestone_id: milestoneish_ids) + .execute.preload(:assignees).where(milestone_id: milestoneish_ids) end end + def sorted_issues(user) + issues_visible_to_user(user).preload_associations.sort('label_priority') + end + + def sorted_merge_requests + merge_requests.sort('label_priority') + end + def upcoming? start_date && start_date.future? end diff --git a/app/models/diff_viewer/added.rb b/app/models/diff_viewer/added.rb new file mode 100644 index 00000000000..1909e6ef9d8 --- /dev/null +++ b/app/models/diff_viewer/added.rb @@ -0,0 +1,8 @@ +module DiffViewer + class Added < Base + include Simple + include Static + + self.partial_name = 'added' + end +end diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb new file mode 100644 index 00000000000..0cbe714288d --- /dev/null +++ b/app/models/diff_viewer/base.rb @@ -0,0 +1,87 @@ +module DiffViewer + class Base + PARTIAL_PATH_PREFIX = 'projects/diffs/viewers'.freeze + + class_attribute :partial_name, :type, :extensions, :file_types, :binary, :switcher_icon, :switcher_title + + # These limits relate to the sum of the old and new blob sizes. + # Limits related to the actual size of the diff are enforced in Gitlab::Diff::File. + class_attribute :collapse_limit, :size_limit + + delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class + + attr_reader :diff_file + + delegate :project, to: :diff_file + + def initialize(diff_file) + @diff_file = diff_file + @initially_binary = diff_file.binary? + end + + def self.partial_path + File.join(PARTIAL_PATH_PREFIX, partial_name) + end + + def self.rich? + type == :rich + end + + def self.simple? + type == :simple + end + + def self.binary? + binary + end + + def self.text? + !binary? + end + + def self.can_render?(diff_file, verify_binary: true) + can_render_blob?(diff_file.old_blob, verify_binary: verify_binary) && + can_render_blob?(diff_file.new_blob, verify_binary: verify_binary) + end + + def self.can_render_blob?(blob, verify_binary: true) + return true if blob.nil? + return false if verify_binary && binary? != blob.binary? + return true if extensions&.include?(blob.extension) + return true if file_types&.include?(blob.file_type) + + false + end + + def collapsed? + return @collapsed if defined?(@collapsed) + return @collapsed = true if diff_file.collapsed? + + @collapsed = !diff_file.expanded? && collapse_limit && diff_file.raw_size > collapse_limit + end + + def too_large? + return @too_large if defined?(@too_large) + return @too_large = true if diff_file.too_large? + + @too_large = size_limit && diff_file.raw_size > size_limit + end + + def binary_detected_after_load? + !@initially_binary && diff_file.binary? + end + + # This method is used on the server side to check whether we can attempt to + # render the diff_file at all. Human-readable error messages are found in the + # `BlobHelper#diff_render_error_reason` helper. + def render_error + if too_large? + :too_large + end + end + + def prepare! + # To be overridden by subclasses + end + end +end diff --git a/app/models/diff_viewer/client_side.rb b/app/models/diff_viewer/client_side.rb new file mode 100644 index 00000000000..cf41d07f8eb --- /dev/null +++ b/app/models/diff_viewer/client_side.rb @@ -0,0 +1,10 @@ +module DiffViewer + module ClientSide + extend ActiveSupport::Concern + + included do + self.collapse_limit = 1.megabyte + self.size_limit = 10.megabytes + end + end +end diff --git a/app/models/diff_viewer/deleted.rb b/app/models/diff_viewer/deleted.rb new file mode 100644 index 00000000000..9c129bac694 --- /dev/null +++ b/app/models/diff_viewer/deleted.rb @@ -0,0 +1,8 @@ +module DiffViewer + class Deleted < Base + include Simple + include Static + + self.partial_name = 'deleted' + end +end diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb new file mode 100644 index 00000000000..759d9a36ebb --- /dev/null +++ b/app/models/diff_viewer/image.rb @@ -0,0 +1,12 @@ +module DiffViewer + class Image < Base + include Rich + include ClientSide + + self.partial_name = 'image' + self.extensions = UploaderHelper::IMAGE_EXT + self.binary = true + self.switcher_icon = 'picture-o' + self.switcher_title = 'image diff' + end +end diff --git a/app/models/diff_viewer/mode_changed.rb b/app/models/diff_viewer/mode_changed.rb new file mode 100644 index 00000000000..d487d996f8d --- /dev/null +++ b/app/models/diff_viewer/mode_changed.rb @@ -0,0 +1,8 @@ +module DiffViewer + class ModeChanged < Base + include Simple + include Static + + self.partial_name = 'mode_changed' + end +end diff --git a/app/models/diff_viewer/no_preview.rb b/app/models/diff_viewer/no_preview.rb new file mode 100644 index 00000000000..5455fee4490 --- /dev/null +++ b/app/models/diff_viewer/no_preview.rb @@ -0,0 +1,9 @@ +module DiffViewer + class NoPreview < Base + include Simple + include Static + + self.partial_name = 'no_preview' + self.binary = true + end +end diff --git a/app/models/diff_viewer/not_diffable.rb b/app/models/diff_viewer/not_diffable.rb new file mode 100644 index 00000000000..4f9638626ea --- /dev/null +++ b/app/models/diff_viewer/not_diffable.rb @@ -0,0 +1,9 @@ +module DiffViewer + class NotDiffable < Base + include Simple + include Static + + self.partial_name = 'not_diffable' + self.binary = true + end +end diff --git a/app/models/diff_viewer/renamed.rb b/app/models/diff_viewer/renamed.rb new file mode 100644 index 00000000000..f1fbfd8c6d5 --- /dev/null +++ b/app/models/diff_viewer/renamed.rb @@ -0,0 +1,8 @@ +module DiffViewer + class Renamed < Base + include Simple + include Static + + self.partial_name = 'renamed' + end +end diff --git a/app/models/diff_viewer/rich.rb b/app/models/diff_viewer/rich.rb new file mode 100644 index 00000000000..3b0ca6e4cff --- /dev/null +++ b/app/models/diff_viewer/rich.rb @@ -0,0 +1,11 @@ +module DiffViewer + module Rich + extend ActiveSupport::Concern + + included do + self.type = :rich + self.switcher_icon = 'file-text-o' + self.switcher_title = 'rendered diff' + end + end +end diff --git a/app/models/diff_viewer/server_side.rb b/app/models/diff_viewer/server_side.rb new file mode 100644 index 00000000000..aed1a0791b1 --- /dev/null +++ b/app/models/diff_viewer/server_side.rb @@ -0,0 +1,26 @@ +module DiffViewer + module ServerSide + extend ActiveSupport::Concern + + included do + self.collapse_limit = 1.megabyte + self.size_limit = 5.megabytes + end + + def prepare! + diff_file.old_blob&.load_all_data! + diff_file.new_blob&.load_all_data! + end + + def render_error + # Files that are not stored in the repository, like LFS files and + # build artifacts, can only be rendered using a client-side viewer, + # since we do not want to read large amounts of data into memory on the + # server side. Client-side viewers use JS and can fetch the file from + # `diff_file_blob_raw_path` and `diff_file_old_blob_raw_path` using AJAX. + return :server_side_but_stored_externally if diff_file.stored_externally? + + super + end + end +end diff --git a/app/models/diff_viewer/simple.rb b/app/models/diff_viewer/simple.rb new file mode 100644 index 00000000000..65750996ee4 --- /dev/null +++ b/app/models/diff_viewer/simple.rb @@ -0,0 +1,11 @@ +module DiffViewer + module Simple + extend ActiveSupport::Concern + + included do + self.type = :simple + self.switcher_icon = 'code' + self.switcher_title = 'source diff' + end + end +end diff --git a/app/models/diff_viewer/static.rb b/app/models/diff_viewer/static.rb new file mode 100644 index 00000000000..d761328b3f6 --- /dev/null +++ b/app/models/diff_viewer/static.rb @@ -0,0 +1,10 @@ +module DiffViewer + module Static + extend ActiveSupport::Concern + + # We can always render a static viewer, even if the diff is too large. + def render_error + nil + end + end +end diff --git a/app/models/diff_viewer/text.rb b/app/models/diff_viewer/text.rb new file mode 100644 index 00000000000..98f4b2aea2a --- /dev/null +++ b/app/models/diff_viewer/text.rb @@ -0,0 +1,15 @@ +module DiffViewer + class Text < Base + include Simple + include ServerSide + + self.partial_name = 'text' + self.binary = false + + # Since the text diff viewer doesn't render the old and new blobs in full, + # we only need the limits related to the actual size of the diff which are + # already enforced in Gitlab::Diff::File. + self.collapse_limit = nil + self.size_limit = nil + end +end diff --git a/app/models/environment.rb b/app/models/environment.rb index 6211a5c1e63..d5b974b2d31 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -209,7 +209,8 @@ class Environment < ActiveRecord::Base def etag_cache_key Gitlab::Routing.url_helpers.namespace_project_environments_path( project.namespace, - project) + project, + format: :json) end private diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index 8867ba0d2ff..532b8f4ad69 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -11,6 +11,7 @@ class GenericCommitStatus < CommitStatus def set_default_values self.context ||= 'default' self.stage ||= 'external' + self.stage_idx ||= 1000000 end def tags diff --git a/app/models/issue.rb b/app/models/issue.rb index 693cc21bb40..f0f525aea21 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -9,6 +9,9 @@ class Issue < ActiveRecord::Base include Spammable include FasterCacheKeys include RelativePositioning + include IgnorableColumn + + ignore_column :position DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze @@ -44,7 +47,7 @@ class Issue < ActiveRecord::Base scope :created_after, -> (datetime) { where("created_at >= ?", datetime) } - scope :include_associations, -> { includes(:labels, project: :namespace) } + scope :preload_associations, -> { preload(:labels, project: :namespace) } after_save :expire_etag_cache diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 7126de2d488..2d5909ab25e 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -42,7 +42,7 @@ class LegacyDiffNote < Note end def for_line?(line) - !line.meta? && diff_file.line_code(line) == self.line_code + line.discussable? && diff_file.line_code(line) == self.line_code end def original_line_code diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index dd155252ad5..ea22ab53587 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -4,6 +4,9 @@ class MergeRequest < ActiveRecord::Base include Noteable include Referable include Sortable + include IgnorableColumn + + ignore_column :position belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" @@ -889,7 +892,7 @@ class MergeRequest < ActiveRecord::Base !has_commits? end - def mergeable_with_slash_command?(current_user, autocomplete_precheck: false, last_diff_sha: nil) + def mergeable_with_quick_action?(current_user, autocomplete_precheck: false, last_diff_sha: nil) return false unless can_be_merged_by?(current_user) return true if autocomplete_precheck diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 99dd2130188..f1ee4d3f7a9 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -10,6 +10,7 @@ class MergeRequestDiff < ActiveRecord::Base VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze belongs_to :merge_request + has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize @@ -91,7 +92,7 @@ class MergeRequestDiff < ActiveRecord::Base head_commit_sha).diffs(options) else @raw_diffs ||= {} - @raw_diffs[options] ||= load_diffs(st_diffs, options) + @raw_diffs[options] ||= load_diffs(options) end end @@ -253,24 +254,44 @@ class MergeRequestDiff < ActiveRecord::Base update_columns_serialized(new_attributes) end - def dump_diffs(diffs) - if diffs.respond_to?(:map) - diffs.map(&:to_hash) + def create_merge_request_diff_files(diffs) + rows = diffs.map.with_index do |diff, index| + diff.to_hash.merge( + merge_request_diff_id: self.id, + relative_order: index + ) end + + Gitlab::Database.bulk_insert('merge_request_diff_files', rows) end - def load_diffs(raw, options) - if valid_raw_diff?(raw) - if paths = options[:paths] - raw = raw.select do |diff| - paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) - end - end + def load_diffs(options) + return Gitlab::Git::DiffCollection.new([]) unless diffs_from_database - Gitlab::Git::DiffCollection.new(raw, options) - else - Gitlab::Git::DiffCollection.new([]) + raw = diffs_from_database + + if paths = options[:paths] + raw = raw.select do |diff| + paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) + end end + + Gitlab::Git::DiffCollection.new(raw, options) + end + + def diffs_from_database + return @diffs_from_database if defined?(@diffs_from_database) + + @diffs_from_database = + if st_diffs.present? + if valid_raw_diff?(st_diffs) + st_diffs + end + elsif merge_request_diff_files.present? + merge_request_diff_files + .as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS) + .map(&:with_indifferent_access) + end end # Load diffs between branches related to current merge request diff from repo @@ -285,11 +306,10 @@ class MergeRequestDiff < ActiveRecord::Base new_attributes[:real_size] = diff_collection.real_size if diff_collection.any? - new_diffs = dump_diffs(diff_collection) new_attributes[:state] = :collected - end - new_attributes[:st_diffs] = new_diffs || [] + create_merge_request_diff_files(diff_collection) + end # Set our state to 'overflow' to make the #empty? and #collected? # methods (generated by StateMachine) return false. diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb new file mode 100644 index 00000000000..598ebd4d829 --- /dev/null +++ b/app/models/merge_request_diff_file.rb @@ -0,0 +1,11 @@ +class MergeRequestDiffFile < ActiveRecord::Base + include Gitlab::EncodingHelper + + belongs_to :merge_request_diff + + def utf8_diff + return '' if diff.blank? + + encode_utf8(diff) if diff.respond_to?(:encoding) + end +end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index b04bed4c014..0a6fc064aec 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -164,38 +164,6 @@ class Milestone < ActiveRecord::Base write_attribute(:title, sanitize_title(value)) if value.present? end - # Sorts the issues for the given IDs. - # - # This method runs a single SQL query using a CASE statement to update the - # position of all issues in the current milestone (scoped to the list of IDs). - # - # Given the ids [10, 20, 30] this method produces a SQL query something like - # the following: - # - # UPDATE issues - # SET position = CASE - # WHEN id = 10 THEN 1 - # WHEN id = 20 THEN 2 - # WHEN id = 30 THEN 3 - # ELSE position - # END - # WHERE id IN (10, 20, 30); - # - # This method expects that the IDs given in `ids` are already Fixnums. - def sort_issues(ids) - pairs = [] - - ids.each_with_index do |id, index| - pairs << id - pairs << index + 1 - end - - conditions = 'WHEN id = ? THEN ? ' * ids.length - - issues.where(id: ids). - update_all(["position = CASE #{conditions} ELSE position END", *pairs]) - end - private def milestone_format_reference(format = :iid) diff --git a/app/models/note.rb b/app/models/note.rb index 244bf169c29..3221e653e30 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -32,7 +32,7 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer attr_accessor :user_visible_reference_count - # Attribute used to store the attributes that have ben changed by slash commands. + # Attribute used to store the attributes that have ben changed by quick actions. attr_accessor :commands_changes default_value_for :system, false diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 42412a9a44b..b0df7aeb323 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -41,10 +41,8 @@ class NotificationSetting < ActiveRecord::Base :success_pipeline ].freeze - store :events, accessors: EMAIL_EVENTS, coder: JSON - - before_create :set_events - before_save :events_to_boolean + store :events, coder: JSON + before_save :convert_events def self.find_or_create_for(source) setting = find_or_initialize_by(source: source) @@ -56,21 +54,18 @@ class NotificationSetting < ActiveRecord::Base setting end - # Set all event attributes to false when level is not custom or being initialized for UX reasons - def set_events - return if custom? - - self.events = {} - end + # 1. Check if this event has a value stored in its database column. + # 2. If it does, return that value. + # 3. If it doesn't (the value is nil), return the value from the serialized + # JSON hash in `events`. + (EMAIL_EVENTS - [:failed_pipeline]).each do |event| + define_method(event) do + bool = super() - # Validates store accessors values as boolean - # It is a text field so it does not cast correct boolean values in JSON - def events_to_boolean - EMAIL_EVENTS.each do |event| - bool = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(public_send(event)) - - events[event] = bool + bool.nil? ? !!events[event] : bool end + + alias_method :"#{event}?", event end # Allow people to receive failed pipeline notifications if they already have @@ -78,7 +73,23 @@ class NotificationSetting < ActiveRecord::Base # custom settings. def failed_pipeline bool = super + bool = events[:failed_pipeline] if bool.nil? bool.nil? || bool end + alias_method :failed_pipeline?, :failed_pipeline + + def event_enabled?(event) + respond_to?(event) && public_send(event) + end + + def convert_events + return if events_before_type_cast.nil? + + EMAIL_EVENTS.each do |event| + write_attribute(event, public_send(event)) + end + + write_attribute(:events, nil) + end end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 8977a7cdafe..48e7802c557 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -116,30 +116,19 @@ class KubernetesService < DeploymentService # short time later def terminals(environment) with_reactive_cache do |data| - pods = data.fetch(:pods, nil) - filter_pods(pods, app: environment.slug). - flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }. - each { |terminal| add_terminal_auth(terminal, terminal_auth) } + pods = filter_by_label(data[:pods], app: environment.slug) + terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) } + terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) } end end - # Caches all pods in the namespace so other calls don't need to block on - # network access. + # Caches resources in the namespace so other calls don't need to block on + # network access def calculate_reactive_cache return unless active? && project && !project.pending_delete? - kubeclient = build_kubeclient! - - # Store as hashes, rather than as third-party types - pods = begin - kubeclient.get_pods(namespace: actual_namespace).as_json - rescue KubeException => err - raise err unless err.error_code == 404 - [] - end - # We may want to cache extra things in the future - { pods: pods } + { pods: read_pods } end TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze @@ -166,6 +155,16 @@ class KubernetesService < DeploymentService ) end + # Returns a hash of all pods in the namespace + def read_pods + kubeclient = build_kubeclient! + + kubeclient.get_pods(namespace: actual_namespace).as_json + rescue KubeException => err + raise err unless err.error_code == 404 + [] + end + def kubeclient_ssl_options opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } @@ -181,11 +180,11 @@ class KubernetesService < DeploymentService { bearer_token: token } end - def join_api_url(*parts) + def join_api_url(api_path) url = URI.parse(api_url) prefix = url.path.sub(%r{/+\z}, '') - url.path = [prefix, *parts].join("/") + url.path = [prefix, api_path].join("/") url.to_s end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 56f42d63b2d..c2f887e24a0 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -1,4 +1,4 @@ -class MattermostSlashCommandsService < ChatSlashCommandsService +class MattermostSlashCommandsService < SlashCommandsService include TriggersHelper prop_accessor :token diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 2182c1c7e4b..1c3892a3f75 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -1,4 +1,4 @@ -class SlackSlashCommandsService < ChatSlashCommandsService +class SlackSlashCommandsService < SlashCommandsService include TriggersHelper def title diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index 8b5bc24fd3c..4592cb747a0 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -1,6 +1,6 @@ # Base class for Chat services # This class is not meant to be used directly, but only to inherrit from. -class ChatSlashCommandsService < Service +class SlashCommandsService < Service default_value_for :category, 'chat' prop_accessor :token @@ -33,10 +33,10 @@ class ChatSlashCommandsService < Service user = find_chat_user(params) if user - Gitlab::ChatCommands::Command.new(project, user, params).execute + Gitlab::SlashCommands::Command.new(project, user, params).execute else url = authorize_chat_name_url(params) - Gitlab::ChatCommands::Presenters::Access.new(url).authorize + Gitlab::SlashCommands::Presenters::Access.new(url).authorize end end diff --git a/app/models/user.rb b/app/models/user.rb index 5d128e4b390..782c162e1f3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -139,21 +139,21 @@ class User < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false } - validate :namespace_uniq, if: ->(user) { user.username_changed? } + validate :namespace_uniq, if: :username_changed? validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } - validate :unique_email, if: ->(user) { user.email_changed? } - validate :owns_notification_email, if: ->(user) { user.notification_email_changed? } - validate :owns_public_email, if: ->(user) { user.public_email_changed? } + validate :unique_email, if: :email_changed? + validate :owns_notification_email, if: :notification_email_changed? + validate :owns_public_email, if: :public_email_changed? validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } before_validation :sanitize_attrs - before_validation :set_notification_email, if: ->(user) { user.email_changed? } - before_validation :set_public_email, if: ->(user) { user.public_email_changed? } + before_validation :set_notification_email, if: :email_changed? + before_validation :set_public_email, if: :public_email_changed? - after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? } + after_update :update_emails_with_primary_email, if: :email_changed? before_save :ensure_authentication_token, :ensure_incoming_email_token - before_save :ensure_external_user_rights + before_save :ensure_user_rights_and_limits, if: :external_changed? after_save :ensure_namespace_correct after_initialize :set_projects_limit after_destroy :post_destroy_hook @@ -1033,11 +1033,14 @@ class User < ActiveRecord::Base super end - def ensure_external_user_rights - return unless external? - - self.can_create_group = false - self.projects_limit = 0 + def ensure_user_rights_and_limits + if external? + self.can_create_group = false + self.projects_limit = 0 + else + self.can_create_group = gitlab_config.default_can_create_group + self.projects_limit = current_application_settings.default_projects_limit + end end def signup_domain_valid? diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 4757ba71680..2683aaad981 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -10,7 +10,7 @@ class GlobalPolicy < BasePolicy can! :access_api can! :access_git can! :receive_notifications - can! :use_slash_commands + can! :use_quick_actions end end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 3959b895f44..47518dddb61 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -203,7 +203,7 @@ class ProjectPolicy < BasePolicy unless project.feature_available?(:builds, user) && repository_enabled cannot!(*named_abilities(:build)) - cannot!(*named_abilities(:pipeline)) + cannot!(*named_abilities(:pipeline) - [:read_pipeline]) cannot!(*named_abilities(:pipeline_schedule)) cannot!(*named_abilities(:environment)) cannot!(*named_abilities(:deployment)) diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 514c4c2e35f..eeb5399aa8b 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -1,18 +1,15 @@ -class BuildDetailsEntity < BuildEntity +class BuildDetailsEntity < JobEntity expose :coverage, :erased_at, :duration expose :tag_list, as: :tags - expose :user, using: UserEntity + expose :runner, using: RunnerEntity + expose :pipeline, using: PipelineEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build| erase_namespace_project_job_path(project.namespace, project, build) end - expose :artifacts, using: BuildArtifactEntity - expose :runner, using: RunnerEntity - expose :pipeline, using: PipelineEntity - expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do expose :iid do |build| build.merge_request.iid @@ -28,7 +25,7 @@ class BuildDetailsEntity < BuildEntity end expose :raw_path do |build| - raw_namespace_project_build_path(project.namespace, project, build) + raw_namespace_project_job_path(project.namespace, project, build) end private diff --git a/app/serializers/build_serializer.rb b/app/serializers/build_serializer.rb index 79b67001199..bae9932847f 100644 --- a/app/serializers/build_serializer.rb +++ b/app/serializers/build_serializer.rb @@ -1,5 +1,5 @@ class BuildSerializer < BaseSerializer - entity BuildEntity + entity JobEntity def represent_status(resource) data = represent(resource, { only: [:status] }) diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb index 8b3de1bed0f..e493c9162fd 100644 --- a/app/serializers/deployment_entity.rb +++ b/app/serializers/deployment_entity.rb @@ -24,6 +24,6 @@ class DeploymentEntity < Grape::Entity expose :user, using: UserEntity expose :commit, using: CommitEntity - expose :deployable, using: BuildEntity - expose :manual_actions, using: BuildEntity + expose :deployable, using: JobEntity + expose :manual_actions, using: JobEntity end diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb index 4f506f7e745..7c872a3e986 100644 --- a/app/serializers/group_entity.rb +++ b/app/serializers/group_entity.rb @@ -5,11 +5,15 @@ class GroupEntity < Grape::Entity include GroupsHelper expose :id, :name, :path, :description, :visibility - expose :web_url expose :full_name, :full_path + expose :web_url expose :parent_id expose :created_at, :updated_at + expose :group_path do |group| + group_path(group) + end + expose :permissions do expose :human_group_access do |group, options| group.group_members.find_by(user_id: request.current_user)&.human_access diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb index 65b204d4dd2..bd5211b8e58 100644 --- a/app/serializers/issuable_entity.rb +++ b/app/serializers/issuable_entity.rb @@ -5,7 +5,6 @@ class IssuableEntity < Grape::Entity expose :description expose :lock_version expose :milestone_id - expose :position expose :state expose :title expose :updated_by_id diff --git a/app/serializers/build_entity.rb b/app/serializers/job_entity.rb index 67001f4547d..d6de43bcbcb 100644 --- a/app/serializers/build_entity.rb +++ b/app/serializers/job_entity.rb @@ -1,11 +1,11 @@ -class BuildEntity < Grape::Entity +class JobEntity < Grape::Entity include RequestAwareEntity expose :id expose :name expose :build_path do |build| - path_to(:namespace_project_job, build) + build.target_url || path_to(:namespace_project_job, build) end expose :retry_path, if: -> (*) { retryable? } do |build| diff --git a/app/serializers/job_group_entity.rb b/app/serializers/job_group_entity.rb index 04487e59009..8554de55517 100644 --- a/app/serializers/job_group_entity.rb +++ b/app/serializers/job_group_entity.rb @@ -4,7 +4,7 @@ class JobGroupEntity < Grape::Entity expose :name expose :size expose :detailed_status, as: :status, with: StatusEntity - expose :jobs, with: BuildEntity + expose :jobs, with: JobEntity private diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f080e6326a1..fb1d4aed58b 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -101,12 +101,12 @@ class GitPushService < BaseService UpdateMergeRequestsWorker .perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) - SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks) - EventCreateService.new.push(@project, current_user, build_push_data) + Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push) + + SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks) - Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push) if push_remove_branch? AfterBranchDeleteService diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 7c424fba428..9917a39b795 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -8,10 +8,12 @@ class GitTagPushService < BaseService @push_data = build_push_data EventCreateService.new.push(project, current_user, @push_data) + Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push) + SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks) - Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push) + ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size]) true diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index a65d6e11c47..c8db4728aea 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -142,9 +142,9 @@ class IssuableBaseService < BaseService LabelsFinder.new(current_user, project_id: @project.id).execute end - def merge_slash_commands_into_params!(issuable) + def merge_quick_actions_into_params!(issuable) description, command_params = - SlashCommands::InterpretService.new(project, current_user). + QuickActions::InterpretService.new(project, current_user). execute(params[:description], issuable) # Avoid a description already set on an issuable to be overwritten by a nil @@ -162,7 +162,7 @@ class IssuableBaseService < BaseService end def create(issuable) - merge_slash_commands_into_params!(issuable) + merge_quick_actions_into_params!(issuable) filter_params(issuable) params.delete(:state_event) @@ -236,8 +236,9 @@ class IssuableBaseService < BaseService ) if old_assignees != issuable.assignees - assignees = old_assignees + issuable.assignees.to_a - invalidate_cache_counts(assignees.compact, issuable) + new_assignees = issuable.assignees.to_a + affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees) + invalidate_cache_counts(affected_assignees.compact, issuable) end after_update(issuable) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 5c843a258fb..75a65aecd1a 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -7,7 +7,7 @@ module MergeRequests params.except!(:target_project_id) params.except!(:source_branch) - merge_from_slash_command(merge_request) if params[:merge] + merge_from_quick_action(merge_request) if params[:merge] if merge_request.closed_without_fork? params.except!(:target_branch, :force_remove_source_branch) @@ -74,9 +74,9 @@ module MergeRequests end end - def merge_from_slash_command(merge_request) + def merge_from_quick_action(merge_request) last_diff_sha = params.delete(:merge) - return unless merge_request.mergeable_with_slash_command?(current_user, last_diff_sha: last_diff_sha) + return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha) merge_request.update(merge_error: nil) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index f3954f6f8c4..06971483992 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -9,11 +9,11 @@ module Notes # We execute commands (extracted from `params[:note]`) on the noteable # **before** we save the note because if the note consists of commands # only, there is no need be create a note! - slash_commands_service = SlashCommandsService.new(project, current_user) + quick_actions_service = QuickActionsService.new(project, current_user) - if slash_commands_service.supported?(note) + if quick_actions_service.supported?(note) options = { merge_request_diff_head_sha: merge_request_diff_head_sha } - content, command_params = slash_commands_service.extract_commands(note, options) + content, command_params = quick_actions_service.extract_commands(note, options) only_commands = content.empty? @@ -30,7 +30,7 @@ module Notes end if command_params.present? - slash_commands_service.execute(command_params, note) + quick_actions_service.execute(command_params, note) # We must add the error after we call #save because errors are reset # when #save is called diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/quick_actions_service.rb index ad1e6f6774a..8f81b54164a 100644 --- a/app/services/notes/slash_commands_service.rb +++ b/app/services/notes/quick_actions_service.rb @@ -1,5 +1,5 @@ module Notes - class SlashCommandsService < BaseService + class QuickActionsService < BaseService UPDATE_SERVICES = { 'Issue' => Issues::UpdateService, 'MergeRequest' => MergeRequests::UpdateService @@ -22,7 +22,7 @@ module Notes def extract_commands(note, options = {}) return [note.note, {}] unless supported?(note) - SlashCommands::InterpretService.new(project, current_user, options). + QuickActions::InterpretService.new(project, current_user, options). execute(note.note, note.noteable) end diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 988bd0a7cdb..8d1820bc504 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -8,7 +8,7 @@ class NotificationRecipientService @project = project end - def build_recipients(target, current_user, action: nil, previous_assignee: nil, skip_current_user: true) + def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true) custom_action = build_custom_key(action, target) recipients = target.participants(current_user) @@ -59,7 +59,7 @@ class NotificationRecipientService return [] if notification_setting.mention? || notification_setting.disabled? - return [] if notification_setting.custom? && !notification_setting.public_send(custom_action) + return [] if notification_setting.custom? && !notification_setting.event_enabled?(custom_action) return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action) @@ -176,7 +176,7 @@ class NotificationRecipientService if notification_level settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level]) - settings = settings.select { |setting| setting.events[action] } if action.present? + settings = settings.select { |setting| setting.event_enabled?(action) } if action.present? settings.map(&:user_id) else resource.notification_settings.pluck(:user_id) @@ -225,7 +225,7 @@ class NotificationRecipientService def user_ids_with_global_level_custom(ids, action) settings = settings_with_global_level_of(:custom, ids) - settings = settings.select { |setting| setting.events[action] } + settings = settings.select { |setting| setting.event_enabled?(action) } settings.map(&:user_id) end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 646ccbdb2bf..3a98a5f6b64 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -273,7 +273,7 @@ class NotificationService end def issue_moved(issue, new_issue, current_user) - recipients = NotificationRecipientService.new(issue.project).build_recipients(issue, current_user) + recipients = NotificationRecipientService.new(issue.project).build_recipients(issue, current_user, action: 'moved') recipients.map do |recipient| email = mailer.issue_moved_email(recipient, issue, new_issue, current_user) diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb index 10d45bbf73c..4ee2c1796bd 100644 --- a/app/services/preview_markdown_service.rb +++ b/app/services/preview_markdown_service.rb @@ -1,6 +1,6 @@ class PreviewMarkdownService < BaseService def execute - text, commands = explain_slash_commands(params[:text]) + text, commands = explain_quick_actions(params[:text]) users = find_user_references(text) success( @@ -12,11 +12,11 @@ class PreviewMarkdownService < BaseService private - def explain_slash_commands(text) + def explain_quick_actions(text) return text, [] unless %w(Issue MergeRequest).include?(commands_target_type) - slash_commands_service = SlashCommands::InterpretService.new(project, current_user) - slash_commands_service.explain(text, find_commands_target) + quick_actions_service = QuickActions::InterpretService.new(project, current_user) + quick_actions_service.explain(text, find_commands_target) end def find_user_references(text) @@ -36,10 +36,10 @@ class PreviewMarkdownService < BaseService end def commands_target_type - params[:slash_commands_target_type] + params[:quick_actions_target_type] end def commands_target_id - params[:slash_commands_target_id] + params[:quick_actions_target_id] end end diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 015f2828921..fc85f398935 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -32,7 +32,7 @@ module Projects issuable: noteable, current_user: current_user } - SlashCommands::InterpretService.command_definitions.map do |definition| + QuickActions::InterpretService.command_definitions.map do |definition| next unless definition.available?(opts) definition.to_h(opts) diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index b6b411d2185..6816b137361 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -1,13 +1,13 @@ -module SlashCommands +module QuickActions class InterpretService < BaseService - include Gitlab::SlashCommands::Dsl + include Gitlab::QuickActions::Dsl attr_reader :issuable # Takes a text and interprets the commands that are extracted from it. # Returns the content without commands, and hash of changes to be applied to a record. def execute(content, issuable) - return [content, {}] unless current_user.can?(:use_slash_commands) + return [content, {}] unless current_user.can?(:use_quick_actions) @issuable = issuable @updates = {} @@ -20,7 +20,7 @@ module SlashCommands # Takes a text and interprets the commands that are extracted from it. # Returns the content without commands, and array of changes explained. def explain(content, issuable) - return [content, []] unless current_user.can?(:use_slash_commands) + return [content, []] unless current_user.can?(:use_quick_actions) @issuable = issuable @@ -32,7 +32,7 @@ module SlashCommands private def extractor - Gitlab::SlashCommands::Extractor.new(self.class.command_definitions) + Gitlab::QuickActions::Extractor.new(self.class.command_definitions) end desc do @@ -71,7 +71,7 @@ module SlashCommands last_diff_sha = params && params[:merge_request_diff_head_sha] issuable.is_a?(MergeRequest) && issuable.persisted? && - issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) + issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) end command :merge do @updates[:merge] = params[:merge_request_diff_head_sha] @@ -410,7 +410,7 @@ module SlashCommands params '@user' command :cc - desc 'Define target branch for MR' + desc 'Set target branch' explanation do |branch_name| "Sets target branch to #{branch_name}." end diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index 3e07b811027..f028f5eb0d4 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -34,7 +34,7 @@ module Users # Keep trying until we obtain the lease. If we don't do so we may end up # not updating the list of authorized projects properly. To prevent # hammering Redis too much we'll wait for a bit between retries. - sleep(1) + sleep(0.1) end begin diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index d552704df88..95dffdafabe 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -180,11 +180,25 @@ .col-sm-10 = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled + + %fieldset + %legend Help Page .form-group = f.label :help_page_text, class: 'control-label col-sm-2' .col-sm-10 = f.text_area :help_page_text, class: 'form-control', rows: 4 .help-block Markdown enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :help_page_hide_commercial_content do + = f.check_box :help_page_hide_commercial_content + Hide marketing-related entries from help + .form-group + = f.label :help_page_support_url, 'Support page URL', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block' + %span.help-block#support_help_block Alternate support URL for help page %fieldset %legend Pages @@ -299,8 +313,9 @@ %fieldset %legend Metrics - Prometheus %p - Setup Prometheus to measure a variety of statistics that partially overlap and complement Influx based metrics. - This setting requires a + Enable a Prometheus metrics endpoint at `#{metrics_path}` to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available + = link_to 'here', admin_health_check_path + \. This setting requires a = link_to 'restart', help_page_path('administration/restart_gitlab') to take effect. = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 2269fb1fd8c..5a4ed1c3a2a 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -21,11 +21,11 @@ .form-group.js-toggle-colors-container.hide = f.label :color, "Background Color", class: 'control-label' .col-sm-10 - = f.text_field :color, class: "form-control" + = f.color_field :color, class: "form-control" .form-group.js-toggle-colors-container.hide = f.label :font, "Font Color", class: 'control-label' .col-sm-10 - = f.text_field :font, class: "form-control" + = f.color_field :font, class: "form-control" .form-group = f.label :starts_at, class: 'control-label' .col-sm-10.datetime-controls diff --git a/app/views/devise/mailer/confirmation_instructions.html.haml b/app/views/devise/mailer/confirmation_instructions.html.haml index 086bb8e083d..a508b7537a2 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.haml +++ b/app/views/devise/mailer/confirmation_instructions.html.haml @@ -1,16 +1,15 @@ -.center - - if @resource.unconfirmed_email.present? - #content - %h2= @resource.unconfirmed_email - %p Click the link below to confirm your email address. - #cta - = link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token) - - else - #content - - if Gitlab.com? - %h2 Thanks for signing up to GitLab! - - else - %h2 Welcome, #{@resource.name}! - %p To get started, click the link below to confirm your account. - #cta - = link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token) +- if @resource.unconfirmed_email.present? + #content + = email_default_heading(@resource.unconfirmed_email) + %p Click the link below to confirm your email address. + #cta + = link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token) +- else + #content + - if Gitlab.com? + = email_default_heading('Thanks for signing up to GitLab!') + - else + = email_default_heading("Welcome, #{@resource.name}!") + %p To get started, click the link below to confirm your account. + #cta + = link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token) diff --git a/app/views/devise/mailer/password_change.html.haml b/app/views/devise/mailer/password_change.html.haml index 3349ee84807..5ec515285f2 100644 --- a/app/views/devise/mailer/password_change.html.haml +++ b/app/views/devise/mailer/password_change.html.haml @@ -1,10 +1,8 @@ -.center - #content - %h2 Hello, #{@resource.name}! - %p - The password for your GitLab account on - #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)} - has successfully been changed. - %p - If you did not initiate this change, please contact your administrator - immediately. += email_default_heading("Hello, #{@resource.name}!") +%p + The password for your GitLab account on + #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)} + has successfully been changed. +%p + If you did not initiate this change, please contact your administrator + immediately. diff --git a/app/views/devise/mailer/reset_password_instructions.html.haml b/app/views/devise/mailer/reset_password_instructions.html.haml index e91c9522520..47e192afa52 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.haml +++ b/app/views/devise/mailer/reset_password_instructions.html.haml @@ -1,12 +1,10 @@ -.center - #content - %h2 Hello, #{@resource.name}! - %p - Someone, hopefully you, has requested to reset the password for your - GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}. - %p - If you did not perform this request, you can safely ignore this email. - %p - Otherwise, click the link below to complete the process. - #cta - = link_to('Reset password', edit_password_url(@resource, reset_password_token: @token)) += email_default_heading("Hello, #{@resource.name}!") +%p + Someone, hopefully you, has requested to reset the password for your + GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}. +%p + If you did not perform this request, you can safely ignore this email. +%p + Otherwise, click the link below to complete the process. +#cta + = link_to('Reset password', edit_password_url(@resource, reset_password_token: @token)) diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml index 9990d1ccac6..79e3a35cc9a 100644 --- a/app/views/devise/mailer/unlock_instructions.html.haml +++ b/app/views/devise/mailer/unlock_instructions.html.haml @@ -1,9 +1,8 @@ -.center - #content - %h2 Hello, #{@resource.name}! - %p - Your GitLab account has been locked due to an excessive amount of unsuccessful - sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)} - or you may click the link below to unlock now. - #cta - = link_to('Unlock account', unlock_url(@resource, unlock_token: @token)) +#content + = email_default_heading("Hello, #{@resource.name}!") + %p + Your GitLab account has been locked due to an excessive amount of unsuccessful + sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)} + or you may click the link below to unlock now. + #cta + = link_to('Unlock account', unlock_url(@resource, unlock_token: @token)) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 70042dee20f..4a41be972da 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -2,8 +2,9 @@ - blob = discussion.blob .diff-file.file-holder - .js-file-title.file-title - = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false + .js-file-title.file-title.file-title-flex-parent + .file-header-content + = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false .diff-content.code.js-syntax-highlight %table diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 31d0e589c26..c25eae63eec 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -1,4 +1,9 @@ %div +- if current_application_settings.help_page_text.present? + = markdown_field(current_application_settings, :help_page_text) + %hr + +- unless current_application_settings.help_page_hide_commercial_content? %h1 GitLab Community Edition @@ -18,13 +23,9 @@ Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}. - - if current_application_settings.help_page_text.present? - %hr - = markdown_field(current_application_settings, :help_page_text) - -%hr + %hr -.row +.row.prepend-top-default .col-md-8 .documentation-index = markdown(@help_index) @@ -33,8 +34,9 @@ .panel-heading Quick help %ul.well-list - %li= link_to 'See our website for getting help', promo_url + '/getting-help/' + %li= link_to 'See our website for getting help', support_url %li= link_to 'Use the search bar on the top of this page', '#', onclick: 'Shortcuts.focusSearch(event)' %li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.toggleHelp()' - %li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/' - %li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare' + - unless current_application_settings.help_page_hide_commercial_content? + %li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/' + %li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare' diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index f6ebd76af9d..c07c148a12a 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ - page_title @path.split("/").reverse.map(&:humanize) -.documentation.wiki +.documentation.wiki.prepend-top-default = markdown @markdown diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml index 3a7e0929c16..bcd2f03e83c 100644 --- a/app/views/layouts/_broadcast.html.haml +++ b/app/views/layouts/_broadcast.html.haml @@ -1 +1,2 @@ -= broadcast_message +- BroadcastMessage.current.each do |message| + = broadcast_message(message) diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml new file mode 100644 index 00000000000..983ed22a506 --- /dev/null +++ b/app/views/layouts/_mailer.html.haml @@ -0,0 +1,74 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +%html{ lang: "en" } + %head + %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ + %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ + %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ + %title= message.subject + :css + /* CLIENT-SPECIFIC STYLES */ + body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } + img { -ms-interpolation-mode: bicubic; } + + /* iOS BLUE LINKS */ + a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } + + /* ANDROID MARGIN HACK */ + body { margin:0 !important; } + div[style*="margin: 16px 0"] { margin:0 !important; } + + @media only screen and (max-width: 639px) { + body, #body { + min-width: 320px !important; + } + table.wrapper { + width: 100% !important; + min-width: 320px !important; + } + table.wrapper > tbody > tr > td { + border-left: 0 !important; + border-right: 0 !important; + border-radius: 0 !important; + padding-left: 10px !important; + padding-right: 10px !important; + } + } + %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } + %tbody + %tr.line + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  + %tr.header + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + = header_logo + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } + %tbody + = yield + + %tr.footer + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ + %div + %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications + · + %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help + %div + You're receiving this email because of your account on + = succeed "." do + %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host + + = yield :additional_footer diff --git a/app/views/layouts/devise_mailer.html.haml b/app/views/layouts/devise_mailer.html.haml deleted file mode 100644 index e1e1f9ae516..00000000000 --- a/app/views/layouts/devise_mailer.html.haml +++ /dev/null @@ -1,34 +0,0 @@ -!!! 5 -%html - %head - %meta{ content: 'text/html; charset=UTF-8', 'http-equiv'=> 'Content-Type' } - = stylesheet_link_tag 'mailers/devise' - - %body - %table#wrapper - %tr - %td - %table#header - %td{ valign: "top" } - = image_tag('mailers/gitlab_header_logo.png', id: 'logo', alt: 'GitLab Wordmark') - - %table#body - %tr - %td#body-container - = yield - - - if Gitlab.com? - %table#footer - %tr - %td#tanuki - = image_tag('mailers/gitlab_tanuki_2x.png', alt: 'GitLab Logo') - %tr - %td#tagline - Everyone can contribute - %tr - %td#social - = link_to 'Blog', 'https://about.gitlab.com/blog/' - = link_to 'Twitter', 'https://twitter.com/gitlab' - = link_to 'Facebook', 'https://www.facebook.com/gitlab/' - = link_to 'YouTube', 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg' - = link_to 'LinkedIn', 'https://www.linkedin.com/company/gitlab-com' diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml index 53268cc22f8..28dcbce7183 100644 --- a/app/views/layouts/mailer.html.haml +++ b/app/views/layouts/mailer.html.haml @@ -1,72 +1 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -%html{ lang: "en" } - %head - %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ - %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ - %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ - %title= message.subject - :css - /* CLIENT-SPECIFIC STYLES */ - body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } - table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } - img { -ms-interpolation-mode: bicubic; } - - /* iOS BLUE LINKS */ - a[x-apple-data-detectors] { - color: inherit !important; - text-decoration: none !important; - font-size: inherit !important; - font-family: inherit !important; - font-weight: inherit !important; - line-height: inherit !important; - } - - /* ANDROID MARGIN HACK */ - body { margin:0 !important; } - div[style*="margin: 16px 0"] { margin:0 !important; } - - @media only screen and (max-width: 639px) { - body, #body { - min-width: 320px !important; - } - table.wrapper { - width: 100% !important; - min-width: 320px !important; - } - table.wrapper > tbody > tr > td { - border-left: 0 !important; - border-right: 0 !important; - border-radius: 0 !important; - padding-left: 10px !important; - padding-right: 10px !important; - } - } - %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } - %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } - %tbody - %tr.line - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  - %tr.header - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } - = header_logo - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } - %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } - %tbody - = yield - - %tr.footer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } - %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ - %div - %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications - · - %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help - %div - You're receiving this email because of your account on - = succeed "." do - %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host += render 'layouts/mailer' diff --git a/app/views/layouts/mailer/devise.html.haml b/app/views/layouts/mailer/devise.html.haml new file mode 100644 index 00000000000..054b2c2fa26 --- /dev/null +++ b/app/views/layouts/mailer/devise.html.haml @@ -0,0 +1,21 @@ +- if Gitlab.com? + - content_for :additional_footer do + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %div + Everyone can contribute + %div + = link_to 'Blog', 'https://about.gitlab.com/blog/', style: "color:#3777b0;text-decoration:none;" + · + = link_to 'Twitter', 'https://twitter.com/gitlab', style: "color:#3777b0;text-decoration:none;" + · + = link_to 'Facebook', 'https://www.facebook.com/gitlab/', style: "color:#3777b0;text-decoration:none;" + · + = link_to 'YouTube', 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg', style: "color:#3777b0;text-decoration:none;" + · + = link_to 'LinkedIn', 'https://www.linkedin.com/company/gitlab-com', style: "color:#3777b0;text-decoration:none;" + += render layout: 'layouts/mailer' do + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" } + = yield diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index fcfd350f0da..15672289c65 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -42,10 +42,17 @@ - if current_user.ldap_user? Some options are unavailable for LDAP accounts .col-lg-9 - .form-group - = f.label :name, class: "label-light" - = f.text_field :name, class: "form-control", required: true - %span.help-block Enter your name, so people you know can recognize you. + .row + .form-group.col-md-9 + = f.label :name, class: "label-light" + = f.text_field :name, class: "form-control", required: true + %span.help-block Enter your name, so people you know can recognize you. + + .form-group.col-md-3 + = f.label :id, class: 'label-light' do + User ID + = f.text_field :id, class: 'form-control', readonly: true + .form-group = f.label :email, class: "label-light" diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml index 65fc0a36ca9..4026b9e3c46 100644 --- a/app/views/projects/_visibility_select.html.haml +++ b/app/views/projects/_visibility_select.html.haml @@ -1,5 +1,7 @@ - if can_change_visibility_level?(@project, current_user) - = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select') + .select-wrapper + = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control') + = icon('chevron-down') - else .info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } } = visibility_level_icon(@project.visibility_level) diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index 3b3d08ddd3c..afc40ca4eab 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,10 +1,15 @@ - @gfm_form = true - current_text ||= nil -- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false) +- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true) +- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false) .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' - if defined?(f) && f - = f.text_area attr, class: classes, placeholder: placeholder, data: { supports_slash_commands: supports_slash_commands } + = f.text_area attr, + class: classes, + placeholder: placeholder, + data: { supports_quick_actions: supports_quick_actions, + supports_autocomplete: supports_autocomplete } - else = text_area_tag attr, current_text, class: classes, placeholder: placeholder %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" } diff --git a/app/views/projects/blame/_age_map_legend.html.haml b/app/views/projects/blame/_age_map_legend.html.haml new file mode 100644 index 00000000000..533dc20ffb3 --- /dev/null +++ b/app/views/projects/blame/_age_map_legend.html.haml @@ -0,0 +1,12 @@ +%span.left-label Newer +%span.legend-box.legend-box-0 +%span.legend-box.legend-box-1 +%span.legend-box.legend-box-2 +%span.legend-box.legend-box-3 +%span.legend-box.legend-box-4 +%span.legend-box.legend-box-5 +%span.legend-box.legend-box-6 +%span.legend-box.legend-box-7 +%span.legend-box.legend-box-8 +%span.legend-box.legend-box-9 +%span.right-label Older diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index a6ee2b2f7b8..ce937ee1842 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- project_duration = age_map_duration(@blame_groups, @project) - page_title "Annotate", @blob.path, @ref = render "projects/commits/head" @@ -8,15 +9,16 @@ .file-holder = render "projects/blob/header", blob: @blob, blame: true - + .file-blame-legend + = render 'age_map_legend' .table-responsive.file-content.blame.code.js-syntax-highlight %table - current_line = 1 - @blame_groups.each do |blame_group| %tr - %td.blame-commit + - commit = blame_group[:commit] + %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) } .commit - - commit = blame_group[:commit] = author_avatar(commit, size: 36) .commit-row-title %strong diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index e14885f264b..24314e03b46 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -9,8 +9,10 @@ .dropzone .dropzone-previews.blob-upload-dropzone-previews %p.dz-message.light - Attach a file by drag & drop or - = link_to 'click to upload', '#', class: "markdown-selector" + - upload_link = link_to n_('UploadLink|click to upload'), '#', class: "markdown-selector" + - dropzone_text = _('Attach a file by drag & drop or %{upload_link}') % { upload_link: upload_link } + #{ dropzone_text.html_safe } + %br .dropzone-alerts.alert.alert-danger.data{ style: "display:none" } @@ -18,7 +20,7 @@ .form-actions = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all' - = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal" - unless can?(current_user, :push_code, @project) .inline.prepend-left-10 diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 55c4d51be14..539ee087b14 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -9,11 +9,11 @@ %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")', data: { container: "body", placement: "bottom" } } {{ list.title }} - .board-issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' } - %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } + .issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' } + %span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } {{ list.issuesSize }} - if can?(current_user, :admin_issue, @project) - %button.btn.btn-small.btn-default.pull-right.has-tooltip.js-no-trigger-collapse{ type: "button", + %button.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button", "@click" => "showNewIssueForm", "v-if" => 'list.type !== "closed"', "aria-label" => "New issue", diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 3cf91bf07f7..a73ddd5eb33 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -2,7 +2,7 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) .project-action-button.dropdown.inline> - %button.btn.has-tooltip{ title: 'Download', 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') } + %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') } = icon('download') = icon("caret-down") %span.sr-only= _('Select Archive Format') diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 312c349da3a..960b57a8008 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,6 +1,6 @@ - if current_user .project-action-button.dropdown.inline - %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: 'Create new...', 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => 'Create new...' } + %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') } = icon('plus') = icon("caret-down") %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 7a08bb37494..42f8c75f57b 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -4,11 +4,15 @@ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do = custom_icon('icon_fork') %span= s_('GoToYourFork|Fork') + - elsif !current_user.can_create_project? + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do + = custom_icon('icon_fork') + %span= s_('CreateNewFork|Fork') - else = link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do = custom_icon('icon_fork') %span= s_('CreateNewFork|Fork') .count-with-arrow %span.arrow - = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Forks', @project.forks_count), class: 'count' do + = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do = @project.forks_count diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 281d823da52..208d4908721 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -1,35 +1,36 @@ - case type.to_s - when 'revert' - - label = 'Revert' - - branch_label = 'Revert in branch' + - label = s_('ChangeTypeAction|Revert') + - branch_label = s_('ChangeTypeActionLabel|Revert in branch') + - revert_merge_request = _('Revert this merge request') + - revert_commit = _('Revert this commit') + - title = commit.merged_merge_request(current_user) ? revert_merge_request : revert_commit - when 'cherry-pick' - - label = 'Cherry-pick' - - branch_label = 'Pick into branch' + - label = s_('ChangeTypeAction|Cherry-pick') + - branch_label = s_('ChangeTypeActionLabel|Pick into branch') + - title = commit.merged_merge_request(current_user) ? _('Cherry-pick this merge request') : _('Cherry-pick this commit') .modal{ id: "modal-#{type}-commit" } .modal-dialog .modal-content .modal-header %a.close{ href: "#", "data-dismiss" => "modal" } × - %h3.page-title== #{label} this #{commit.change_type_title(current_user)} + %h3.page-title= title .modal-body = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do .form-group.branch = label_tag 'start_branch', branch_label, class: 'control-label' .col-sm-10 = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch' - = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) + = dropdown_tag(@project.default_branch, options: { title: n_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: n_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) - if can?(current_user, :push_code, @project) - .checkbox - = label_tag do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil - Start a <strong>new merge request</strong> with these changes + = render 'shared/new_merge_request_checkbox' - else = hidden_field_tag 'create_merge_request', 1, id: nil .form-actions = submit_tag label, class: 'btn btn-create' - = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal" - unless can?(current_user, :push_code, @project) .inline.prepend-left-10 diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index aab50310234..7fe44816bae 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,17 +1,17 @@ .page-content-header .header-main-content %strong - Commit + #{ s_('CommitBoxTitle|Commit') } %span.commit-sha= @commit.short_id - = clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard") + = clipboard_button(text: @commit.id, title: _("Copy commit SHA to clipboard")) %span.hidden-xs authored #{time_ago_with_tooltip(@commit.authored_date)} - %span by + %span= s_('ByAuthor|by') = author_avatar(@commit, size: 24) %strong = commit_author_link(@commit, avatar: true, size: 24) - if @commit.different_committer? - %span.light Committed by + %span.light= _('Committed by') %strong = commit_committer_link(@commit, avatar: true, size: 24) #{time_ago_with_tooltip(@commit.committed_date)} @@ -22,15 +22,15 @@ = icon('comment') = @notes_count = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do - Browse files + #{ _('Browse files') } .dropdown.inline %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } - %span Options + %span= _('Options') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li.visible-xs-block.visible-sm-block = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do - Browse Files + _('Browse Files') - unless @commit.has_been_reverted?(current_user) %li.clearfix = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) @@ -38,13 +38,13 @@ = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) - if can_collaborate_with_project? %li.clearfix - = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit) + = link_to s_("CreateTag|Tag"), new_namespace_project_tag_path(@project.namespace, @project, ref: @commit) %li.divider %li.dropdown-header - Download + #{ _('Download') } - unless @commit.parents.length > 1 - %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) - %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) + %li= link_to s_("DownloadCommit|Email Patches"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) + %li= link_to s_("DownloadCommit|Plain Diff"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) .commit-box %h3.commit-title @@ -57,7 +57,7 @@ .well-segment.branch-info .icon-container.commit-icon = custom_icon("icon_commit") - %span.cgray= pluralize(@commit.parents.count, "parent") + %span.cgray= n_('parent', 'parents', @commit.parents.count) - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha" %span.commit-info.branches @@ -69,11 +69,11 @@ .status-icon-container{ class: "ci-status-icon-#{@commit.status}" } = link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do = ci_icon_for_status(last_pipeline.status) - Pipeline + #{ _('Pipeline') } = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) = ci_label_for_status(last_pipeline.status) - if last_pipeline.stages_count.nonzero? - with #{"stage".pluralize(last_pipeline.stages_count)} + #{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) } .mr-widget-pipeline-graph = render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph' in diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7a03c3561af..11de6915961 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -30,9 +30,11 @@ %pre.commit-row-description.js-toggle-content = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author)) .commiter - = commit_author_link(commit, avatar: false, size: 24) - #{ _('committed') } - #{time_ago_with_tooltip(commit.committed_date)} + - commit_author_link = commit_author_link(commit, avatar: false, size: 24) + - commit_timeago = time_ago_with_tooltip(commit.committed_date) + - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago } + #{ commit_text.html_safe } + .commit-actions.flex-row.hidden-xs - if commit.status(ref) diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index d956cb2cc1a..9b2ec9ae41c 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -20,7 +20,7 @@ .table-mobile-header{ role: 'rowheader' } Created %span.table-mobile-content= time_ago_with_tooltip(deployment.created_at) - .table-section.section-20.environments-actions.table-button-footer{ role: 'gridcell' } - .btn-group.environment-action-buttons + .table-section.section-20.table-button-footer{ role: 'gridcell' } + .btn-group.table-action-button = render 'projects/deployments/actions', deployment: deployment = render 'projects/deployments/rollback', deployment: deployment diff --git a/app/views/projects/diffs/_collapsed.html.haml b/app/views/projects/diffs/_collapsed.html.haml new file mode 100644 index 00000000000..8772bd4705f --- /dev/null +++ b/app/views/projects/diffs/_collapsed.html.haml @@ -0,0 +1,5 @@ +- diff_file = viewer.diff_file +- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier)) +.nothing-here-block.diff-collapsed{ data: { diff_for_path: url } } + This diff is collapsed. + %a.click-to-expand Click to expand it. diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index ec1c434a4b8..68f74f702ea 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -1,27 +1,2 @@ -- blob = diff_file.blob - .diff-content - - if diff_file.too_large? - .nothing-here-block This diff could not be displayed because it is too large. - - elsif blob.truncated? - .nothing-here-block The file could not be displayed because it is too large. - - elsif blob.readable_text? - - if !diff_file.diffable? - .nothing-here-block This diff was suppressed by a .gitattributes entry. - - elsif diff_file.collapsed? - - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier)) - .nothing-here-block.diff-collapsed{ data: { diff_for_path: url } } - This diff is collapsed. - %a.click-to-expand - Click to expand it. - - elsif diff_file.diff_lines.length > 0 - = render "projects/diffs/viewers/text", diff_file: diff_file - - else - - if diff_file.mode_changed? - .nothing-here-block File mode changed - - elsif diff_file.renamed_file? - .nothing-here-block File moved - - elsif blob.image? - = render "projects/diffs/viewers/image", diff_file: diff_file - - else - .nothing-here-block No preview for this file type + = render 'projects/diffs/viewer', viewer: diff_file.rich_viewer || diff_file.simple_viewer diff --git a/app/views/projects/diffs/_render_error.html.haml b/app/views/projects/diffs/_render_error.html.haml new file mode 100644 index 00000000000..47a9ac3ee6b --- /dev/null +++ b/app/views/projects/diffs/_render_error.html.haml @@ -0,0 +1,6 @@ +.nothing-here-block + This #{viewer.switcher_title} could not be displayed because #{diff_render_error_reason(viewer)}. + + You can + = diff_render_error_options(viewer).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ').html_safe + instead. diff --git a/app/views/projects/diffs/_viewer.html.haml b/app/views/projects/diffs/_viewer.html.haml new file mode 100644 index 00000000000..5c4d1760871 --- /dev/null +++ b/app/views/projects/diffs/_viewer.html.haml @@ -0,0 +1,16 @@ +- hidden = local_assigns.fetch(:hidden, false) + +.diff-viewer{ data: { type: viewer.type }, class: ('hidden' if hidden) } + - if viewer.render_error + = render 'projects/diffs/render_error', viewer: viewer + - elsif viewer.collapsed? + = render 'projects/diffs/collapsed', viewer: viewer + - else + - viewer.prepare! + + -# In the rare case where the first kilobyte of the file looks like text, + -# but the file turns out to actually be binary after loading all data, + -# we fall back on the binary No Preview viewer. + - viewer = DiffViewer::NoPreview.new(viewer.diff_file) if viewer.binary_detected_after_load? + + = render viewer.partial_path, viewer: viewer diff --git a/app/views/projects/diffs/viewers/_added.html.haml b/app/views/projects/diffs/viewers/_added.html.haml new file mode 100644 index 00000000000..8004fe16688 --- /dev/null +++ b/app/views/projects/diffs/viewers/_added.html.haml @@ -0,0 +1,2 @@ +.nothing-here-block + File added diff --git a/app/views/projects/diffs/viewers/_deleted.html.haml b/app/views/projects/diffs/viewers/_deleted.html.haml new file mode 100644 index 00000000000..0ac7b4ca8f6 --- /dev/null +++ b/app/views/projects/diffs/viewers/_deleted.html.haml @@ -0,0 +1,2 @@ +.nothing-here-block + File deleted diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index ea75373581e..19d08181223 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -1,3 +1,4 @@ +- diff_file = viewer.diff_file - blob = diff_file.blob - old_blob = diff_file.old_blob - blob_raw_path = diff_file_blob_raw_path(diff_file) diff --git a/app/views/projects/diffs/viewers/_mode_changed.html.haml b/app/views/projects/diffs/viewers/_mode_changed.html.haml new file mode 100644 index 00000000000..69bc96bbdad --- /dev/null +++ b/app/views/projects/diffs/viewers/_mode_changed.html.haml @@ -0,0 +1,3 @@ +- diff_file = viewer.diff_file +.nothing-here-block + File mode changed from #{diff_file.a_mode} to #{diff_file.b_mode} diff --git a/app/views/projects/diffs/viewers/_no_preview.html.haml b/app/views/projects/diffs/viewers/_no_preview.html.haml new file mode 100644 index 00000000000..befe070af2b --- /dev/null +++ b/app/views/projects/diffs/viewers/_no_preview.html.haml @@ -0,0 +1,2 @@ +.nothing-here-block + No preview for this file type diff --git a/app/views/projects/diffs/viewers/_not_diffable.html.haml b/app/views/projects/diffs/viewers/_not_diffable.html.haml new file mode 100644 index 00000000000..b2c677ec59c --- /dev/null +++ b/app/views/projects/diffs/viewers/_not_diffable.html.haml @@ -0,0 +1,2 @@ +.nothing-here-block + This diff was suppressed by a .gitattributes entry. diff --git a/app/views/projects/diffs/viewers/_renamed.html.haml b/app/views/projects/diffs/viewers/_renamed.html.haml new file mode 100644 index 00000000000..ef05ee38d8d --- /dev/null +++ b/app/views/projects/diffs/viewers/_renamed.html.haml @@ -0,0 +1,2 @@ +.nothing-here-block + File moved diff --git a/app/views/projects/diffs/viewers/_text.html.haml b/app/views/projects/diffs/viewers/_text.html.haml index 120d3540223..509e68598c9 100644 --- a/app/views/projects/diffs/viewers/_text.html.haml +++ b/app/views/projects/diffs/viewers/_text.html.haml @@ -1,5 +1,5 @@ +- diff_file = viewer.diff_file - blob = diff_file.blob -- blob.load_all_data! - total_lines = blob.lines.size - total_lines -= 1 if total_lines > 0 && blob.lines.last.blank? - if diff_view == :parallel diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index c3dab68cea5..296e37e20e6 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -99,9 +99,9 @@ Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select', data: { field: 'lfs_enabled' } - - + .select-wrapper + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' } + = icon('chevron-down') - if Gitlab.config.registry.enabled .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) } .checkbox diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml index da65157a10b..35b7d1b920c 100644 --- a/app/views/projects/issues/_issue_by_email.html.haml +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -20,7 +20,7 @@ %p The subject will be used as the title of the new issue, and the message will be the description. - = link_to 'Slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1 + = link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1 and styling with = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1 are supported. diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 8b9e6e57ec4..93e8a4e385c 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -72,6 +72,7 @@ .title %span{ class: "ci-status-icon-#{@build.pipeline.status}" } = ci_icon_for_status(@build.pipeline.status) + Pipeline = link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit' from = link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit' diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 62c9748c510..e675e1830d0 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,7 +1,7 @@ .form-horizontal.resolve-conflicts-form .form-group %label.col-sm-2.control-label{ "for" => "commit-message" } - Commit message + #{ _('Commit message') } .col-sm-10 .commit-message-container .max-width-marker diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index 25ae4e0e18f..467f8844e33 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -7,7 +7,7 @@ .form-group .col-md-9 = f.label :description, _('Description'), class: 'label-light' - = f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: _('PipelineSchedules|Provide a short description for this pipeline') + = f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: s_('PipelineSchedules|Provide a short description for this pipeline') .form-group .col-md-9 = f.label :cron, _('Interval Pattern'), class: 'label-light' @@ -15,19 +15,19 @@ .form-group .col-md-9 = f.label :cron_timezone, _('Cron Timezone'), class: 'label-light' - = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: _("Filter"), data: { data: timezone_data } } ) + = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: _("OfSearchInADropdown|Filter"), data: { data: timezone_data } } ) = f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true .form-group .col-md-9 = f.label :ref, _('Target Branch'), class: 'label-light' - = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown git-revision-dropdown-toggle', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) + = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true .form-group .col-md-9 - = f.label :active, _('PipelineSchedules|Activated'), class: 'label-light' + = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light' %div = f.check_box :active, required: false, value: @schedule.active? - Active + = _('Active') .footer-block.row-content-block = f.submit _('Save pipeline schedule'), class: 'btn btn-create', tabindex: 3 = link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index 2d3344a4aaf..966d6cd8495 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -13,12 +13,12 @@ = ci_icon_for_status(pipeline_schedule.last_pipeline.status) %span ##{pipeline_schedule.last_pipeline.id} - else - = _("PipelineSchedules|None") + = s_("PipelineSchedules|None") %td.next-run-cell - if pipeline_schedule.active? = time_ago_with_tooltip(pipeline_schedule.real_next_run) - else - = _("PipelineSchedules|Inactive") + = s_("PipelineSchedules|Inactive") %td - if pipeline_schedule.owner = image_tag avatar_icon(pipeline_schedule.owner, 20), class: "avatar s20" diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 4a96ee652d2..c296152e54f 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -14,7 +14,7 @@ .nav-controls = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do - %span New schedule + %span= _('New schedule') - if @schedules.present? %ul.content-list diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 247c4bdbe2d..8bf2246662a 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -6,7 +6,9 @@ = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite") .form-group = label_tag :access_level, "Choose a role permission", class: "label-light" - = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select" + .select-wrapper + = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control" + = icon('chevron-down') .help-block.append-bottom-10 = link_to "Read more", help_page_path("user/permissions"), class: "vlink" about role permissions diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml index b7cc8dd7062..643569db646 100644 --- a/app/views/projects/project_members/_new_shared_group.html.haml +++ b/app/views/projects/project_members/_new_shared_group.html.haml @@ -8,7 +8,7 @@ = label_tag :link_group_access, "Max access level", class: "label-light" .select-wrapper = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" - = icon('caret-down') + = icon('chevron-down') .help-block.append-bottom-10 = link_to "Read more", help_page_path("user/permissions"), class: "vlink" about role permissions diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 6cb7c1e9c4d..c10b3004bc3 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -5,13 +5,13 @@ = f.hidden_field :title, value: @page.title .form-group - = f.label :format, class: 'control-label' - .col-sm-10 + .col-sm-12= f.label :format, class: 'control-label-full-width' + .col-sm-12 = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control" .form-group - = f.label :content, class: 'control-label' - .col-sm-10 + .col-sm-12= f.label :content, class: 'control-label-full-width' + .col-sm-12 = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...' = render 'shared/notes/hints' @@ -29,8 +29,8 @@ = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown") .form-group - = f.label :commit_message, class: 'control-label' - .col-sm-10= f.text_field :message, class: 'form-control', rows: 18, value: commit_message + .col-sm-12= f.label :commit_message, class: 'control-label-full-width' + .col-sm-12= f.text_field :message, class: 'form-control', rows: 18, value: commit_message .form-actions - if @page && @page.persisted? diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index ba47574563d..1e553940593 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -1,21 +1,18 @@ -- @no_container = true - -%div{ class: container_class } - #modal-new-wiki.modal - .modal-dialog - .modal-content - .modal-header - %a.close{ href: "#", "data-dismiss" => "modal" } × - %h3.page-title New Wiki Page - .modal-body - %form.new-wiki-page - .form-group - = label_tag :new_wiki_path do - %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true - %span.new-wiki-page-slug-tip - = icon('lightbulb-o') - Tip: You can specify the full path for the new file. - We will automatically create any missing directories. - .form-actions - = button_tag 'Create page', class: 'build-new-wiki btn btn-create' +#modal-new-wiki.modal + .modal-dialog + .modal-content + .modal-header + %a.close{ href: "#", "data-dismiss" => "modal" } × + %h3.page-title New Wiki Page + .modal-body + %form.new-wiki-page + .form-group + = label_tag :new_wiki_path do + %span Page slug + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true + %span.new-wiki-page-slug-tip + = icon('lightbulb-o') + Tip: You can specify the full path for the new file. + We will automatically create any missing directories. + .form-actions + = button_tag 'Create page', class: 'build-new-wiki btn btn-create' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index b995d08cd02..fbe192a40ec 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,35 +1,34 @@ -- @no_container = true +- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - page_title "Edit", @page.title.capitalize, "Wiki" -%div{ class: container_class } - .wiki-page-header.has-sidebar-toggle - %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } - = icon('angle-double-left') +.wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') - .nav-text - %h2.wiki-page-title + .nav-text + %h2.wiki-page-title + - if @page.persisted? + = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + - else + = @page.title.capitalize + %span.light + · - if @page.persisted? - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + Edit Page - else - = @page.title.capitalize - %span.light - · - - if @page.persisted? - Edit Page - - else - Create Page + Create Page - .nav-controls - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New page - - if @page.persisted? - = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do - Page history - - if can?(current_user, :admin_wiki, @project) - = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do - Delete + .nav-controls + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New page + - if @page.persisted? + = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do + Page history + - if can?(current_user, :admin_wiki, @project) + = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do + Delete - = render 'form' += render 'form' = render 'sidebar' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 68862206248..e64dd6085fe 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,43 +1,42 @@ -- @no_container = true +- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - page_title "Git Access", "Wiki" -%div{ class: container_class } - .wiki-page-header.has-sidebar-toggle - %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } - = icon('angle-double-left') +.wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') - .git-access-header - Clone repository - %strong= @project_wiki.path_with_namespace + .git-access-header + Clone repository + %strong= @project_wiki.path_with_namespace - = render "shared/clone_panel", project: @project_wiki + = render "shared/clone_panel", project: @project_wiki - .wiki-git-access - %h3 Install Gollum - %pre.dark - :preserve - gem install gollum - %p - It is recommended to install - %code github-markdown - so that GFM features render locally: - %pre.dark - :preserve - gem install github-markdown +.wiki-git-access + %h3 Install Gollum + %pre.dark + :preserve + gem install gollum + %p + It is recommended to install + %code github-markdown + so that GFM features render locally: + %pre.dark + :preserve + gem install github-markdown - %h3 Clone your wiki - %pre.dark - :preserve - git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')} - cd #{h @project_wiki.path} + %h3 Clone your wiki + %pre.dark + :preserve + git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')} + cd #{h @project_wiki.path} - %h3 Start Gollum and edit locally - %pre.dark - :preserve - gollum - == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin - >> Thin web server (v1.5.0 codename Knife) - >> Maximum connections set to 1024 - >> Listening on 0.0.0.0:4567, CTRL+C to stop + %h3 Start Gollum and edit locally + %pre.dark + :preserve + gollum + == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin + >> Thin web server (v1.5.0 codename Knife) + >> Maximum connections set to 1024 + >> Listening on 0.0.0.0:4567, CTRL+C to stop = render 'sidebar' diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index dd7213622c1..0e47e2a5fa3 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,42 +1,41 @@ - page_title "History", @page.title.capitalize, "Wiki" -%div{ class: container_class } - .wiki-page-header.has-sidebar-toggle - %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } - = icon('angle-double-left') +.wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') - .nav-text - %h2.wiki-page-title - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - %span.light - · - History + .nav-text + %h2.wiki-page-title + = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + %span.light + · + History - .table-holder - %table.table - %thead +.table-holder + %table.table + %thead + %tr + %th Page version + %th Author + %th Commit Message + %th Last updated + %th Format + %tbody + - @page.versions.each_with_index do |version, index| + - commit = version %tr - %th Page version - %th Author - %th Commit Message - %th Last updated - %th Format - %tbody - - @page.versions.each_with_index do |version, index| - - commit = version - %tr - %td - = link_to project_wiki_path_with_version(@project, @page, - commit.id, index == 0) do - = truncate_sha(commit.id) - %td - = commit.author.name - %td - = commit.message - %td - #{time_ago_with_tooltip(version.authored_date)} - %td - %strong - = @page.page.wiki.page(@page.page.name, commit.id).try(:format) + %td + = link_to project_wiki_path_with_version(@project, @page, + commit.id, index == 0) do + = truncate_sha(commit.id) + %td + = commit.author.name + %td + = commit.message + %td + #{time_ago_with_tooltip(version.authored_date)} + %td + %strong + = @page.page.wiki.page(@page.page.name, commit.id).try(:format) = render 'sidebar' diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index c00967546aa..f003ff6b63f 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,32 +1,31 @@ -- @no_container = true +- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - page_title @page.title.capitalize, "Wiki" -%div{ class: container_class } - .wiki-page-header.has-sidebar-toggle - %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } - = icon('angle-double-left') +.wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') - .wiki-breadcrumb - %span= breadcrumb(@page.slug) + .wiki-breadcrumb + %span= breadcrumb(@page.slug) - .nav-text - %h2.wiki-page-title= @page.title.capitalize - %span.wiki-last-edit-by - Last edited by - %strong - #{@page.commit.author.name} - #{time_ago_with_tooltip(@page.commit.authored_date)} + .nav-text + %h2.wiki-page-title= @page.title.capitalize + %span.wiki-last-edit-by + Last edited by + %strong + #{@page.commit.author.name} + #{time_ago_with_tooltip(@page.commit.authored_date)} - .nav-controls - = render 'main_links' + .nav-controls + = render 'main_links' - - if @page.historical? - .warning_message - This is an old version of this page. - You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. +- if @page.historical? + .warning_message + This is an old version of this page. + You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. - .wiki-holder.prepend-top-default.append-bottom-default - .wiki - = render_wiki_content(@page) +.wiki-holder.prepend-top-default.append-bottom-default + .wiki + = render_wiki_content(@page) = render 'sidebar' diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 0aad4d0714f..75704eda361 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -19,7 +19,7 @@ = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } .input-group-btn - = clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard")) + = clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "btn-default btn-clipboard") :javascript $('ul.clone-options-dropdown a').on('click',function(e){ diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 4b98ff88241..2329de9e11f 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -2,7 +2,7 @@ - nonce = SecureRandom.hex - descriptions = local_assigns.slice(:message_with_description, :message_without_description) = label_tag "commit_message-#{nonce}", class: 'control-label' do - Commit message + #{ _('Commit message') } .col-sm-10 .commit-message-container .max-width-marker diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml index 25a56f84ec5..0a4a24ae807 100644 --- a/app/views/shared/_new_commit_form.html.haml +++ b/app/views/shared/_new_commit_form.html.haml @@ -5,16 +5,12 @@ - else - if can?(current_user, :push_code, @project) .form-group.branch - = label_tag 'branch_name', 'Target branch', class: 'control-label' + = label_tag 'branch_name', _('Target Branch'), class: 'control-label' .col-sm-10 = text_field_tag 'branch_name', @branch_name || tree_edit_branch, required: true, class: "form-control js-branch-name ref-name" .js-create-merge-request-container - .checkbox - - nonce = SecureRandom.hex - = label_tag "create_merge_request-#{nonce}" do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" - Start a <strong>new merge request</strong> with these changes + = render 'shared/new_merge_request_checkbox' - else = hidden_field_tag 'branch_name', @branch_name || tree_edit_branch = hidden_field_tag 'create_merge_request', 1 diff --git a/app/views/shared/_new_merge_request_checkbox.html.haml b/app/views/shared/_new_merge_request_checkbox.html.haml new file mode 100644 index 00000000000..133c31f09c4 --- /dev/null +++ b/app/views/shared/_new_merge_request_checkbox.html.haml @@ -0,0 +1,8 @@ +.checkbox + - nonce = SecureRandom.hex + = label_tag "create_merge_request-#{nonce}" do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" + - translation_variables = { new_merge_request: "<strong>#{_('new merge request')}</strong>" } + - translation = _('Start a %{new_merge_request} with these changes') % translation_variables + #{ translation.html_safe } + diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml index 307d4919224..f65bb6a29e6 100644 --- a/app/views/shared/form_elements/_description.html.haml +++ b/app/views/shared/form_elements/_description.html.haml @@ -2,10 +2,10 @@ - model = local_assigns.fetch(:model) - form = local_assigns.fetch(:form) -- supports_slash_commands = model.new_record? +- supports_quick_actions = model.new_record? -- if supports_slash_commands - - preview_url = preview_markdown_path(project, slash_commands_target_type: model.class.name) +- if supports_quick_actions + - preview_url = preview_markdown_path(project, quick_actions_target_type: model.class.name) - else - preview_url = preview_markdown_path(project) @@ -17,7 +17,7 @@ = render 'projects/zen', f: form, attr: :description, classes: 'note-textarea', placeholder: "Write a comment or drag your files here...", - supports_slash_commands: supports_slash_commands - = render 'shared/notes/hints', supports_slash_commands: supports_slash_commands + supports_quick_actions: supports_quick_actions + = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions .clearfix .error-alert diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index 271150ed318..bfa91629e1e 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -3,7 +3,8 @@ - return unless issuable.is_a?(MergeRequest) - return if issuable.closed_without_fork? --# This check is duplicated below, to avoid conflicts with EE. +-# This check is duplicated below to avoid CE -> EE merge conflicts. +-# This comment and the following line should only exist in CE. - return unless issuable.can_remove_source_branch?(current_user) .form-group diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index 8af3bd597c5..7175e275f95 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -8,11 +8,11 @@ = title - if show_counter .counter - = number_with_delimiter(issuables.size) + = number_with_delimiter(issuables.length) - class_prefix = dom_class(issuables).pluralize - %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id } + %ul{ class: "well-list milestone-#{class_prefix}-list", id: "#{class_prefix}-list-#{id}" } = render partial: 'shared/milestones/issuable', - collection: issuables.order_position_asc, + collection: issuables, as: :issuable, locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name } diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index 6a6d817b344..4de8a6cb15f 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -31,12 +31,12 @@ .tab-content.milestone-content - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project) .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } } - = render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user).include_associations, show_project_name: show_project_name, show_full_project_name: show_full_project_name - .tab-pane#tab-merge-requests{ data: { sort_endpoint: (sort_merge_requests_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } } + = render 'shared/milestones/issues_tab', issues: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name + .tab-pane#tab-merge-requests -# loaded async = render "shared/milestones/tab_loading" - else - .tab-pane.active#tab-merge-requests{ data: { sort_endpoint: (sort_merge_requests_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } } + .tab-pane.active#tab-merge-requests -# loaded async = render "shared/milestones/tab_loading" .tab-pane#tab-participants diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index eaf50bc2115..c6b5dcc3647 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -1,6 +1,7 @@ -- supports_slash_commands = note_supports_slash_commands?(@note) -- if supports_slash_commands - - preview_url = preview_markdown_path(@project, slash_commands_target_type: @note.noteable_type, slash_commands_target_id: @note.noteable_id) +- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true) +- supports_quick_actions = note_supports_quick_actions?(@note) +- if supports_quick_actions + - preview_url = preview_markdown_path(@project, quick_actions_target_type: @note.noteable_type, quick_actions_target_id: @note.noteable_id) - else - preview_url = preview_markdown_path(@project) @@ -27,8 +28,9 @@ attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here...", - supports_slash_commands: supports_slash_commands - = render 'shared/notes/hints', supports_slash_commands: supports_slash_commands + supports_quick_actions: supports_quick_actions, + supports_autocomplete: supports_autocomplete + = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions .error-alert .note-form-actions.clearfix diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml index 7ce6130de60..bc1ac3d8ac2 100644 --- a/app/views/shared/notes/_hints.html.haml +++ b/app/views/shared/notes/_hints.html.haml @@ -1,10 +1,10 @@ -- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false) +- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false) .comment-toolbar.clearfix .toolbar-text = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1 - - if supports_slash_commands + - if supports_quick_actions and - = link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1 + = link_to 'quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1 are - else is diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml index 5902798dfd0..0cca8d875d2 100644 --- a/app/views/shared/notes/_notes_with_form.html.haml +++ b/app/views/shared/notes/_notes_with_form.html.haml @@ -12,7 +12,7 @@ %a.author_link{ href: user_path(current_user) } = image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40' .timeline-content.timeline-content-form - = render "shared/notes/form", view: diff_view + = render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete - elsif !current_user .disabled-comment.text-center.prepend-top-default Please diff --git a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml new file mode 100644 index 00000000000..2c915e62357 --- /dev/null +++ b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml @@ -0,0 +1,4 @@ +--- +title: Added "created_after" and "created_before" params to issuables +merge_request: 12151 +author: Kyle Bishop @kybishop diff --git a/changelogs/unreleased/12200-add-french-translation.yml b/changelogs/unreleased/12200-add-french-translation.yml new file mode 100644 index 00000000000..f31d982e0b9 --- /dev/null +++ b/changelogs/unreleased/12200-add-french-translation.yml @@ -0,0 +1,4 @@ +--- +title: "Adding French translations" +merge_request: 12200 +author : Erwan "Dremor" Georget diff --git a/changelogs/unreleased/13336-multiple-broadcast-messages.yml b/changelogs/unreleased/13336-multiple-broadcast-messages.yml new file mode 100644 index 00000000000..7dc73e1c6ea --- /dev/null +++ b/changelogs/unreleased/13336-multiple-broadcast-messages.yml @@ -0,0 +1,4 @@ +--- +title: Display all current broadcast messages, not just the last one +merge_request: 11113 +author: rickettm diff --git a/changelogs/unreleased/23998-blame-age-map.yml b/changelogs/unreleased/23998-blame-age-map.yml new file mode 100644 index 00000000000..26a38f0939c --- /dev/null +++ b/changelogs/unreleased/23998-blame-age-map.yml @@ -0,0 +1,4 @@ +--- +title: Add blame view age mapping +merge_request: 7198 +author: Jeff Stubler diff --git a/changelogs/unreleased/25164-disable-fork-on-project-limit.yml b/changelogs/unreleased/25164-disable-fork-on-project-limit.yml new file mode 100644 index 00000000000..9fa824b161d --- /dev/null +++ b/changelogs/unreleased/25164-disable-fork-on-project-limit.yml @@ -0,0 +1,4 @@ +--- +title: Disable fork button on project limit +merge_request: 12145 +author: Ivan Chernov diff --git a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml new file mode 100644 index 00000000000..667454ae95d --- /dev/null +++ b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml @@ -0,0 +1,4 @@ +--- +title: Accept image for avatar in user API +merge_request: 12143 +author: Ivan Chernov diff --git a/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml b/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml new file mode 100644 index 00000000000..497239db808 --- /dev/null +++ b/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml @@ -0,0 +1,5 @@ +--- +title: Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor + of "slash commands" +merge_request: +author: diff --git a/changelogs/unreleased/27586-center-dropdown.yml b/changelogs/unreleased/27586-center-dropdown.yml new file mode 100644 index 00000000000..4935f7504f7 --- /dev/null +++ b/changelogs/unreleased/27586-center-dropdown.yml @@ -0,0 +1,4 @@ +--- +title: Center dropdown for mini graph +merge_request: +author: diff --git a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml new file mode 100644 index 00000000000..92b5b59f46f --- /dev/null +++ b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml @@ -0,0 +1,4 @@ +--- +title: Use fa-chevron-down on dropdown arrows for consistency +merge_request: 9659 +author: TM Lee diff --git a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml new file mode 100644 index 00000000000..97ebabaff1c --- /dev/null +++ b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml @@ -0,0 +1,4 @@ +--- +title: Use color inputs for broadcast messages +merge_request: +author: diff --git a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml new file mode 100644 index 00000000000..3058404b3f8 --- /dev/null +++ b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml @@ -0,0 +1,4 @@ +--- +title: Ensures default user limits when external user is unchecked +merge_request: 12218 +author: diff --git a/changelogs/unreleased/31415-responsive-pipelines-table-2.yml b/changelogs/unreleased/31415-responsive-pipelines-table-2.yml new file mode 100644 index 00000000000..59402b85871 --- /dev/null +++ b/changelogs/unreleased/31415-responsive-pipelines-table-2.yml @@ -0,0 +1,4 @@ +--- +title: Create responsive mobile view for pipelines table +merge_request: +author: diff --git a/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml deleted file mode 100644 index 4137050a077..00000000000 --- a/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix the last coverage in trace log should be extracted -merge_request: 11128 -author: dosuken123 diff --git a/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml b/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml new file mode 100644 index 00000000000..7fc9e0a4f0e --- /dev/null +++ b/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml @@ -0,0 +1,4 @@ +--- +title: Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone' +merge_request: 11229 +author: @blackst0ne diff --git a/changelogs/unreleased/32470-pag-links.yml b/changelogs/unreleased/32470-pag-links.yml new file mode 100644 index 00000000000..d0fd284f3ee --- /dev/null +++ b/changelogs/unreleased/32470-pag-links.yml @@ -0,0 +1,4 @@ +--- +title: more visual contrast in pagination widget +merge_request: +author: diff --git a/changelogs/unreleased/32790-pipeline_schedules-pages-throwing-error-500.yml b/changelogs/unreleased/32790-pipeline_schedules-pages-throwing-error-500.yml deleted file mode 100644 index a58f3a7429e..00000000000 --- a/changelogs/unreleased/32790-pipeline_schedules-pages-throwing-error-500.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix pipeline_schedules pages throwing error 500 -merge_request: 11706 -author: dosuken123 diff --git a/changelogs/unreleased/32995-issue-contents-dynamically-replaced-with-stale-version-after-saving-or-refreshing-relative-external_url-only.yml b/changelogs/unreleased/32995-issue-contents-dynamically-replaced-with-stale-version-after-saving-or-refreshing-relative-external_url-only.yml deleted file mode 100644 index 5cd36a4e3e2..00000000000 --- a/changelogs/unreleased/32995-issue-contents-dynamically-replaced-with-stale-version-after-saving-or-refreshing-relative-external_url-only.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix incorrect ETag cache key when relative instance URL is used -merge_request: 11964 -author: diff --git a/changelogs/unreleased/33048-markdown-rendering-of-md-files-has-ceased-to-display-latex-equations.yml b/changelogs/unreleased/33048-markdown-rendering-of-md-files-has-ceased-to-display-latex-equations.yml deleted file mode 100644 index 5648e013e75..00000000000 --- a/changelogs/unreleased/33048-markdown-rendering-of-md-files-has-ceased-to-display-latex-equations.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix math rendering on blob pages -merge_request: -author: diff --git a/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml b/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml new file mode 100644 index 00000000000..385f18e2560 --- /dev/null +++ b/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml @@ -0,0 +1,4 @@ +--- +title: Document the Delete Merged Branches functionality +merge_request: +author: diff --git a/changelogs/unreleased/33461-display-user-id.yml b/changelogs/unreleased/33461-display-user-id.yml new file mode 100644 index 00000000000..cba94625b07 --- /dev/null +++ b/changelogs/unreleased/33461-display-user-id.yml @@ -0,0 +1,4 @@ +--- +title: Display own user id in account settings page +merge_request: 12141 +author: Riccardo Padovani diff --git a/changelogs/unreleased/counters_cache_invalidation.yml b/changelogs/unreleased/counters_cache_invalidation.yml deleted file mode 100644 index 1e78765ec10..00000000000 --- a/changelogs/unreleased/counters_cache_invalidation.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Invalidate cache for issue and MR counters more granularly -merge_request: -author: diff --git a/changelogs/unreleased/disable-blocked-manual-actions.yml b/changelogs/unreleased/disable-blocked-manual-actions.yml new file mode 100644 index 00000000000..a640f61a7dd --- /dev/null +++ b/changelogs/unreleased/disable-blocked-manual-actions.yml @@ -0,0 +1,4 @@ +--- +title: disable blocked manual actions +merge_request: +author: diff --git a/changelogs/unreleased/dm-diff-viewers.yml b/changelogs/unreleased/dm-diff-viewers.yml new file mode 100644 index 00000000000..e5b1352c8f1 --- /dev/null +++ b/changelogs/unreleased/dm-diff-viewers.yml @@ -0,0 +1,4 @@ +--- +title: Implement diff viewers +merge_request: +author: diff --git a/changelogs/unreleased/dm-fix-parser-cache.yml b/changelogs/unreleased/dm-fix-parser-cache.yml new file mode 100644 index 00000000000..31c163b7272 --- /dev/null +++ b/changelogs/unreleased/dm-fix-parser-cache.yml @@ -0,0 +1,4 @@ +--- +title: Don't return nil for missing objects from parser cache +merge_request: +author: diff --git a/changelogs/unreleased/dm-target-branch-slash-command-desc.yml b/changelogs/unreleased/dm-target-branch-slash-command-desc.yml new file mode 100644 index 00000000000..768ddf0416e --- /dev/null +++ b/changelogs/unreleased/dm-target-branch-slash-command-desc.yml @@ -0,0 +1,4 @@ +--- +title: Update /target_branch slash command description to be more consistent +merge_request: +author: diff --git a/changelogs/unreleased/feature-add-support-for-services-configuration.yml b/changelogs/unreleased/feature-add-support-for-services-configuration.yml new file mode 100644 index 00000000000..88a3eacd774 --- /dev/null +++ b/changelogs/unreleased/feature-add-support-for-services-configuration.yml @@ -0,0 +1,4 @@ +--- +title: Add support for image and services configuration in .gitlab-ci.yml +merge_request: 8578 +author: diff --git a/changelogs/unreleased/feature-unify-email-layouts.yml b/changelogs/unreleased/feature-unify-email-layouts.yml new file mode 100644 index 00000000000..7a2e3f20b6b --- /dev/null +++ b/changelogs/unreleased/feature-unify-email-layouts.yml @@ -0,0 +1,4 @@ +--- +title: Update the devise mail templates to match the design of the pipeline emails +merge_request: 10483 +author: Alexis Reigel diff --git a/changelogs/unreleased/fix-backup-restore-resume.yml b/changelogs/unreleased/fix-backup-restore-resume.yml deleted file mode 100644 index b7dfd451f5d..00000000000 --- a/changelogs/unreleased/fix-backup-restore-resume.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make backup task to continue on corrupt repositories -merge_request: 11962 -author: diff --git a/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml b/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml deleted file mode 100644 index 43c18502cd6..00000000000 --- a/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Respect merge, instead of push, permissions for protected actions -merge_request: 11648 -author: diff --git a/changelogs/unreleased/fix-github-clone-wiki.yml b/changelogs/unreleased/fix-github-clone-wiki.yml new file mode 100644 index 00000000000..eadd90e1390 --- /dev/null +++ b/changelogs/unreleased/fix-github-clone-wiki.yml @@ -0,0 +1,4 @@ +--- +title: Github - Fix token interpolation when cloning wiki repository +merge_request: +author: diff --git a/changelogs/unreleased/fix-missing-function-dropzone-input.yml b/changelogs/unreleased/fix-missing-function-dropzone-input.yml new file mode 100644 index 00000000000..d9dfc76faaf --- /dev/null +++ b/changelogs/unreleased/fix-missing-function-dropzone-input.yml @@ -0,0 +1,4 @@ +--- +title: Fix for cut & pasted images not working +merge_request: +author: diff --git a/changelogs/unreleased/fix-overflow-slash-commands.yml b/changelogs/unreleased/fix-overflow-slash-commands.yml new file mode 100644 index 00000000000..98ec399e8cb --- /dev/null +++ b/changelogs/unreleased/fix-overflow-slash-commands.yml @@ -0,0 +1,4 @@ +--- +title: Fixed overflow on mobile screens for the slash commands +merge_request: +author: diff --git a/changelogs/unreleased/fix-support-for-external-ci-services.yml b/changelogs/unreleased/fix-support-for-external-ci-services.yml new file mode 100644 index 00000000000..eecb4519259 --- /dev/null +++ b/changelogs/unreleased/fix-support-for-external-ci-services.yml @@ -0,0 +1,4 @@ +--- +title: Fix support for external CI services +merge_request: 11176 +author: diff --git a/changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml b/changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml deleted file mode 100644 index fb91da9510c..00000000000 --- a/changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix terminals support for Kubernetes Service -merge_request: -author: diff --git a/changelogs/unreleased/help-landing-page-customizations.yml b/changelogs/unreleased/help-landing-page-customizations.yml new file mode 100644 index 00000000000..58cab751ded --- /dev/null +++ b/changelogs/unreleased/help-landing-page-customizations.yml @@ -0,0 +1,4 @@ +--- +title: Help landing page customizations +merge_request: 11878 +author: Robin Bobbitt diff --git a/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml b/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml new file mode 100644 index 00000000000..916b182a48b --- /dev/null +++ b/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml @@ -0,0 +1,4 @@ +--- +title: Instrument MergeRequestDiff#load_commits +merge_request: +author: diff --git a/changelogs/unreleased/issue_20900.yml b/changelogs/unreleased/issue_20900.yml new file mode 100644 index 00000000000..e8cef6d2bce --- /dev/null +++ b/changelogs/unreleased/issue_20900.yml @@ -0,0 +1,4 @@ +--- +title: Remove issues/merge requests drag n drop and sorting from milestone view +merge_request: +author: diff --git a/changelogs/unreleased/issue_33205.yml b/changelogs/unreleased/issue_33205.yml new file mode 100644 index 00000000000..54b442048d8 --- /dev/null +++ b/changelogs/unreleased/issue_33205.yml @@ -0,0 +1,4 @@ +--- +title: Fix API bug accepting wrong parameter to create merge request +merge_request: +author: diff --git a/changelogs/unreleased/karma-headless-chrome.yml b/changelogs/unreleased/karma-headless-chrome.yml new file mode 100644 index 00000000000..af3e9b3b0f9 --- /dev/null +++ b/changelogs/unreleased/karma-headless-chrome.yml @@ -0,0 +1,4 @@ +--- +title: Replace PhantomJS with headless Chrome for karma test suite +merge_request: 12036 +author: diff --git a/changelogs/unreleased/moved-submodules.yml b/changelogs/unreleased/moved-submodules.yml new file mode 100644 index 00000000000..eee858717ed --- /dev/null +++ b/changelogs/unreleased/moved-submodules.yml @@ -0,0 +1,4 @@ +--- +title: 'Handle renamed submodules in repository browser' +merge_request: 10798 +author: David Turner diff --git a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml new file mode 100644 index 00000000000..14b5493a246 --- /dev/null +++ b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml @@ -0,0 +1,4 @@ +--- +title: Changed utilities imports from ~ to relative paths +merge_request: +author: diff --git a/changelogs/unreleased/reduce-sidekiq-wait-timings.yml b/changelogs/unreleased/reduce-sidekiq-wait-timings.yml new file mode 100644 index 00000000000..4d23accc82e --- /dev/null +++ b/changelogs/unreleased/reduce-sidekiq-wait-timings.yml @@ -0,0 +1,4 @@ +--- +title: Reduce time spent waiting for certain Sidekiq jobs to complete +merge_request: +author: diff --git a/changelogs/unreleased/sh-fix-lfs-from-moving-across-filesystems.yml b/changelogs/unreleased/sh-fix-lfs-from-moving-across-filesystems.yml deleted file mode 100644 index 161bce45601..00000000000 --- a/changelogs/unreleased/sh-fix-lfs-from-moving-across-filesystems.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix LFS timeouts when trying to save large files -merge_request: -author: diff --git a/changelogs/unreleased/sh-fix-submodules-trailing-spaces.yml b/changelogs/unreleased/sh-fix-submodules-trailing-spaces.yml deleted file mode 100644 index d633995d467..00000000000 --- a/changelogs/unreleased/sh-fix-submodules-trailing-spaces.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Strip trailing whitespaces in submodule URLs -merge_request: -author: diff --git a/changelogs/unreleased/sh-recaptcha-fix-try2.yml b/changelogs/unreleased/sh-recaptcha-fix-try2.yml deleted file mode 100644 index 94729252c6f..00000000000 --- a/changelogs/unreleased/sh-recaptcha-fix-try2.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make sure reCAPTCHA configuration is loaded when spam checks are initiated -merge_request: -author: diff --git a/changelogs/unreleased/speed-up-graphs.yml b/changelogs/unreleased/speed-up-graphs.yml new file mode 100644 index 00000000000..7cb155af6fd --- /dev/null +++ b/changelogs/unreleased/speed-up-graphs.yml @@ -0,0 +1,4 @@ +--- +title: Speed up used languages calculation on charts page +merge_request: +author: diff --git a/changelogs/unreleased/zj-commit-status-sortable-name.yml b/changelogs/unreleased/zj-commit-status-sortable-name.yml new file mode 100644 index 00000000000..1be9ac6380f --- /dev/null +++ b/changelogs/unreleased/zj-commit-status-sortable-name.yml @@ -0,0 +1,4 @@ +--- +title: Handle nameless legacy jobs +merge_request: +author: diff --git a/changelogs/unreleased/zj-drop-fk-if-exists.yml b/changelogs/unreleased/zj-drop-fk-if-exists.yml deleted file mode 100644 index 237ba936de9..00000000000 --- a/changelogs/unreleased/zj-drop-fk-if-exists.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove foreigh key on ci_trigger_schedules only if it exists -merge_request: -author: diff --git a/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml b/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml new file mode 100644 index 00000000000..57a5f4e44c0 --- /dev/null +++ b/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml @@ -0,0 +1,4 @@ +--- +title: Fix etag route not being a match for environments +merge_request: +author: diff --git a/config.ru b/config.ru index 89aba462f19..065ce59932f 100644 --- a/config.ru +++ b/config.ru @@ -15,9 +15,6 @@ if defined?(Unicorn) end end -# set default directory for multiproces metrics gathering -ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir' - require ::File.expand_path('../config/environment', __FILE__) map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do diff --git a/config/boot.rb b/config/boot.rb index 17a71148370..db5ab918021 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -5,6 +5,9 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +# set default directory for multiproces metrics gathering +ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir' + # Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage require 'bootsnap' Bootsnap.setup( diff --git a/config/environments/production.rb b/config/environments/production.rb index 82a19085b1d..c5cbfcf64cf 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -50,7 +50,7 @@ Rails.application.configure do # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Enable serving of images, stylesheets, and JavaScripts from an asset server - # config.action_controller.asset_host = "http://assets.example.com" + config.action_controller.asset_host = ENV['GITLAB_CDN_HOST'] if ENV['GITLAB_CDN_HOST'].present? # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) # config.assets.precompile += %w( search.js ) diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb index 5e0eefdb154..508b886d6a0 100644 --- a/config/initializers/8_metrics.rb +++ b/config/initializers/8_metrics.rb @@ -113,6 +113,9 @@ def instrument_classes(instrumentation) # This is a Rails scope so we have to instrument it manually. instrumentation.instrument_method(Project, :visible_to_user) + + # Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159 + instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits) end # rubocop:enable Metrics/AbcSize diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb new file mode 100644 index 00000000000..beb97c6fce0 --- /dev/null +++ b/config/initializers/active_record_data_types.rb @@ -0,0 +1,24 @@ +# ActiveRecord custom data type for storing datetimes with timezone information. +# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229 + +if Gitlab::Database.postgresql? + require 'active_record/connection_adapters/postgresql_adapter' + + module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapter + NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamptz' }) + end + end + end +elsif Gitlab::Database.mysql? + require 'active_record/connection_adapters/mysql2_adapter' + + module ActiveRecord + module ConnectionAdapters + class AbstractMysqlAdapter + NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamp' }) + end + end + end +end diff --git a/config/initializers/active_record_table_definition.rb b/config/initializers/active_record_table_definition.rb new file mode 100644 index 00000000000..4f59e35f4da --- /dev/null +++ b/config/initializers/active_record_table_definition.rb @@ -0,0 +1,24 @@ +# ActiveRecord custom method definitions with timezone information. +# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229 + +require 'active_record/connection_adapters/abstract/schema_definitions' + +# Appends columns `created_at` and `updated_at` to a table. +# +# It is used in table creation like: +# create_table 'users' do |t| +# t.timestamps_with_timezone +# end +module ActiveRecord + module ConnectionAdapters + class TableDefinition + def timestamps_with_timezone(**options) + options[:null] = false if options[:null].nil? + + [:created_at, :updated_at].each do |column_name| + column(column_name, :datetime_with_timezone, options) + end + end + end + end +end diff --git a/config/initializers/rugged_use_gitlab_git_attributes.rb b/config/initializers/rugged_use_gitlab_git_attributes.rb new file mode 100644 index 00000000000..7d652799786 --- /dev/null +++ b/config/initializers/rugged_use_gitlab_git_attributes.rb @@ -0,0 +1,25 @@ +# We don't want to ever call Rugged::Repository#fetch_attributes, because it has +# a lot of I/O overhead: +# <https://gitlab.com/gitlab-org/gitlab_git/commit/340e111e040ae847b614d35b4d3173ec48329015> +# +# While we don't do this from within the GitLab source itself, the Linguist gem +# has a dependency on Rugged and uses the gitattributes file when calculating +# repository-wide language statistics: +# <https://github.com/github/linguist/blob/v4.7.0/lib/linguist/lazy_blob.rb#L33-L36> +# +# The options passed by Linguist are those assumed by Gitlab::Git::Attributes +# anyway, and there is no great efficiency gain from just fetching the listed +# attributes with our implementation, so we ignore the additional arguments. +# +module Rugged + class Repository + module UseGitlabGitAttributes + def fetch_attributes(name, *) + @attributes ||= Gitlab::Git::Attributes.new(path) + @attributes.attributes(name) + end + end + + prepend UseGitlabGitAttributes + end +end diff --git a/config/karma.config.js b/config/karma.config.js index 40c58e7771d..5911a9a7e10 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -21,7 +21,18 @@ module.exports = function(config) { var karmaConfig = { basePath: ROOT_PATH, - browsers: ['PhantomJS'], + browsers: ['ChromeHeadlessCustom'], + customLaunchers: { + ChromeHeadlessCustom: { + base: 'ChromeHeadless', + displayName: 'Chrome', + flags: [ + // chrome cannot run in sandboxed mode inside a docker container unless it is run with + // escalated kernel privileges (e.g. docker run --cap-add=CAP_SYS_ADMIN) + '--no-sandbox', + ], + } + }, frameworks: ['jasmine'], files: [ { pattern: 'spec/javascripts/test_bundle.js', watched: false }, @@ -43,6 +54,16 @@ module.exports = function(config) { subdir: '.', fixWebpackSourcePaths: true }; + karmaConfig.browserNoActivityTimeout = 60000; // 60 seconds + } + + if (process.env.DEBUG) { + karmaConfig.logLevel = config.LOG_DEBUG; + process.env.CHROME_LOG_FILE = process.env.CHROME_LOG_FILE || 'chrome_debug.log'; + } + + if (process.env.CHROME_LOG_FILE) { + karmaConfig.customLaunchers.ChromeHeadlessCustom.flags.push('--enable-logging', '--v=1'); } config.set(karmaConfig); diff --git a/config/locales/en.yml b/config/locales/en.yml index 9d47425950a..8932db138d9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,17 +2,63 @@ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: - hello: "Hello world" - errors: - messages: - label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one." - wrong_size: "is the wrong size (should be %{file_size})" - size_too_small: "is too small (should be at least %{file_size})" - size_too_big: "is too big (should be at most %{file_size})" views: pagination: previous: "Prev" next: "Next" + date: + abbr_day_names: + - Sun + - Mon + - Tue + - Wed + - Thu + - Fri + - Sat + abbr_month_names: + - + - Jan + - Feb + - Mar + - Apr + - May + - Jun + - Jul + - Aug + - Sep + - Oct + - Nov + - Dec + day_names: + - Sunday + - Monday + - Tuesday + - Wednesday + - Thursday + - Friday + - Saturday + formats: + default: "%Y-%m-%d" + long: "%B %d, %Y" + short: "%b %d" + month_names: + - + - January + - February + - March + - April + - May + - June + - July + - August + - September + - October + - November + - December + order: + - :year + - :month + - :day datetime: time_ago_in_words: half_a_minute: "half a minute ago" @@ -49,3 +95,158 @@ en: almost_x_years: one: "almost 1 year ago" other: "almost %{count} years ago" + distance_in_words: + about_x_hours: + one: about 1 hour + other: about %{count} hours + about_x_months: + one: about 1 month + other: about %{count} months + about_x_years: + one: about 1 year + other: about %{count} years + almost_x_years: + one: almost 1 year + other: almost %{count} years + half_a_minute: half a minute + less_than_x_minutes: + one: less than a minute + other: less than %{count} minutes + less_than_x_seconds: + one: less than 1 second + other: less than %{count} seconds + over_x_years: + one: over 1 year + other: over %{count} years + x_days: + one: 1 day + other: "%{count} days" + x_minutes: + one: 1 minute + other: "%{count} minutes" + x_months: + one: 1 month + other: "%{count} months" + x_years: + one: 1 year + other: "%{count} years" + x_seconds: + one: 1 second + other: "%{count} seconds" + prompts: + day: Day + hour: Hour + minute: Minute + month: Month + second: Seconds + year: Year + errors: + format: "%{attribute} %{message}" + messages: + label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one." + wrong_size: "is the wrong size (should be %{file_size})" + size_too_small: "is too small (should be at least %{file_size})" + size_too_big: "is too big (should be at most %{file_size})" + accepted: must be accepted + blank: can't be blank + present: must be blank + confirmation: doesn't match %{attribute} + empty: can't be empty + equal_to: must be equal to %{count} + even: must be even + exclusion: is reserved + greater_than: must be greater than %{count} + greater_than_or_equal_to: must be greater than or equal to %{count} + inclusion: is not included in the list + invalid: is invalid + less_than: must be less than %{count} + less_than_or_equal_to: must be less than or equal to %{count} + model_invalid: "Validation failed: %{errors}" + not_a_number: is not a number + not_an_integer: must be an integer + odd: must be odd + required: must exist + taken: has already been taken + too_long: + one: is too long (maximum is 1 character) + other: is too long (maximum is %{count} characters) + too_short: + one: is too short (minimum is 1 character) + other: is too short (minimum is %{count} characters) + wrong_length: + one: is the wrong length (should be 1 character) + other: is the wrong length (should be %{count} characters) + other_than: must be other than %{count} + template: + body: 'There were problems with the following fields:' + header: + one: 1 error prohibited this %{model} from being saved + other: "%{count} errors prohibited this %{model} from being saved" + helpers: + select: + prompt: Please select + submit: + create: Create %{model} + submit: Save %{model} + update: Update %{model} + number: + currency: + format: + delimiter: "," + format: "%u%n" + precision: 2 + separator: "." + significant: false + strip_insignificant_zeros: false + unit: "$" + format: + delimiter: "," + precision: 3 + separator: "." + significant: false + strip_insignificant_zeros: false + human: + decimal_units: + format: "%n %u" + units: + billion: Billion + million: Million + quadrillion: Quadrillion + thousand: Thousand + trillion: Trillion + unit: '' + format: + delimiter: '' + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: Byte + other: Bytes + gb: GB + kb: KB + mb: MB + tb: TB + percentage: + format: + delimiter: '' + format: "%n%" + precision: + format: + delimiter: '' + support: + array: + last_word_connector: ", and " + two_words_connector: " and " + words_connector: ", " + time: + am: am + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + long: "%B %d, %Y %H:%M" + short: "%d %b %H:%M" + timeago_tooltip: "%b %-d, %Y %-l:%M%P" + pm: pm diff --git a/config/locales/es.yml b/config/locales/es.yml index d71c6eb5047..fdc52b4ae11 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -251,4 +251,5 @@ es: default: "%A, %d de %B de %Y %H:%M:%S %z" long: "%d de %B de %Y %H:%M" short: "%d de %b %H:%M" + timeago_tooltip: "%d de %B de %Y %H:%M" pm: pm diff --git a/config/webpack.config.js b/config/webpack.config.js index 120f9d3193d..fb91ffef7e7 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -11,22 +11,13 @@ var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeMod var ROOT_PATH = path.resolve(__dirname, '..'); var IS_PRODUCTION = process.env.NODE_ENV === 'production'; -var IS_DEV_SERVER = process.argv[1].indexOf('webpack-dev-server') !== -1; +var IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; var DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; var DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808; var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false'; var WEBPACK_REPORT = process.env.WEBPACK_REPORT; var NO_COMPRESSION = process.env.NO_COMPRESSION; -// optional dependency `node-zopfli` is unavailable on CentOS 6 -var ZOPFLI_AVAILABLE; -try { - require.resolve('node-zopfli'); - ZOPFLI_AVAILABLE = true; -} catch(err) { - ZOPFLI_AVAILABLE = false; -} - var config = { // because sqljs requires fs. node: { @@ -61,7 +52,7 @@ var config = { network: './network/network_bundle.js', notebook_viewer: './blob/notebook_viewer.js', pdf_viewer: './blob/pdf_viewer.js', - pipelines: './pipelines/index.js', + pipelines: './pipelines/pipelines_bundle.js', pipelines_details: './pipelines/pipeline_details_bundle.js', profile: './profile/profile_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js', @@ -233,12 +224,12 @@ if (IS_PRODUCTION) { // zopfli requires a lot of compute time and is disabled in CI if (!NO_COMPRESSION) { - config.plugins.push( - new CompressionPlugin({ - asset: '[path].gz[query]', - algorithm: ZOPFLI_AVAILABLE ? 'zopfli' : 'gzip', - }) - ); + // gracefully fall back to gzip if `node-zopfli` is unavailable (e.g. in CentOS 6) + try { + config.plugins.push(new CompressionPlugin({ algorithm: 'zopfli' })); + } catch(err) { + config.plugins.push(new CompressionPlugin({ algorithm: 'gzip' })); + } } } diff --git a/db/migrate/20160314114439_add_requested_at_to_members.rb b/db/migrate/20160314114439_add_requested_at_to_members.rb index 273819d4cd8..76c8b8a1a24 100644 --- a/db/migrate/20160314114439_add_requested_at_to_members.rb +++ b/db/migrate/20160314114439_add_requested_at_to_members.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddRequestedAtToMembers < ActiveRecord::Migration def change add_column :members, :requested_at, :datetime diff --git a/db/migrate/20160415062917_create_personal_access_tokens.rb b/db/migrate/20160415062917_create_personal_access_tokens.rb index ce0b33f32bd..c7b49870bf7 100644 --- a/db/migrate/20160415062917_create_personal_access_tokens.rb +++ b/db/migrate/20160415062917_create_personal_access_tokens.rb @@ -1,3 +1,5 @@ +# rubocop:disable Migration/Datetime +# rubocop:disable Migration/Timestamps class CreatePersonalAccessTokens < ActiveRecord::Migration def change create_table :personal_access_tokens do |t| diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb index cb144ea8a6d..0e7e6e747a3 100644 --- a/db/migrate/20160610204157_add_deployments.rb +++ b/db/migrate/20160610204157_add_deployments.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Datetime class AddDeployments < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160610204158_add_environments.rb b/db/migrate/20160610204158_add_environments.rb index e1c71d173c4..699cee2b246 100644 --- a/db/migrate/20160610204158_add_environments.rb +++ b/db/migrate/20160610204158_add_environments.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Datetime class AddEnvironments < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb index f27295524e1..97aaaf9d2c8 100644 --- a/db/migrate/20160705054938_add_protected_branches_push_access.rb +++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Timestamps class AddProtectedBranchesPushAccess < ActiveRecord::Migration DOWNTIME = false diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb index 32adfa266cd..51a52a5ac17 100644 --- a/db/migrate/20160705054952_add_protected_branches_merge_access.rb +++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Timestamps class AddProtectedBranchesMergeAccess < ActiveRecord::Migration DOWNTIME = false diff --git a/db/migrate/20160724205507_add_resolved_to_notes.rb b/db/migrate/20160724205507_add_resolved_to_notes.rb index b8ebcdbd156..3aca272a3f7 100644 --- a/db/migrate/20160724205507_add_resolved_to_notes.rb +++ b/db/migrate/20160724205507_add_resolved_to_notes.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddResolvedToNotes < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160727163552_create_user_agent_details.rb b/db/migrate/20160727163552_create_user_agent_details.rb index ed4ccfedc0a..3eb36f8464f 100644 --- a/db/migrate/20160727163552_create_user_agent_details.rb +++ b/db/migrate/20160727163552_create_user_agent_details.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateUserAgentDetails < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160727191041_create_boards.rb b/db/migrate/20160727191041_create_boards.rb index 56afbd4e030..9ec8df1b8e8 100644 --- a/db/migrate/20160727191041_create_boards.rb +++ b/db/migrate/20160727191041_create_boards.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateBoards < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160727193336_create_lists.rb b/db/migrate/20160727193336_create_lists.rb index 61d501215f2..3fd95dc8cfc 100644 --- a/db/migrate/20160727193336_create_lists.rb +++ b/db/migrate/20160727193336_create_lists.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateLists < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb index 30d98a0124e..404c253e18b 100644 --- a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb +++ b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime # rubocop:disable RemoveIndex class AddDeletedAtToNamespaces < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160824124900_add_table_issue_metrics.rb b/db/migrate/20160824124900_add_table_issue_metrics.rb index e9bb79b3c62..30d35ef1db2 100644 --- a/db/migrate/20160824124900_add_table_issue_metrics.rb +++ b/db/migrate/20160824124900_add_table_issue_metrics.rb @@ -1,6 +1,8 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Datetime +# rubocop:disable Migration/Timestamps class AddTableIssueMetrics < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160825052008_add_table_merge_request_metrics.rb b/db/migrate/20160825052008_add_table_merge_request_metrics.rb index e01cc5038b9..56b39634dfd 100644 --- a/db/migrate/20160825052008_add_table_merge_request_metrics.rb +++ b/db/migrate/20160825052008_add_table_merge_request_metrics.rb @@ -1,6 +1,8 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Datetime +# rubocop:disable Migration/Timestamps class AddTableMergeRequestMetrics < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb index 343953826f0..7ac6c8ec654 100644 --- a/db/migrate/20160831214002_create_project_features.rb +++ b/db/migrate/20160831214002_create_project_features.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateProjectFeatures < ActiveRecord::Migration DOWNTIME = false diff --git a/db/migrate/20160915042921_create_merge_requests_closing_issues.rb b/db/migrate/20160915042921_create_merge_requests_closing_issues.rb index 94874a853da..10c5604bb5c 100644 --- a/db/migrate/20160915042921_create_merge_requests_closing_issues.rb +++ b/db/migrate/20160915042921_create_merge_requests_closing_issues.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Timestamps class CreateMergeRequestsClosingIssues < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161014173530_create_label_priorities.rb b/db/migrate/20161014173530_create_label_priorities.rb index 2c22841c28a..28937c81e02 100644 --- a/db/migrate/20161014173530_create_label_priorities.rb +++ b/db/migrate/20161014173530_create_label_priorities.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateLabelPriorities < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161113184239_create_user_chat_names_table.rb b/db/migrate/20161113184239_create_user_chat_names_table.rb index 97b597654f7..62ccb599f2e 100644 --- a/db/migrate/20161113184239_create_user_chat_names_table.rb +++ b/db/migrate/20161113184239_create_user_chat_names_table.rb @@ -1,3 +1,5 @@ +# rubocop:disable Migration/Datetime +# rubocop:disable Migration/Timestamps class CreateUserChatNamesTable < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161124111402_add_routes_table.rb b/db/migrate/20161124111402_add_routes_table.rb index a02e046a18e..f5241d906d1 100644 --- a/db/migrate/20161124111402_add_routes_table.rb +++ b/db/migrate/20161124111402_add_routes_table.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Timestamps class AddRoutesTable < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161221152132_add_last_used_at_to_key.rb b/db/migrate/20161221152132_add_last_used_at_to_key.rb index fb2b15817de..86dc7870247 100644 --- a/db/migrate/20161221152132_add_last_used_at_to_key.rb +++ b/db/migrate/20161221152132_add_last_used_at_to_key.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddLastUsedAtToKey < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161223034646_create_timelogs_ce.rb b/db/migrate/20161223034646_create_timelogs_ce.rb index 66d9cd823fb..1e894cc9161 100644 --- a/db/migrate/20161223034646_create_timelogs_ce.rb +++ b/db/migrate/20161223034646_create_timelogs_ce.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateTimelogsCe < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb b/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb index af1bac897cc..16f7cc487ce 100644 --- a/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb +++ b/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable Migration/Datetime class ChangeExpiresAtToDateInPersonalAccessTokens < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170120131253_create_chat_teams.rb b/db/migrate/20170120131253_create_chat_teams.rb index 7995d383986..52208821911 100644 --- a/db/migrate/20170120131253_create_chat_teams.rb +++ b/db/migrate/20170120131253_create_chat_teams.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateChatTeams < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170130221926_create_uploads.rb b/db/migrate/20170130221926_create_uploads.rb index 6f06c5dd840..4d9fa0bb692 100644 --- a/db/migrate/20170130221926_create_uploads.rb +++ b/db/migrate/20170130221926_create_uploads.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class CreateUploads < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170222143317_drop_ci_projects.rb b/db/migrate/20170222143317_drop_ci_projects.rb index 4db8658f36f..9973e53501c 100644 --- a/db/migrate/20170222143317_drop_ci_projects.rb +++ b/db/migrate/20170222143317_drop_ci_projects.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class DropCiProjects < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb b/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb index 69dd15b8b4e..1a77d5934a3 100644 --- a/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb +++ b/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class RemoveUnusedCiTablesAndColumns < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170309173138_create_protected_tags.rb b/db/migrate/20170309173138_create_protected_tags.rb index 796f3c90344..4684c9964c4 100644 --- a/db/migrate/20170309173138_create_protected_tags.rb +++ b/db/migrate/20170309173138_create_protected_tags.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateProtectedTags < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170314082049_create_system_note_metadata.rb b/db/migrate/20170314082049_create_system_note_metadata.rb index dd1e6cf8172..fee47e96053 100644 --- a/db/migrate/20170314082049_create_system_note_metadata.rb +++ b/db/migrate/20170314082049_create_system_note_metadata.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateSystemNoteMetadata < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170315194013_add_closed_at_to_issues.rb b/db/migrate/20170315194013_add_closed_at_to_issues.rb index 1326118cc8d..34a1bd7ca8c 100644 --- a/db/migrate/20170315194013_add_closed_at_to_issues.rb +++ b/db/migrate/20170315194013_add_closed_at_to_issues.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddClosedAtToIssues < ActiveRecord::Migration DOWNTIME = false diff --git a/db/migrate/20170322013926_create_container_repository.rb b/db/migrate/20170322013926_create_container_repository.rb index 91540bc88bd..242f7b8d17d 100644 --- a/db/migrate/20170322013926_create_container_repository.rb +++ b/db/migrate/20170322013926_create_container_repository.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateContainerRepository < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170329095907_create_ci_trigger_schedules.rb b/db/migrate/20170329095907_create_ci_trigger_schedules.rb index cfcfa27ebb5..06a2010db23 100644 --- a/db/migrate/20170329095907_create_ci_trigger_schedules.rb +++ b/db/migrate/20170329095907_create_ci_trigger_schedules.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class CreateCiTriggerSchedules < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170425112128_create_pipeline_schedules_table.rb b/db/migrate/20170425112128_create_pipeline_schedules_table.rb index 3612a796ae8..57df47f5f42 100644 --- a/db/migrate/20170425112128_create_pipeline_schedules_table.rb +++ b/db/migrate/20170425112128_create_pipeline_schedules_table.rb @@ -1,3 +1,5 @@ +# rubocop:disable Migration/Datetime +# rubocop:disable Migration/Timestamps class CreatePipelineSchedulesTable < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170427215854_create_redirect_routes.rb b/db/migrate/20170427215854_create_redirect_routes.rb index 2bf086b3e30..6db508e5db4 100644 --- a/db/migrate/20170427215854_create_redirect_routes.rb +++ b/db/migrate/20170427215854_create_redirect_routes.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateRedirectRoutes < ActiveRecord::Migration # Set this constant to true if this migration requires downtime. DOWNTIME = false diff --git a/db/migrate/20170503004125_add_last_repository_updated_at_to_projects.rb b/db/migrate/20170503004125_add_last_repository_updated_at_to_projects.rb index 00c685cf342..2ea49f62742 100644 --- a/db/migrate/20170503004125_add_last_repository_updated_at_to_projects.rb +++ b/db/migrate/20170503004125_add_last_repository_updated_at_to_projects.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddLastRepositoryUpdatedAtToProjects < ActiveRecord::Migration DOWNTIME = false diff --git a/db/migrate/20170523121229_create_conversational_development_index_metrics.rb b/db/migrate/20170523121229_create_conversational_development_index_metrics.rb index 9f9ec526055..7026a867ae1 100644 --- a/db/migrate/20170523121229_create_conversational_development_index_metrics.rb +++ b/db/migrate/20170523121229_create_conversational_development_index_metrics.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreateConversationalDevelopmentIndexMetrics < ActiveRecord::Migration DOWNTIME = false diff --git a/db/migrate/20170525132202_create_pipeline_stages.rb b/db/migrate/20170525132202_create_pipeline_stages.rb index 25656f2a2c2..825993aa41e 100644 --- a/db/migrate/20170525132202_create_pipeline_stages.rb +++ b/db/migrate/20170525132202_create_pipeline_stages.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Timestamps class CreatePipelineStages < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb b/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb new file mode 100644 index 00000000000..5e8b667b86d --- /dev/null +++ b/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb @@ -0,0 +1,9 @@ +class AddHelpPageHideCommercialContentToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :application_settings, :help_page_hide_commercial_content, :boolean, default: false + end +end diff --git a/db/migrate/20170602154813_add_help_page_support_url_to_application_settings.rb b/db/migrate/20170602154813_add_help_page_support_url_to_application_settings.rb new file mode 100644 index 00000000000..138fe9b2a37 --- /dev/null +++ b/db/migrate/20170602154813_add_help_page_support_url_to_application_settings.rb @@ -0,0 +1,9 @@ +class AddHelpPageSupportUrlToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :application_settings, :help_page_support_url, :string + end +end diff --git a/db/migrate/20170606154216_add_notification_setting_columns.rb b/db/migrate/20170606154216_add_notification_setting_columns.rb new file mode 100644 index 00000000000..0a9b5da6583 --- /dev/null +++ b/db/migrate/20170606154216_add_notification_setting_columns.rb @@ -0,0 +1,26 @@ +class AddNotificationSettingColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + COLUMNS = [ + :new_note, + :new_issue, + :reopen_issue, + :close_issue, + :reassign_issue, + :new_merge_request, + :reopen_merge_request, + :close_merge_request, + :reassign_merge_request, + :merge_merge_request, + :failed_pipeline, + :success_pipeline + ] + + def change + COLUMNS.each do |column| + add_column(:notification_settings, column, :boolean) + end + end +end diff --git a/db/migrate/20170608171156_create_merge_request_diff_files.rb b/db/migrate/20170608171156_create_merge_request_diff_files.rb new file mode 100644 index 00000000000..bf0c0d29adc --- /dev/null +++ b/db/migrate/20170608171156_create_merge_request_diff_files.rb @@ -0,0 +1,22 @@ +class CreateMergeRequestDiffFiles < ActiveRecord::Migration + DOWNTIME = false + + disable_ddl_transaction! + + def change + create_table :merge_request_diff_files, id: false do |t| + t.belongs_to :merge_request_diff, null: false, foreign_key: { on_delete: :cascade } + t.integer :relative_order, null: false + t.boolean :new_file, null: false + t.boolean :renamed_file, null: false + t.boolean :deleted_file, null: false + t.boolean :too_large, null: false + t.string :a_mode, null: false + t.string :b_mode, null: false + t.text :new_path, null: false + t.text :old_path, null: false + t.text :diff, null: false + t.index [:merge_request_diff_id, :relative_order], name: 'index_merge_request_diff_files_on_mr_diff_id_and_order', unique: true + end + end +end diff --git a/db/migrate/20170614115405_merge_request_diff_file_limits_to_mysql.rb b/db/migrate/20170614115405_merge_request_diff_file_limits_to_mysql.rb new file mode 100644 index 00000000000..4c1cf08aa06 --- /dev/null +++ b/db/migrate/20170614115405_merge_request_diff_file_limits_to_mysql.rb @@ -0,0 +1 @@ +require_relative 'merge_request_diff_file_limits_to_mysql' diff --git a/db/migrate/merge_request_diff_file_limits_to_mysql.rb b/db/migrate/merge_request_diff_file_limits_to_mysql.rb new file mode 100644 index 00000000000..3958380e4b9 --- /dev/null +++ b/db/migrate/merge_request_diff_file_limits_to_mysql.rb @@ -0,0 +1,12 @@ +class MergeRequestDiffFileLimitsToMysql < ActiveRecord::Migration + DOWNTIME = false + + def up + return unless Gitlab::Database.mysql? + + change_column :merge_request_diff_files, :diff, :text, limit: 2147483647 + end + + def down + end +end diff --git a/db/post_migrate/20170425130047_drop_ci_trigger_schedules_table.rb b/db/post_migrate/20170425130047_drop_ci_trigger_schedules_table.rb index 24750c58ef0..159b533eaaa 100644 --- a/db/post_migrate/20170425130047_drop_ci_trigger_schedules_table.rb +++ b/db/post_migrate/20170425130047_drop_ci_trigger_schedules_table.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class DropCiTriggerSchedulesTable < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/post_migrate/20170607121233_convert_custom_notification_settings_to_columns.rb b/db/post_migrate/20170607121233_convert_custom_notification_settings_to_columns.rb new file mode 100644 index 00000000000..9abda6a1d73 --- /dev/null +++ b/db/post_migrate/20170607121233_convert_custom_notification_settings_to_columns.rb @@ -0,0 +1,55 @@ +class ConvertCustomNotificationSettingsToColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + class NotificationSetting < ActiveRecord::Base + self.table_name = 'notification_settings' + + store :events, coder: JSON + end + + EMAIL_EVENTS = [ + :new_note, + :new_issue, + :reopen_issue, + :close_issue, + :reassign_issue, + :new_merge_request, + :reopen_merge_request, + :close_merge_request, + :reassign_merge_request, + :merge_merge_request, + :failed_pipeline, + :success_pipeline + ] + + # We only need to migrate (up or down) rows where at least one of these + # settings is set. + def up + NotificationSetting.where("events LIKE '%true%'").find_each do |notification_setting| + EMAIL_EVENTS.each do |event| + notification_setting[event] = notification_setting.events[event] + end + + notification_setting[:events] = nil + notification_setting.save! + end + end + + def down + NotificationSetting.where(EMAIL_EVENTS.join(' OR ')).find_each do |notification_setting| + events = {} + + EMAIL_EVENTS.each do |event| + events[event] = !!notification_setting.public_send(event) + notification_setting[event] = nil + end + + notification_setting[:events] = events + notification_setting.save! + end + end +end diff --git a/db/post_migrate/20170609183112_remove_position_from_issuables.rb b/db/post_migrate/20170609183112_remove_position_from_issuables.rb new file mode 100644 index 00000000000..4caaa2e83e8 --- /dev/null +++ b/db/post_migrate/20170609183112_remove_position_from_issuables.rb @@ -0,0 +1,8 @@ +class RemovePositionFromIssuables < ActiveRecord::Migration + DOWNTIME = false + + def change + remove_column :issues, :position, :integer + remove_column :merge_requests, :position, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index b93630a410d..f42827991aa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170606202615) do +ActiveRecord::Schema.define(version: 20170614115405) do + # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" enable_extension "pg_trgm" @@ -123,6 +124,8 @@ ActiveRecord::Schema.define(version: 20170606202615) do t.boolean "clientside_sentry_enabled", default: false, null: false t.string "clientside_sentry_dsn" t.boolean "prometheus_metrics_enabled", default: false, null: false + t.boolean "help_page_hide_commercial_content", default: false + t.string "help_page_support_url" end create_table "audit_events", force: :cascade do |t| @@ -544,7 +547,6 @@ ActiveRecord::Schema.define(version: 20170606202615) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 t.string "branch_name" t.text "description" t.integer "milestone_id" @@ -690,6 +692,22 @@ ActiveRecord::Schema.define(version: 20170606202615) do add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree + create_table "merge_request_diff_files", id: false, force: :cascade do |t| + t.integer "merge_request_diff_id", null: false + t.integer "relative_order", null: false + t.boolean "new_file", null: false + t.boolean "renamed_file", null: false + t.boolean "deleted_file", null: false + t.boolean "too_large", null: false + t.string "a_mode", null: false + t.string "b_mode", null: false + t.text "new_path", null: false + t.text "old_path", null: false + t.text "diff", null: false + end + + add_index "merge_request_diff_files", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_files_on_mr_diff_id_and_order", unique: true, using: :btree + create_table "merge_request_diffs", force: :cascade do |t| t.string "state" t.text "st_commits" @@ -735,7 +753,6 @@ ActiveRecord::Schema.define(version: 20170606202615) do t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" t.text "merge_error" @@ -875,6 +892,18 @@ ActiveRecord::Schema.define(version: 20170606202615) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "events" + t.boolean "new_note" + t.boolean "new_issue" + t.boolean "reopen_issue" + t.boolean "close_issue" + t.boolean "reassign_issue" + t.boolean "new_merge_request" + t.boolean "reopen_merge_request" + t.boolean "close_merge_request" + t.boolean "reassign_merge_request" + t.boolean "merge_merge_request" + t.boolean "failed_pipeline" + t.boolean "success_pipeline" end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree @@ -1517,6 +1546,7 @@ ActiveRecord::Schema.define(version: 20170606202615) do add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "lists", "boards" add_foreign_key "lists", "labels" + add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade diff --git a/doc/README.md b/doc/README.md index 9f12eed1471..ab8ea192a26 100644 --- a/doc/README.md +++ b/doc/README.md @@ -24,7 +24,7 @@ Shortcuts to GitLab's most visited docs: - [GitLab Workflow](workflow/README.md): Enhance your workflow with the best of GitLab Workflow. - See also [GitLab Workflow - an overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/). - [GitLab Markdown](user/markdown.md): GitLab's advanced formatting system (GitLab Flavored Markdown). -- [GitLab Slash Commands](user/project/slash_commands.md): Textual shortcuts for common actions on issues or merge requests that are usually done by clicking buttons or dropdowns in GitLab's UI. +- [GitLab Quick Actions](user/project/quick_actions.md): Textual shortcuts for common actions on issues or merge requests that are usually done by clicking buttons or dropdowns in GitLab's UI. ### User account @@ -59,6 +59,7 @@ Manage files and branches from the UI (user interface): - Branches - [Create a branch](user/project/repository/web_editor.md#create-a-new-branch) - [Protected branches](user/project/protected_branches.md#protected-branches) + - [Delete merged branches](user/project/repository/branches/index.md#delete-merged-branches) ### Issues and Merge Requests (MRs) diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md index b6676026d06..9bcd13a52f7 100644 --- a/doc/administration/environment_variables.md +++ b/doc/administration/environment_variables.md @@ -13,6 +13,7 @@ override certain values. Variable | Type | Description -------- | ---- | ----------- +`GITLAB_CDN_HOST` | string | Sets the hostname for a CDN to serve static assets (e.g. `mycdnsubdomain.fictional-cdn.com`) `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`) `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test` @@ -58,6 +59,9 @@ to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`. ## Omnibus configuration +To set environment variables, follow [these +instructions](https://docs.gitlab.com/omnibus/settings/environment-variables.html). + It's possible to preconfigure the GitLab docker image by adding the environment variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command. For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://docs.gitlab.com/omnibus/docker/#preconfigure-docker-container). diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 5599435564e..3587696225c 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -46,7 +46,10 @@ To disable artifacts site-wide, follow the steps below. After a successful job, GitLab Runner uploads an archive containing the job artifacts to GitLab. -To change the location where the artifacts are stored, follow the steps below. +### Using local storage + +To change the location where the artifacts are stored locally, follow the steps +below. --- @@ -82,6 +85,13 @@ _The artifacts are stored by default in 1. Save the file and [restart GitLab][] for the changes to take effect. +### Using object storage + +In [GitLab Enterprise Edition Premium][eep] you can use an object storage like +AWS S3 to store the artifacts. + +[Learn how to use the object storage option.][ee-os] + ## Expiring artifacts If an expiry date is used for the artifacts, they are marked for deletion @@ -148,3 +158,5 @@ memory and disk I/O. [reconfigure gitlab]: restart_gitlab.md "How to restart GitLab" [restart gitlab]: restart_gitlab.md "How to restart GitLab" [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" +[ee-os]: https://docs.gitlab.com/ee/administration/job_artifacts.html#using-object-storage +[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition Premium" diff --git a/doc/administration/raketasks/github_import.md b/doc/administration/raketasks/github_import.md index affb4d17861..04c70c3644e 100644 --- a/doc/administration/raketasks/github_import.md +++ b/doc/administration/raketasks/github_import.md @@ -3,7 +3,7 @@ >**Note:** > > - [Introduced][ce-10308] in GitLab 9.1. -> - You need a personal access token in order to retrieve and import GitHub +> - You need a personal access token in order to retrieve and import GitHub > projects. You can get it from: https://github.com/settings/tokens > - You also need to pass an username as the second argument to the rake task > which will become the owner of the project. @@ -19,7 +19,7 @@ bundle exec rake import:github[access_token,root,foo/bar] RAILS_ENV=production ``` In this case, `access_token` is your GitHub personal access token, `root` -is your GitLab username, and `foo/bar` is the new GitLab namespace/project that +is your GitLab username, and `foo/bar` is the new GitLab namespace/project that will get created from your GitHub project. Subgroups are also possible: `foo/foo/bar`. diff --git a/doc/api/README.md b/doc/api/README.md index 1241801a81c..4f189c16673 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -55,6 +55,11 @@ following locations: - [V3 to V4](v3_to_v4.md) - [Version](version.md) +The following documentation is for the [internal CI API](ci/README.md): + +- [Builds](ci/builds.md) +- [Runners](ci/runners.md) + ## Road to GraphQL Going forward, we will start on moving to @@ -65,22 +70,20 @@ controller-specific endpoints. GraphQL has a number of benefits: 2. Callers of the API can request only what they need. 3. It is versioned by default. -It will co-exist with the current V4 REST API. If we have a V5 API, this should be -compatability layer on top of GraphQL. - -### Internal CI API +It will co-exist with the current v4 REST API. If we have a v5 API, this should +be a compatibility layer on top of GraphQL. -The following documentation is for the [internal CI API](ci/README.md): +## Authentication -- [Builds](ci/builds.md) -- [Runners](ci/runners.md) +Most API requests require authentication via a session cookie or token. For +those cases where it is not required, this will be mentioned in the documentation +for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md). -## Authentication +There are three types of access tokens available: -Most API requests require authentication via a session cookie or token. For those cases where it is not required, this will be mentioned in the documentation -for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md). -There are three types of tokens available: private tokens, OAuth 2 tokens, and personal -access tokens. +1. [OAuth2 tokens](#oauth2-tokens) +1. [Private tokens](#private-tokens) +1. [Personal access tokens](#personal-access-tokens) If authentication information is invalid or omitted, an error message will be returned with status code `401`: @@ -91,20 +94,13 @@ returned with status code `401`: } ``` -### Session Cookie +### Session cookie When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is set. The API will use this cookie for authentication if it is present, but using the API to generate a new session cookie is currently not supported. -### Private Tokens - -You need to pass a `private_token` parameter via query string or header. If passed as a -header, the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of -an underscore). You can find or reset your private token in your account page -(`/profile/account`). - -### OAuth 2 Tokens +### OAuth2 tokens You can use an OAuth 2 token to authenticate with the API by passing it either in the `access_token` parameter or in the `Authorization` header. @@ -117,30 +113,31 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api Read more about [GitLab as an OAuth2 client](oauth2.md). -### Personal Access Tokens +### Private tokens -> [Introduced][ce-3749] in GitLab 8.8. +Private tokens provide full access to the GitLab API. Anyone with access to +them can interact with GitLab as if they were you. You can find or reset your +private token in your account page (`/profile/account`). -You can create as many personal access tokens as you like from your GitLab -profile (`/profile/personal_access_tokens`); perhaps one for each application -that needs access to the GitLab API. +For examples of usage, [read the basic usage section](#basic-usage). -Once you have your token, pass it to the API using either the `private_token` -parameter or the `PRIVATE-TOKEN` header. +### Personal access tokens -> [Introduced][ce-5951] in GitLab 8.15. +Instead of using your private token which grants full access to your account, +personal access tokens could be a better fit because of their granular +permissions. -Personal Access Tokens can be created with one or more scopes that allow various actions -that a given token can perform. Although there are only two scopes available at the -moment – `read_user` and `api` – the groundwork has been laid to add more scopes easily. +Once you have your token, pass it to the API using either the `private_token` +parameter or the `PRIVATE-TOKEN` header. For examples of usage, +[read the basic usage section](#basic-usage). -At any time you can revoke any personal access token by just clicking **Revoke**. +[Read more about personal access tokens.][pat] ### Impersonation tokens > [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions. -Impersonation tokens are a type of [Personal Access Token](#personal-access-tokens) +Impersonation tokens are a type of [personal access token][pat] that can only be created by an admin for a specific user. They are a better alternative to using the user's password/private token @@ -149,9 +146,11 @@ or private token, since the password/token can change over time. Impersonation tokens are a great fit if you want to build applications or tools which authenticate with the API as a specific user. -For more information about the usage please refer to the +For more information, refer to the [users API](users.md#retrieve-user-impersonation-tokens) docs. +For examples of usage, [read the basic usage section](#basic-usage). + ### Sudo > Needs admin permissions. @@ -204,11 +203,16 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" ``` -## Basic Usage +## Basic usage API requests should be prefixed with `api` and the API version. The API version is defined in [`lib/api.rb`][lib-api-url]. +For endpoints that require [authentication](#authentication), you need to pass +a `private_token` parameter via query string or header. If passed as a header, +the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of +an underscore). + Example of a valid API request: ``` @@ -221,6 +225,12 @@ Example of a valid API request using cURL and authentication via header: curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" ``` +Example of a valid API request using cURL and authentication via a query string: + +```shell +curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK" +``` + The API uses JSON to serialize data. You don't need to specify `.json` at the end of an API URL. @@ -436,3 +446,4 @@ programming languages. Visit the [GitLab website] for a complete list. [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 [ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 [ce-9099]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9099 +[pat]: ../user/profile/personal_access_tokens.md diff --git a/doc/api/branches.md b/doc/api/branches.md index 325d0ea4ce3..dfaa7d6fab7 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -251,6 +251,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gi Will delete all branches that are merged into the project's default branch. +Protected branches will not be deleted as part of this operation. + ``` DELETE /projects/:id/repository/merged_branches ``` diff --git a/doc/api/issues.md b/doc/api/issues.md index 3f949ca5667..df5666bb7b6 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -221,7 +221,8 @@ GET /projects/:id/issues?search=issue+title+or+description | `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search project issues against their `title` and `description` | - +| `created_after` | datetime | no | Return issues created after the given time (inclusive) | +| `created_before` | datetime | no | Return issues created before the given time (inclusive) | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index cb22b67f556..3dc808c196d 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -26,6 +26,8 @@ Parameters: | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `milestone` | string | no | Return merge requests for a specific milestone | | `labels` | string | no | Return merge requests matching a comma separated list of labels | +| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) | +| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) | ```json [ diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md index 46fe64d382e..07cb64cb373 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -134,4 +134,4 @@ access_token = client.password.get_token('user@example.com', 'secret') puts access_token.token ``` -[personal access tokens]: ./README.md#personal-access-tokens
\ No newline at end of file +[personal access tokens]: ../user/profile/personal_access_tokens.md diff --git a/doc/api/session.md b/doc/api/session.md index 7dd504b67c5..f79eac11689 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -1,11 +1,9 @@ # Session API -## Deprecation Notice - -1. Starting in GitLab 8.11, this feature has been *disabled* for users with two-factor authentication turned on. -2. These users can access the API using [personal access tokens] instead. - ---- +>**Deprecation notice:** +Starting in GitLab 8.11, this feature has been **disabled** for users with +[two-factor authentication][2fa] turned on. These users can access the API +using [personal access tokens] instead. You can login with both GitLab and LDAP credentials in order to obtain the private token. @@ -52,4 +50,5 @@ Example response: } ``` -[personal access tokens]: ./README.md#personal-access-tokens +[2fa]: ../user/profile/account/two_factor_authentication.md +[personal access tokens]: ../user/profile/personal_access_tokens.md diff --git a/doc/api/users.md b/doc/api/users.md index f4167ba2605..b1ebd7b0c47 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -251,6 +251,7 @@ Parameters: - `can_create_group` (optional) - User can create groups - true or false - `confirm` (optional) - Require confirmation - true (default) or false - `external` (optional) - Flags the user as external - true or false(default) +- `avatar` (optional) - Image file for user's avatar ## User modification @@ -279,6 +280,7 @@ Parameters: - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false - `external` (optional) - Flags the user as external - true or false(default) +- `avatar` (optional) - Image file for user's avatar On password update, user will be forced to change it upon next login. Note, at the moment this method does only return a `404` error, @@ -804,7 +806,7 @@ Example response: It creates a new impersonation token. Note that only administrators can do this. You are only able to create impersonation tokens to impersonate the user and perform -both API calls and Git reads and writes. The user will not see these tokens in his profile +both API calls and Git reads and writes. The user will not see these tokens in their profile settings page. ``` diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 408d46a756c..f7c2a0ef0ca 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -282,9 +282,9 @@ which can be avoided if a different driver is used, for example `overlay`. > **Notes:** - This feature requires GitLab 8.8 and GitLab Runner 1.2. -- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need - to pass a personal access token instead of your password in order to login to - GitLab's Container Registry. +- Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need + to pass a [personal access token][pat] instead of your password in order to + login to GitLab's Container Registry. Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../user/project/container_registry.md). For example, @@ -409,3 +409,5 @@ Some things you should be aware of when using the Container Registry: [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities +[2fa]: ../../user/profile/account/two_factor_authentication.md +[pat]: ../../user/profile/personal_access_tokens.md diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 7709541ba9d..be4dea55c20 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -210,6 +210,18 @@ When the job is run, `tutum/wordpress` will be started and you will have access to it from your build container under the hostnames `tutum-wordpress` (requires GitLab Runner v1.1.0 or newer) and `tutum__wordpress`. +When using a private registry, the image name also includes a hostname and port +of the registry. + +```yaml +services: +- docker.example.com:5000/wordpress:latest +``` + +The service hostname will also include the registry hostname. Service will be +available under hostnames `docker.example.com-wordpress` (requires GitLab Runner v1.1.0 or newer) +and `docker.example.com__wordpress`. + *Note: hostname with underscores is not RFC valid and may cause problems in 3rd party applications.* The alias hostnames for the service are made from the image name following these diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md index a047e809788..5659a8c2a2a 100644 --- a/doc/ci/examples/code_climate.md +++ b/doc/ci/examples/code_climate.md @@ -27,7 +27,7 @@ download and analyze the report artifact in JSON format. For GitLab [Enterprise Edition Starter][ee] users, this information can be automatically extracted and shown right in the merge request widget. [Learn more on code quality -diffs in merge requests](http://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.md). +diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html). [cli]: https://github.com/codeclimate/codeclimate [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 73aee3c3f56..76d746155eb 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -117,21 +117,21 @@ lowest number of jobs currently running on shared Runners. We have following jobs in queue: -- job 1 for project 1 -- job 2 for project 1 -- job 3 for project 1 -- job 4 for project 2 -- job 5 for project 2 -- job 6 for project 3 +- Job 1 for Project 1 +- Job 2 for Project 1 +- Job 3 for Project 1 +- Job 4 for Project 2 +- Job 5 for Project 2 +- Job 6 for Project 3 With the fair usage algorithm jobs are assigned in following order: -1. We choose job 1, because project 1 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs -1. We choose job 4, because project 2 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs -1. We choose job 6, because project 3 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs -1. We choose job 2, because project 1 as other it runs 1 job -1. We choose job 5, because project 2 runs 1 job, where project 1 runs 2 jobs now -1. We choose job 3, because project 1 and runs 2 jobs +1. Job 1 is chosen first, because it has the lowest job number from projects with no running jobs (i.e. all projects) +1. Job 4 is next, because 4 is now the lowest job number from projects with no running jobs (Project 1 has a job running) +1. Job 6 is next, because 6 is now the lowest job number from projects with no running jobs (Projects 1 and 2 have jobs running) +1. Job 2 is next, because, of projects with the lowest number of jobs running (each has 1), it is the lowest job number +1. Job 5 is next, because Project 1 now has 2 jobs running, and between Projects 2 and 3, Job 5 is the lowest remaining job number +1. Lastly we choose Job 3... because it's the only job left --- @@ -139,23 +139,23 @@ With the fair usage algorithm jobs are assigned in following order: We have following jobs in queue: -- job 1 for project 1 -- job 2 for project 1 -- job 3 for project 1 -- job 4 for project 2 -- job 5 for project 2 -- job 6 for project 3 +- Job 1 for project 1 +- Job 2 for project 1 +- Job 3 for project 1 +- Job 4 for project 2 +- Job 5 for project 2 +- Job 6 for project 3 With the fair usage algorithm jobs are assigned in following order: -1. We choose job 1, because project 1 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs +1. Job 1 is chosen first, because it has the lowest job number from projects with no running jobs (i.e. all projects) 1. We finish job 1 -1. We choose job 2, because project 1 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs -1. We choose job 4, because project 2 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs +1. Job 2 is next, because, having finished Job 1, all projects have 0 jobs running again, and 2 is the lowest available job number +1. Job 4 is next, because with Project 1 running a job, 4 is the lowest number from projects running no jobs (Projects 2 and 3) 1. We finish job 4 -1. We choose job 5, because project 2 doesn't run currently any jobs and has the lowest job number from projects that doesn't run jobs -1. We choose job 6, because project 3 doesn't run currently any jobs -1. We choose job 3, because project 1, 2 and 3 runs exactly one job now +1. Job 5 is next, because having finished Job 4, Project 2 has no jobs running again +1. Job 6 is next, because Project 3 is the only project left with no running jobs +1. Lastly we choose Job 3... because, again, it's the only job left (who says 1 is the loneliest number?) ## Using shared Runners effectively diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md index 51b4b398f2c..899be9eae4b 100644 --- a/doc/development/limit_ee_conflicts.md +++ b/doc/development/limit_ee_conflicts.md @@ -166,8 +166,8 @@ For instance this kind of thing: = render 'projects/zen', f: form, attr: :description, classes: 'note-textarea', placeholder: "Write a comment or drag your files here...", - supports_slash_commands: !issuable.persisted? - = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? + supports_quick_actions: !issuable.persisted? + = render 'projects/notes/hints', supports_quick_actions: !issuable.persisted? .clearfix .error-alert - if issuable.is_a?(Issue) diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 77ba2a5fd87..161d2544169 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -122,7 +122,7 @@ limit can vary from installation to installation. As a result it's recommended you do not use more than 32 threads in a single migration. Usually 4-8 threads should be more than enough. -## Removing indices +## Removing indexes When removing an index make sure to use the method `remove_concurrent_index` instead of the regular `remove_index` method. The `remove_concurrent_index` method @@ -142,7 +142,7 @@ class MyMigration < ActiveRecord::Migration end ``` -## Adding indices +## Adding indexes If you need to add a unique index please keep in mind there is the possibility of existing duplicates being present in the database. This means that should @@ -222,6 +222,41 @@ add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8) add_column(:projects, :foo, :integer, default: 10, limit: 8) ``` +## Timestamp column type + +By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information. +The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method. +Also Rails converts the `:datetime` data type to the `timestamp` one. + +Example: + +```ruby +# timestamps +create_table :users do |t| + t.timestamps +end + +# add_timestamps +def up + add_timestamps :users +end + +# :datetime +def up + add_column :users, :last_sign_in, :datetime +end +``` + +Instead of using these methods one should use the following methods to store timestamps with timezones: + +* `add_timestamps_with_timezone` +* `timestamps_with_timezone` + +This ensures all timestamps have a time zone specified. This in turn means existing timestamps won't +suddenly use a different timezone when the system's timezone changes. It also makes it very clear which +timezone was used in the first place. + + ## Testing Make sure that your migration works with MySQL and PostgreSQL with data. An diff --git a/doc/development/testing.md b/doc/development/testing.md index 6d8b846d27f..cf3ea2ccfc2 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -25,7 +25,7 @@ records should use stubs/doubles as much as possible. | --------- | ---------- | -------------- | ----- | | `app/finders/` | `spec/finders/` | RSpec | | | `app/helpers/` | `spec/helpers/` | RSpec | | -| `app/db/{post_,}migrate/` | `spec/migrations/` | RSpec | | +| `app/db/{post_,}migrate/` | `spec/migrations/` | RSpec | More details at [`spec/migrations/README.md`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md). | | `app/policies/` | `spec/policies/` | RSpec | | | `app/presenters/` | `spec/presenters/` | RSpec | | | `app/routing/` | `spec/routing/` | RSpec | | diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index 88c56a1d17c..5ea08869a9b 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -1,7 +1,7 @@ # Installing GitLab on Kubernetes > Officially supported cloud providers are Google Container Service and Azure Container Service. -> Officially supported schedulers are Kubernetes and Terraform. +> Officially supported schedulers are Kubernetes, Terraform and Tectonic. The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is to take advantage of the official GitLab Helm charts. [Helm] is a package diff --git a/doc/integration/chat_commands.md b/doc/integration/chat_commands.md index c878dc7e650..2856992ee25 100644 --- a/doc/integration/chat_commands.md +++ b/doc/integration/chat_commands.md @@ -1,14 +1 @@ -# Chat Commands - -Chat commands in Mattermost and Slack (also called Slack slash commands) allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it. - -Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are: - - -| Command | Effect | -| ------- | ------ | -| `/project-name help` | Shows all available chat commands | -| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` | -| `/project-name issue show <id>` | Shows the issue with id `<id>` | -| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` | -| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
\ No newline at end of file +This document was moved to [integration/slash_commands.md](slash_commands.md). diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md new file mode 100644 index 00000000000..5d880ba785c --- /dev/null +++ b/doc/integration/slash_commands.md @@ -0,0 +1,14 @@ +# Slash Commands + +Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it. + +Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are: + + +| Command | Effect | +| ------- | ------ | +| `/project-name help` | Shows all available slash commands | +| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` | +| `/project-name issue show <id>` | Shows the issue with id `<id>` | +| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` | +| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment | diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md new file mode 100644 index 00000000000..a712ce5a8b1 --- /dev/null +++ b/doc/update/9.3-to-9.4.md @@ -0,0 +1,317 @@ +# From 9.3 to 9.4 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be +sure to upgrade your interpreter if necessary. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz +echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz +cd ruby-2.3.3 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Update Node + +GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and +it has a minimum requirement of node v4.3.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v4.3.0` you will need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the nodejs.org website. + +<https://nodejs.org/en/download/> + + +Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage +JavaScript dependencies. + +```bash +curl --location https://yarnpkg.com/install.sh | bash - +``` + +More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). + +### 5. Update Go + +NOTE: GitLab 9.4 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be +sure to upgrade your installation if necessary + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.8.3.linux-amd64.tar.gz +``` + +### 6. Get latest code + +```bash +cd /home/git/gitlab + +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 9-4-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 9-4-stable-ee +``` + +### 5. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell + +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) +sudo -u git -H bin/compile +``` + +### 6. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.8](https://golang.org/dl) which should already be on your system from +GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + +```bash +cd /home/git/gitlab-workhorse + +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) +sudo -u git -H make +``` + +### 7. Update Gitaly + +If you have not yet set up Gitaly then follow [Gitaly section of the installation +guide](../install/installation.md#install-gitaly). + +#### Check Gitaly configuration + +Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly +configuration file may contain syntax errors. The block name +`[[storages]]`, which may occur more than once in your `config.toml` +file, should be `[[storage]]` instead. + +```shell +cd /home/git/gitaly +sudo -u git -H editor config.toml +``` + +#### Compile Gitaly + +```shell +cd /home/git/gitaly +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) +sudo -u git -H make +``` + +### 10. Update configuration files + +#### New configuration options for `gitlab.yml` + +There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +cd /home/git/gitlab + +git diff origin/9-3-stable:config/gitlab.yml.example origin/9-4-stable:config/gitlab.yml.example +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +cd /home/git/gitlab + +# For HTTPS configurations +git diff origin/9-3-stable:lib/support/nginx/gitlab-ssl origin/9-4-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/9-3-stable:lib/support/nginx/gitlab origin/9-4-stable:lib/support/nginx/gitlab +``` + +If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx +configuration as GitLab application no longer handles setting it. + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-3-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`: + +```sh +cd /home/git/gitlab + +git diff origin/9-3-stable:lib/support/init.d/gitlab.default.example origin/9-4-stable:lib/support/init.d/gitlab.default.example +``` + +Ensure you're still up-to-date with the latest init script changes: + +```bash +cd /home/git/gitlab + +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +For Ubuntu 16.04.1 LTS: + +```bash +sudo systemctl daemon-reload +``` + +### 11. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production + +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). + +### 12. Start application + +```bash +sudo service gitlab start +sudo service nginx restart +``` + +### 13. Check application status + +Check if GitLab and its environment are configured correctly: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +``` + +To make sure you didn't miss anything run a more thorough check: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (9.3) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 9.2 to 9.3](9.2-to-9.3.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. + +[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/config/gitlab.yml.example +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/lib/support/init.d/gitlab.default.example diff --git a/doc/user/admin_area/monitoring/convdev.md b/doc/user/admin_area/monitoring/convdev.md new file mode 100644 index 00000000000..3d93c7557a4 --- /dev/null +++ b/doc/user/admin_area/monitoring/convdev.md @@ -0,0 +1,29 @@ +# Conversational Development Index + +> [Introduced][ce-30469] in GitLab 9.3. + +Conversational Development Index (ConvDev) gives you an overview of your entire +instance's feature usage, from idea to production. It looks at your usage in the +past 30 days, averaged over the number of active users in that time period. It also +provides a lead score per feature, which is calculated based on GitLab's analysis +of top performing instances, based on [usage ping data][ping] that GitLab has +collected. Your score is compared to the lead score, expressed as a percentage. +The overall index score is an average over all your feature scores. + +![ConvDev index](img/convdev_index.png) + +The page also provides helpful links to articles and GitLab docs, to help you +improve your scores. + +Your GitLab instance's usage ping must be activated in order to use this feature. +Usage ping data is aggregated on GitLab's servers for analysis. Your usage +information is **not sent** to any other GitLab instances. + +If you have just started using GitLab, it may take a few weeks for data to be +collected before this feature is available. + +This feature is accessible only to a system admin, at +**Admin area > Monitoring > ConvDev Index**. + +[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469 +[ping]: ../settings/usage_statistics.md#usage-ping diff --git a/doc/user/admin_area/monitoring/img/convdev_index.png b/doc/user/admin_area/monitoring/img/convdev_index.png Binary files differnew file mode 100644 index 00000000000..4e47ff2228d --- /dev/null +++ b/doc/user/admin_area/monitoring/img/convdev_index.png diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index f3745d0efa7..d874688cc29 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -3,7 +3,8 @@ GitLab Inc. will periodically collect information about your instance in order to perform various actions. -All statistics are opt-out, you can disable them from the admin panel. +All statistics are opt-out, you can enable/disable them from the admin panel +under **Admin area > Settings > Usage statistics**. ## Version check diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 59e343ebe51..8b1d299484c 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -10,7 +10,7 @@ You can leave a comment in the following places: - commits - commit diffs -The comment area supports [Markdown] and [slash commands]. One can edit their +The comment area supports [Markdown] and [quick actions]. One can edit their own comment at any time, and anyone with [Master access level][permissions] or higher can also edit a comment made by someone else. @@ -146,5 +146,5 @@ comments in greater detail. [discussion-view]: img/discussion_view.png [discussions-resolved]: img/discussions_resolved.png [markdown]: ../markdown.md -[slash commands]: ../project/slash_commands.md +[quick actions]: ../project/quick_actions.md [permissions]: ../permissions.md diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 3fda47b9e34..3d47e644ad2 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -89,6 +89,7 @@ group. | Create project in group | | | | ✓ | ✓ | | Manage group members | | | | | ✓ | | Remove group | | | | | ✓ | +| Manage group labels | | ✓ | ✓ | ✓ | ✓ | ## External Users diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index fb69d934ae1..590c3f862fb 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -125,23 +125,14 @@ applications and U2F devices. ## Personal access tokens When 2FA is enabled, you can no longer use your normal account password to -authenticate with Git over HTTPS on the command line, you must use a personal -access token instead. - -1. Log in to your GitLab account. -1. Go to your **Profile Settings**. -1. Go to **Access Tokens**. -1. Choose a name and expiry date for the token. -1. Click on **Create Personal Access Token**. -1. Save the personal access token somewhere safe. - -When using Git over HTTPS on the command line, enter the personal access token -into the password field. +authenticate with Git over HTTPS on the command line or when using +[GitLab's API][api], you must use a [personal access token][pat] instead. ## Recovery options To disable two-factor authentication on your account (for example, if you have lost your code generation device) you can: + * [Use a saved recovery code](#use-a-saved-recovery-code) * [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh) * [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account) @@ -154,8 +145,9 @@ codes. If you saved these codes, you can use one of them to sign in. To use a recovery code, enter your username/email and password on the GitLab sign-in page. When prompted for a two-factor code, enter the recovery code. -> **Note:** Once you use a recovery code, you cannot re-use it. You can still - use the other recovery codes you saved. +>**Note:** +Once you use a recovery code, you cannot re-use it. You can still use the other +recovery codes you saved. ### Generate new recovery codes using SSH @@ -190,11 +182,14 @@ a new set of recovery codes with SSH. two-factor code. Then, visit your Profile Settings and add a new device so you do not lose access to your account again. ``` -3. Go to the GitLab sign-in page and enter your username/email and password. When prompted for a two-factor code, enter one of the recovery codes obtained -from the command-line output. -> **Note:** After signing in, visit your **Profile Settings -> Account** immediately to set up two-factor authentication with a new - device. +3. Go to the GitLab sign-in page and enter your username/email and password. + When prompted for a two-factor code, enter one of the recovery codes obtained + from the command-line output. + +>**Note:** +After signing in, visit your **Profile settings > Account** immediately to set +up two-factor authentication with a new device. ### Ask a GitLab administrator to disable two-factor authentication on your account @@ -206,23 +201,23 @@ Sign in and re-enable two-factor authentication as soon as possible. ## Note to GitLab administrators - You need to take special care to that 2FA keeps working after -[restoring a GitLab backup](../../../raketasks/backup_restore.md). - + [restoring a GitLab backup](../../../raketasks/backup_restore.md). - To ensure 2FA authorizes correctly with TOTP server, you may want to ensure -your GitLab server's time is synchronized via a service like NTP. Otherwise, -you may have cases where authorization always fails because of time differences. - -[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en -[FreeOTP]: https://freeotp.github.io/ -[YubiKey]: https://www.yubico.com/products/yubikey-hardware/ - + your GitLab server's time is synchronized via a service like NTP. Otherwise, + you may have cases where authorization always fails because of time differences. - The GitLab U2F implementation does _not_ work when the GitLab instance is accessed from -multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at -the time of registration, and cannot be used for other hostnames/FQDNs. + multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at + the time of registration, and cannot be used for other hostnames/FQDNs. For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`: - The user logs in via `first.host.xyz` and registers their U2F key. - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication suceeds. - - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because + - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because the U2F key has only been registered on `first.host.xyz`. + +[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en +[FreeOTP]: https://freeotp.github.io/ +[YubiKey]: https://www.yubico.com/products/yubikey-hardware/ +[api]: ../../../api/README.md +[pat]: ../personal_access_tokens.md diff --git a/doc/user/profile/img/personal_access_tokens.png b/doc/user/profile/img/personal_access_tokens.png Binary files differnew file mode 100644 index 00000000000..6aa63dbe342 --- /dev/null +++ b/doc/user/profile/img/personal_access_tokens.png diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md new file mode 100644 index 00000000000..9488ce1ef30 --- /dev/null +++ b/doc/user/profile/personal_access_tokens.md @@ -0,0 +1,57 @@ +# Personal access tokens + +> [Introduced][ce-3749] in GitLab 8.8. + +Personal access tokens are useful if you need access to the [GitLab API][api]. +Instead of using your private token which grants full access to your account, +personal access tokens could be a better fit because of their +[granular permissions](#limiting-scopes-of-a-personal-access-token). + +You can also use them to authenticate against Git over HTTP. They are the only +accepted method of authentication when you have +[Two-Factor Authentication (2FA)][2fa] enabled. + +Once you have your token, [pass it to the API][usage] using either the +`private_token` parameter or the `PRIVATE-TOKEN` header. + +## Creating a personal access token + +You can create as many personal access tokens as you like from your GitLab +profile. + +1. Log in to your GitLab account. +1. Go to your **Profile settings**. +1. Go to **Access tokens**. +1. Choose a name and optionally an expiry date for the token. +1. Choose the [desired scopes](#limiting-scopes-of-a-personal-access-token). +1. Click on **Create personal access token**. +1. Save the personal access token somewhere safe. Once you leave or refresh + the page, you won't be able to access it again. + +![Personal access tokens page](img/personal_access_tokens.png) + +## Revoking a personal access token + +At any time, you can revoke any personal access token by just clicking the +respective **Revoke** button under the 'Active personal access tokens' area. + +## Limiting scopes of a personal access token + +Personal access tokens can be created with one or more scopes that allow various +actions that a given token can perform. The available scopes are depicted in +the following table. + +| Scope | Description | +| ----- | ----------- | +|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). | +| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. | +| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). | + +[2fa]: ../account/two_factor_authentication.md +[api]: ../../api/README.md +[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 +[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 +[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845 +[container registry]: ../project/container_registry.md +[users]: ../../api/users.md +[usage]: ../../api/README.md#basic-usage diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md index 75ea911b9bc..629d69d8aea 100644 --- a/doc/user/project/container_registry.md +++ b/doc/user/project/container_registry.md @@ -8,8 +8,8 @@ Registry across your GitLab instance, visit the [administrator documentation](../../administration/container_registry.md). - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need - to pass a personal access token instead of your password in order to login to - GitLab's Container Registry. + to pass a [personal access token][pat] instead of your password in order to + login to GitLab's Container Registry. - Multiple level image names support was added in GitLab 9.1 With the Docker Container Registry integrated into GitLab, every project can @@ -114,12 +114,11 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do ## Using with private projects -If a project is private, credentials will need to be provided for authorization. -The preferred way to do this, is by using personal access tokens, which can be -created under `/profile/personal_access_tokens`. The minimal scope needed is: -`read_registry`. +> [Introduced][ce-11845] in GitLab 9.3. -This feature was introduced in GitLab 9.3. +If a project is private, credentials will need to be provided for authorization. +The preferred way to do this, is by using [personal access tokens][pat]. +The minimal scope needed is `read_registry`. ## Troubleshooting the GitLab Container Registry @@ -264,4 +263,6 @@ The solution: check the [IAM permissions again](https://docs.docker.com/registry Once the right permissions were set, the error will go away. [ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040 +[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845 [docker-docs]: https://docs.docker.com/engine/userguide/intro/ +[pat]: ../profile/personal_access_tokens.md diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png Binary files differindex c74351b57b8..e69376f74c4 100644 --- a/doc/user/project/integrations/img/jira_service_page.png +++ b/doc/user/project/integrations/img/jira_service_page.png diff --git a/doc/user/project/integrations/img/merge_request_performance.png b/doc/user/project/integrations/img/merge_request_performance.png Binary files differindex 93b2626fed7..eba6515a6ae 100644 --- a/doc/user/project/integrations/img/merge_request_performance.png +++ b/doc/user/project/integrations/img/merge_request_performance.png diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index a048260b033..cf03f2a9033 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -76,7 +76,7 @@ We have split this stage in steps so it is easier to follow. ![JIRA add user to group](img/jira_add_user_to_group.png) ---- + --- The JIRA configuration is over. Write down the new JIRA username and its password as they will be needed when configuring GitLab in the next section. @@ -98,14 +98,14 @@ in the table below. | Field | Description | | ----- | ----------- | | `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. | -| `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. | -| `Project key` | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. | +| `JIRA API URL` | The base URL to the JIRA instance API. E.g., `https://jira-api.example.com`. This is optional. If not entered, the Web URL value be used. | +| `Project key` | Put a JIRA project key (in uppercase), e.g. `MARS` in this field. This is only for testing the configuration settings. JIRA integration in GitLab works with _all_ JIRA projects in your JIRA instance. This field will be removed in a future release. | | `Username` | The user name created in [configuring JIRA step](#configuring-jira). | | `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). | | `JIRA issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** | After saving the configuration, your GitLab project will be able to interact -with the linked JIRA project. +with all JIRA projects in your JIRA instance. ![JIRA service page](img/jira_service_page.png) diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index d3fb5916dc6..86ceb14b965 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -167,15 +167,15 @@ environment which has had a successful deployment. ## Determining the performance impact of a merge > [Introduced][ce-10408] in GitLab 9.2. +> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages. Developers can view the performance impact of their changes within the merge -request workflow. When a source branch has been deployed to an environment, a -sparkline will appear showing the average memory consumption of the app. The dot +request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot indicates when the current changes were deployed, with up to 30 minutes of -performance data displayed before and after. The sparkline will be updated after +performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after each commit has been deployed. -Once merged and the target branch has been redeployed, the sparkline will switch +Once merged and the target branch has been redeployed, the metrics will switch to show the new environments this revision has been deployed to. Performance data will be available for the duration it is persisted on the diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md index 54e0ee611cb..c267da69bb3 100644 --- a/doc/user/project/integrations/slack_slash_commands.md +++ b/doc/user/project/integrations/slack_slash_commands.md @@ -2,7 +2,7 @@ > Introduced in GitLab 8.15 -Slack slash commands (also known as chat commmands) allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab. +Slack slash commands allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab. > Note: GitLab can also send events (e.g. issue created) to Slack as notifications. This is the separately configured [Slack Notifications Service](slack.md). @@ -20,4 +20,4 @@ Slack slash commands (also known as chat commmands) allow you to control GitLab ## Usage -You can now use the [Slack slash commands](../../../integration/chat_commands.md).
\ No newline at end of file +You can now use the [Slack slash commands](../../../integration/slash_commands.md). diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 5aa8337b75d..ebea7062ecb 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -31,10 +31,11 @@ Below is a table of the definitions used for GitLab's Issue Board. | **Card** | Every card represents an issue and it is shown under the list for which it has a label. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. You can re-order cards within a list. | There are two types of lists, the ones you create based on your labels, and -one default: +two defaults: - Label list: a list based on a label. It shows all opened issues with that label. -- **Done** (default): shows all closed issues. Always appears on the very right. +- **Backlog** (default): shows all open issues that does not belong to one of lists. Always appears on the very left. +- **Closed** (default): shows all closed issues. Always appears on the very right. ![GitLab Issue Board](img/issue_board.png) diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md index 1760b182114..208be7d0ed5 100644 --- a/doc/user/project/issues/confidential_issues.md +++ b/doc/user/project/issues/confidential_issues.md @@ -43,9 +43,8 @@ next to the issues that are marked as confidential. --- -Likewise, while inside the issue, you can see the eye-slash icon right next to -the issue number, but there is also an indicator in the comment area that the -issue you are commenting on is confidential. +While inside the issue, you can see a persistent dark banner at the top of the +screen. ![Confidential issue page](img/confidential_issues_issue_page.png) diff --git a/doc/user/project/issues/img/confidential_issues_issue_page.png b/doc/user/project/issues/img/confidential_issues_issue_page.png Binary files differindex f04ec8ff32b..91f7cc8d3ca 100755 --- a/doc/user/project/issues/img/confidential_issues_issue_page.png +++ b/doc/user/project/issues/img/confidential_issues_issue_page.png diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index ba843201e1a..294176e61f9 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -68,7 +68,7 @@ This feature is available only in [GitLab Enterprise Edition](https://about.gitl - Spend: add the time spent on the implementation of that issue > **Note:** -both estimate and spend times are set via [GitLab Slash Commands](../slash_commands.md). +both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md). Learn more on the [Time Tracking documentation](https://docs.gitlab.com/ee/workflow/time_tracking.html). @@ -147,7 +147,7 @@ or in the issue thread. #### 15. Award emoji -- Award an emoji to that issue. +- Award an emoji to that issue. > **Tip:** Posting "+1" as comments in threads spam all diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index e9512497d6c..271adee7da1 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -212,9 +212,9 @@ Container Registries for private projects. access token created explicitly for this purpose). This issue is resolved with latest changes in GitLab Runner 1.8 which receives GitLab credentials with build data. -- Starting with GitLab 8.12, if you have 2FA enabled in your account, you need - to pass a personal access token instead of your password in order to login to - GitLab's Container Registry. +- Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need + to pass a [personal access token][pat] instead of your password in order to + login to GitLab's Container Registry. Your jobs can access all container images that you would normally have access to. The only implication is that you can push to the Container Registry of the @@ -239,3 +239,5 @@ test: [update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update [workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse [jobenv]: ../../ci/variables/README.md#predefined-variables-environment-variables +[2fa]: ../profile/account/two_factor_authentication.md +[pat]: ../profile/personal_access_tokens.md diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md new file mode 100644 index 00000000000..19b51c83222 --- /dev/null +++ b/doc/user/project/quick_actions.md @@ -0,0 +1,39 @@ +# GitLab quick actions + +Quick actions are textual shortcuts for common actions on issues or merge +requests that are usually done by clicking buttons or dropdowns in GitLab's UI. +You can enter these commands while creating a new issue or merge request, and +in comments. Each command should be on a separate line in order to be properly +detected and executed. The commands are removed from the issue, merge request or +comment body before it is saved and will not be visible to anyone else. + +Below is a list of all of the available commands and descriptions about what they +do. + +| Command | Action | +|:---------------------------|:-------------| +| `/close` | Close the issue or merge request | +| `/reopen` | Reopen the issue or merge request | +| `/merge` | Merge (when pipeline succeeds) | +| `/title <New title>` | Change title | +| `/assign @username` | Assign | +| `/unassign` | Remove assignee | +| `/milestone %milestone` | Set milestone | +| `/remove_milestone` | Remove milestone | +| `/label ~foo ~"bar baz"` | Add label(s) | +| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) | +| `/relabel ~foo ~"bar baz"` | Replace all label(s) | +| `/todo` | Add a todo | +| `/done` | Mark todo as done | +| `/subscribe` | Subscribe | +| `/unsubscribe` | Unsubscribe | +| <code>/due <in 2 days | this Friday | December 31st></code> | Set due date | +| `/remove_due_date` | Remove due date | +| `/wip` | Toggle the Work In Progress status | +| <code>/estimate <1w 3d 2h 14m></code> | Set time estimate | +| `/remove_estimate` | Remove estimated time | +| <code>/spend <1h 30m | -1h 5m></code> | Add or subtract spent time | +| `/remove_time_spent` | Remove time spent | +| `/target_branch <Branch Name>` | Set target branch for current merge request | +| `/award :emoji:` | Toggle award for :emoji: | +| `/board_move ~column` | Move issue to column on the board | diff --git a/doc/user/project/repository/branches/img/delete_merged_branches.png b/doc/user/project/repository/branches/img/delete_merged_branches.png Binary files differnew file mode 100644 index 00000000000..1856a624f74 --- /dev/null +++ b/doc/user/project/repository/branches/img/delete_merged_branches.png diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md new file mode 100644 index 00000000000..1948627ee79 --- /dev/null +++ b/doc/user/project/repository/branches/index.md @@ -0,0 +1,17 @@ +# Branches + +## Delete merged branches + +> [Introduced][ce-6449] in GitLab 8.14. + +![Delete merged branches](img/delete_merged_branches.png) + +This feature allows merged branches to be deleted in bulk. Only branches that +have been merged and [are not protected][protected] will be deleted as part of +this operation. + +It's particularly useful to clean up old branches that were not deleting +automatically when a merge request was merged. + +[ce-6449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449 "Add button to delete all merged branches" +[protected]: ../../protected_branches.md diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 58d2fd76c61..35960ade3d4 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -27,14 +27,15 @@ with all their related data and be moved into a new GitLab instance. | GitLab version | Import/Export version | | -------- | -------- | -| 9.2.0 to current | 0.1.7 | -| 8.17.0 | 0.1.6 | -| 8.13.0 | 0.1.5 | -| 8.12.0 | 0.1.4 | -| 8.10.3 | 0.1.3 | -| 8.10.0 | 0.1.2 | -| 8.9.5 | 0.1.1 | -| 8.9.0 | 0.1.0 | +| 9.4.0 to current | 0.1.8 | +| 9.2.0 | 0.1.7 | +| 8.17.0 | 0.1.6 | +| 8.13.0 | 0.1.5 | +| 8.12.0 | 0.1.4 | +| 8.10.3 | 0.1.3 | +| 8.10.0 | 0.1.2 | +| 8.9.5 | 0.1.1 | +| 8.9.0 | 0.1.0 | > The table reflects what GitLab version we updated the Import/Export version at. > For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3) diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md index 08452ca75cd..e9103a3f49c 100644 --- a/doc/user/project/slash_commands.md +++ b/doc/user/project/slash_commands.md @@ -1,39 +1 @@ -# GitLab slash commands - -Slash commands are textual shortcuts for common actions on issues or merge -requests that are usually done by clicking buttons or dropdowns in GitLab's UI. -You can enter these commands while creating a new issue or merge request, and -in comments. Each command should be on a separate line in order to be properly -detected and executed. The commands are removed from the issue, merge request or -comment body before it is saved and will not be visible to anyone else. - -Below is a list of all of the available commands and descriptions about what they -do. - -| Command | Action | -|:---------------------------|:-------------| -| `/close` | Close the issue or merge request | -| `/reopen` | Reopen the issue or merge request | -| `/merge` | Merge (when pipeline succeeds) | -| `/title <New title>` | Change title | -| `/assign @username` | Assign | -| `/unassign` | Remove assignee | -| `/milestone %milestone` | Set milestone | -| `/remove_milestone` | Remove milestone | -| `/label ~foo ~"bar baz"` | Add label(s) | -| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) | -| `/relabel ~foo ~"bar baz"` | Replace all label(s) | -| `/todo` | Add a todo | -| `/done` | Mark todo as done | -| `/subscribe` | Subscribe | -| `/unsubscribe` | Unsubscribe | -| <code>/due <in 2 days | this Friday | December 31st></code> | Set due date | -| `/remove_due_date` | Remove due date | -| `/wip` | Toggle the Work In Progress status | -| <code>/estimate <1w 3d 2h 14m></code> | Set time estimate | -| `/remove_estimate` | Remove estimated time | -| <code>/spend <1h 30m | -1h 5m></code> | Add or subtract spent time | -| `/remove_time_spent` | Remove time spent | -| `/target_branch <Branch Name>` | Set target branch for current merge request | -| `/award :emoji:` | Toggle award for :emoji: | -| `/board_move ~column` | Move issue to column on the board | +This document was moved to [user/project/quick_actions.md](quick_actions.md). diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 604c7d5cefb..54d4028a50a 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -21,7 +21,7 @@ - [Project users](add-user/add-user.md) - [Protected branches](../user/project/protected_branches.md) - [Protected tags](../user/project/protected_tags.md) -- [Slash commands](../user/project/slash_commands.md) +- [Quick Actions](../user/project/quick_actions.md) - [Sharing a project with a group](share_with_group.md) - [Share projects with other groups](share_projects_with_other_groups.md) - [Time tracking](time_tracking.md) diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md index de12994c516..bfe87bb2ceb 100644 --- a/doc/workflow/time_tracking.md +++ b/doc/workflow/time_tracking.md @@ -21,13 +21,13 @@ below. ## How to enter data -Time Tracking uses two [slash commands] that GitLab introduced with this new +Time Tracking uses two [quick actions] that GitLab introduced with this new feature: `/spend` and `/estimate`. -Slash commands can be used in the body of an issue or a merge request, but also +Quick actions can be used in the body of an issue or a merge request, but also in a comment in both an issue or a merge request. -Below is an example of how you can use those new slash commands inside a comment. +Below is an example of how you can use those new quick actions inside a comment. ![Time tracking example in a comment](time-tracking/time-tracking-example.png) @@ -70,4 +70,4 @@ The following time units are available: Default conversion rates are 1w = 5d and 1d = 8h. [landing]: https://about.gitlab.com/features/time-tracking -[slash-commands]: ../user/project/slash_commands.md +[quick actions]: ../user/project/quick_actions.md diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index dfd0bc13305..2324edda975 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -34,8 +34,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps page.within '.awards' do expect do page.find('.js-emoji-btn.active').click - sleep 0.3 - end.to change{ page.all(".award-control.js-emoji-btn").size }.from(3).to(2) + wait_for_requests + end.to change { page.all(".award-control.js-emoji-btn").size }.from(3).to(2) end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a836df3dc81..412443a2405 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -603,6 +603,9 @@ module API expose :plantuml_url expose :terminal_max_session_time expose :polling_interval_multiplier + expose :help_page_hide_commercial_content + expose :help_page_text + expose :help_page_support_url end class Release < Grape::Entity @@ -804,7 +807,11 @@ module API end class Image < Grape::Entity - expose :name + expose :name, :entrypoint + end + + class Service < Image + expose :alias, :command end class Artifacts < Grape::Entity @@ -848,7 +855,7 @@ module API expose :variables expose :steps, using: Step expose :image, using: Image - expose :services, using: Image + expose :services, using: Service expose :artifacts, using: Artifacts expose :cache, using: Cache expose :credentials, using: Credentials diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index d3732d67622..5e9cf5e68b1 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -10,6 +10,10 @@ module API set_project unless defined?(@project) @project end + + def redirected_path + @redirected_path + end def ssh_authentication_abilities [ @@ -38,8 +42,9 @@ module API def set_project if params[:gl_repository] @project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository]) + @redirected_path = nil else - @project, @wiki = Gitlab::RepoPath.parse(params[:project]) + @project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project]) end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 38631953014..9ec418edea4 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -34,7 +34,7 @@ module API access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess access_checker = access_checker_klass - .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) + .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path) begin access_checker.check(params[:action], params[:changes]) @@ -86,8 +86,16 @@ module API } end + get "/broadcast_messages" do + if messages = BroadcastMessage.current + present messages, with: Entities::BroadcastMessage + else + [] + end + end + get "/broadcast_message" do - if message = BroadcastMessage.current + if message = BroadcastMessage.current.last present message, with: Entities::BroadcastMessage else {} diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 78db960ae28..09dca0dff8b 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -27,6 +27,8 @@ module API optional :milestone, type: String, desc: 'Return issues for a specific milestone' optional :iids, type: Array[Integer], desc: 'The IID array of issues' optional :search, type: String, desc: 'Search issues for text present in the title or description' + optional :created_after, type: DateTime, desc: 'Return issues created after the specified time' + optional :created_before, type: DateTime, desc: 'Return issues created before the specified time' use :pagination end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 710deba5ae3..1118fc7465b 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -72,6 +72,8 @@ module API optional :iids, type: Array[Integer], desc: 'The IID array of merge requests' optional :milestone, type: String, desc: 'Return merge requests for a specific milestone' optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time' + optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time' use :pagination end get ":id/merge_requests" do @@ -97,7 +99,7 @@ module API authorize! :create_merge_request, user_project mr_params = declared_params(include_missing: false) - mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? + mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index a3ea619a2fb..3541d3c95fb 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -117,7 +117,7 @@ module API finder_params = { project_id: user_project.id, milestone_title: milestone.title, - sort: 'position_asc' + sort: 'label_priority' } issues = IssuesFinder.new(current_user, finder_params).execute @@ -140,7 +140,7 @@ module API finder_params = { project_id: user_project.id, milestone_title: milestone.title, - sort: 'position_asc' + sort: 'label_priority' } merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute diff --git a/lib/api/services.rb b/lib/api/services.rb index 47bd9940f77..7488f95a9b7 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -685,7 +685,7 @@ module API trigger_services.each do |service_slug, settings| helpers do - def chat_command_service(project, service_slug, params) + def slash_command_service(project, service_slug, params) project.services.active.where(template: false).find do |service| service.try(:token) == params[:token] && service.to_param == service_slug.underscore end @@ -710,7 +710,7 @@ module API # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project - service = chat_command_service(project, service_slug, params) + service = slash_command_service(project, service_slug, params) result = service.try(:trigger, params) if result diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 25027c3b114..d598f9a62a2 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -39,7 +39,9 @@ module API :email_author_in_body, :enabled_git_access_protocol, :gravatar_enabled, + :help_page_hide_commercial_content, :help_page_text, + :help_page_support_url, :home_page_url, :housekeeping_enabled, :html_emails_enabled, @@ -101,7 +103,9 @@ module API optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page' optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out' optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application' + optional :help_page_hide_commercial_content, type: Boolean, desc: 'Hide marketing-related entries from help' optional :help_page_text, type: String, desc: 'Custom text displayed on the help page' + optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page' optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects' given shared_runners_enabled: ->(val) { val } do requires :shared_runners_text, type: String, desc: 'Shared runners text ' diff --git a/lib/api/users.rb b/lib/api/users.rb index dda64715ee1..7257ecb5b67 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -29,6 +29,7 @@ module API optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' + optional :avatar, type: File, desc: 'Avatar image for user' all_or_none_of :extern_uid, :provider end end diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb index 118c6df6549..2d13d6fabfd 100644 --- a/lib/api/v3/services.rb +++ b/lib/api/v3/services.rb @@ -608,7 +608,7 @@ module API trigger_services.each do |service_slug, settings| helpers do - def chat_command_service(project, service_slug, params) + def slash_command_service(project, service_slug, params) project.services.active.where(template: false).find do |service| service.try(:token) == params[:token] && service.to_param == service_slug.underscore end @@ -633,7 +633,7 @@ module API # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project - service = chat_command_service(project, service_slug, params) + service = slash_command_service(project, service_slug, params) result = service.try(:trigger, params) if result diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 1e2536231d8..279fca8d043 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -171,7 +171,7 @@ module Banzai collection.where(id: to_query).each { |row| cache[row.id] = row } end - cache.values_at(*ids) + cache.values_at(*ids).compact else collection.where(id: ids) end diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 792ff628b09..6b82b2b4f13 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -45,7 +45,21 @@ module Ci expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? } expose :options do |model| - model.options + # This part ensures that output of old API is still the same after adding support + # for extended docker configuration options, used by new API + # + # I'm leaving this here, not in the model, because it should be removed at the same time + # when old API will be removed (planned for August 2017). + model.options.dup.tap do |options| + options[:image] = options[:image][:name] if options[:image].is_a?(Hash) + options[:services].map! do |service| + if service.is_a?(Hash) + service[:name] + else + service + end + end + end end expose :timeout do |model| diff --git a/lib/github/import.rb b/lib/github/import.rb index 9c7eb965f93..b20614b3060 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -92,7 +92,7 @@ module Github end def fetch_wiki_repository - wiki_url = "https://{options.fetch(:token)}@github.com/#{repo}.wiki.git" + wiki_url = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git" wiki_path = "#{project.path_with_namespace}.wiki" unless project.wiki.repository_exists? diff --git a/lib/gitlab/ci/build/image.rb b/lib/gitlab/ci/build/image.rb index c62aeb60fa9..b88b2e36d53 100644 --- a/lib/gitlab/ci/build/image.rb +++ b/lib/gitlab/ci/build/image.rb @@ -2,7 +2,7 @@ module Gitlab module Ci module Build class Image - attr_reader :name + attr_reader :alias, :command, :entrypoint, :name class << self def from_image(job) @@ -21,7 +21,14 @@ module Gitlab end def initialize(image) - @name = image + if image.is_a?(String) + @name = image + elsif image.is_a?(Hash) + @alias = image[:alias] + @command = image[:command] + @entrypoint = image[:entrypoint] + @name = image[:name] + end end def valid? diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb index b5050257688..897dcff8012 100644 --- a/lib/gitlab/ci/config/entry/image.rb +++ b/lib/gitlab/ci/config/entry/image.rb @@ -8,8 +8,36 @@ module Gitlab class Image < Node include Validatable + ALLOWED_KEYS = %i[name entrypoint].freeze + validations do - validates :config, type: String + validates :config, hash_or_string: true + validates :config, allowed_keys: ALLOWED_KEYS + + validates :name, type: String, presence: true + validates :entrypoint, type: String, allow_nil: true + end + + def hash? + @config.is_a?(Hash) + end + + def string? + @config.is_a?(String) + end + + def name + value[:name] + end + + def entrypoint + value[:entrypoint] + end + + def value + return { name: @config } if string? + return @config if hash? + {} end end end diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb new file mode 100644 index 00000000000..b52faf48b58 --- /dev/null +++ b/lib/gitlab/ci/config/entry/service.rb @@ -0,0 +1,34 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a configuration of Docker service. + # + class Service < Image + include Validatable + + ALLOWED_KEYS = %i[name entrypoint command alias].freeze + + validations do + validates :config, hash_or_string: true + validates :config, allowed_keys: ALLOWED_KEYS + + validates :name, type: String, presence: true + validates :entrypoint, type: String, allow_nil: true + validates :command, type: String, allow_nil: true + validates :alias, type: String, allow_nil: true + end + + def alias + value[:alias] + end + + def command + value[:command] + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/services.rb b/lib/gitlab/ci/config/entry/services.rb index 84f8ab780f5..0066894e069 100644 --- a/lib/gitlab/ci/config/entry/services.rb +++ b/lib/gitlab/ci/config/entry/services.rb @@ -9,7 +9,30 @@ module Gitlab include Validatable validations do - validates :config, array_of_strings: true + validates :config, type: Array + end + + def compose!(deps = nil) + super do + @entries = [] + @config.each do |config| + @entries << Entry::Factory.new(Entry::Service) + .value(config || {}) + .create! + end + + @entries.each do |entry| + entry.compose!(deps) + end + end + end + + def value + @entries.map(&:value) + end + + def descendants + @entries end end end diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index bd7428b1272..b2ca3c881e4 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -44,6 +44,14 @@ module Gitlab end end + class HashOrStringValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unless value.is_a?(Hash) || value.is_a?(String) + record.errors.add(attribute, 'should be a hash or a string') + end + end + end + class KeyValidator < ActiveModel::EachValidator include LegacyValidationHelpers diff --git a/lib/gitlab/ci/status/external/common.rb b/lib/gitlab/ci/status/external/common.rb index 4969a350862..9307545b5b1 100644 --- a/lib/gitlab/ci/status/external/common.rb +++ b/lib/gitlab/ci/status/external/common.rb @@ -3,6 +3,10 @@ module Gitlab module Status module External module Common + def label + subject.description + end + def has_details? subject.target_url.present? && can?(user, :read_commit_status, subject) diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 48735fd197d..284e6ad55a5 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -10,43 +10,49 @@ module Gitlab delegate :sidekiq_throttling_enabled?, to: :current_application_settings - def fake_application_settings - OpenStruct.new(::ApplicationSetting.defaults) + def fake_application_settings(defaults = ApplicationSetting.defaults) + FakeApplicationSettings.new(defaults) end private def ensure_application_settings! - unless ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' - settings = retrieve_settings_from_database? - end + return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' - settings || in_memory_application_settings + cached_application_settings || uncached_application_settings end - def retrieve_settings_from_database? - settings = retrieve_settings_from_database_cache? - return settings if settings.present? - - return fake_application_settings unless connect_to_db? - + def cached_application_settings begin - db_settings = ::ApplicationSetting.current - # In case Redis isn't running or the Redis UNIX socket file is not available + ApplicationSetting.cached rescue ::Redis::BaseError, ::Errno::ENOENT - db_settings = ::ApplicationSetting.last + # In case Redis isn't running or the Redis UNIX socket file is not available end - db_settings || ::ApplicationSetting.create_from_defaults end - def retrieve_settings_from_database_cache? + def uncached_application_settings + return fake_application_settings unless connect_to_db? + + # This loads from the database into the cache, so handle Redis errors begin - settings = ApplicationSetting.cached + db_settings = ApplicationSetting.current rescue ::Redis::BaseError, ::Errno::ENOENT # In case Redis isn't running or the Redis UNIX socket file is not available - settings = nil end - settings + + # If there are pending migrations, it's possible there are columns that + # need to be added to the application settings. To prevent Rake tasks + # and other callers from failing, use any loaded settings and return + # defaults for missing columns. + if ActiveRecord::Migrator.needs_migration? + defaults = ApplicationSetting.defaults + defaults.merge!(db_settings.attributes.symbolize_keys) if db_settings.present? + return fake_application_settings(defaults) + end + + return db_settings if db_settings.present? + + ApplicationSetting.create_from_defaults || in_memory_application_settings end def in_memory_application_settings @@ -62,8 +68,7 @@ module Gitlab active_db_connection = ActiveRecord::Base.connection.active? rescue false active_db_connection && - ActiveRecord::Base.connection.table_exists?('application_settings') && - !ActiveRecord::Migrator.needs_migration? + ActiveRecord::Base.connection.table_exists?('application_settings') rescue ActiveRecord::NoDatabaseError false end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index d0bd1299671..0d5a7cf0694 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -83,6 +83,22 @@ module Gitlab end end + def self.bulk_insert(table, rows) + return if rows.empty? + + keys = rows.first.keys + columns = keys.map { |key| connection.quote_column_name(key) } + + tuples = rows.map do |row| + row.values_at(*keys).map { |value| connection.quote(value) } + end + + connection.execute <<-EOF.strip_heredoc + INSERT INTO #{table} (#{columns.join(', ')}) + VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')} + EOF + end + # pool_size - The size of the DB pool. # host - An optional host name to use instead of the default one. def self.create_connection_pool(pool_size, host = nil) diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index a412bb6dbd2..9181202a091 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -1,6 +1,39 @@ module Gitlab module Database module MigrationHelpers + # Adds `created_at` and `updated_at` columns with timezone information. + # + # This method is an improved version of Rails' built-in method `add_timestamps`. + # + # Available options are: + # default - The default value for the column. + # null - When set to `true` the column will allow NULL values. + # The default is to not allow NULL values. + def add_timestamps_with_timezone(table_name, options = {}) + options[:null] = false if options[:null].nil? + + [:created_at, :updated_at].each do |column_name| + if options[:default] && transaction_open? + raise '`add_timestamps_with_timezone` with default value cannot be run inside a transaction. ' \ + 'You can disable transactions by calling `disable_ddl_transaction!` ' \ + 'in the body of your migration class' + end + + # If default value is presented, use `add_column_with_default` method instead. + if options[:default] + add_column_with_default( + table_name, + column_name, + :datetime_with_timezone, + default: options[:default], + allow_null: options[:null] + ) + else + add_column(table_name, column_name, :datetime_with_timezone, options) + end + end + end + # Creates a new index, concurrently when supported # # On PostgreSQL this method creates an index concurrently, on MySQL this @@ -200,6 +233,12 @@ module Gitlab # Update in batches of 5% until we run out of any rows to update. batch_size = ((total / 100.0) * 5.0).ceil + max_size = 1000 + + # The upper limit is 1000 to ensure we don't lock too many rows. For + # example, for "merge_requests" even 1% of the table is around 35 000 + # rows for GitLab.com. + batch_size = max_size if batch_size > max_size start_arel = table.project(table[:id]).order(table[:id].asc).take(1) start_arel = yield table, start_arel if block_given? diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 4212a0dbe2e..d2863a4da71 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -5,7 +5,20 @@ module Gitlab delegate :new_file?, :deleted_file?, :renamed_file?, :old_path, :new_path, :a_mode, :b_mode, :mode_changed?, - :submodule?, :too_large?, :collapsed?, to: :diff, prefix: false + :submodule?, :expanded?, :too_large?, :collapsed?, :line_count, to: :diff, prefix: false + + # Finding a viewer for a diff file happens based only on extension and whether the + # diff file blobs are binary or text, which means 1 diff file should only be matched by 1 viewer, + # and the order of these viewers doesn't really matter. + # + # However, when the diff file blobs are LFS pointers, we cannot know for sure whether the + # file being pointed to is binary or text. In this case, we match only on + # extension, preferring binary viewers over text ones if both exist, since the + # large files referred to in "Large File Storage" are much more likely to be + # binary than text. + RICH_VIEWERS = [ + DiffViewer::Image + ].sort_by { |v| v.binary? ? 0 : 1 }.freeze def initialize(diff, repository:, diff_refs: nil, fallback_diff_refs: nil) @diff = diff @@ -177,6 +190,100 @@ module Gitlab def text? !binary? end + + def external_storage_error? + old_blob&.external_storage_error? || new_blob&.external_storage_error? + end + + def stored_externally? + old_blob&.stored_externally? || new_blob&.stored_externally? + end + + def external_storage + old_blob&.external_storage || new_blob&.external_storage + end + + def content_changed? + old_blob && new_blob && old_blob.id != new_blob.id + end + + def different_type? + old_blob && new_blob && old_blob.binary? != new_blob.binary? + end + + def size + [old_blob&.size, new_blob&.size].compact.sum + end + + def raw_size + [old_blob&.raw_size, new_blob&.raw_size].compact.sum + end + + def raw_binary? + old_blob&.raw_binary? || new_blob&.raw_binary? + end + + def raw_text? + !raw_binary? && !different_type? + end + + def simple_viewer + @simple_viewer ||= simple_viewer_class.new(self) + end + + def rich_viewer + return @rich_viewer if defined?(@rich_viewer) + + @rich_viewer = rich_viewer_class&.new(self) + end + + def rendered_as_text?(ignore_errors: true) + simple_viewer.is_a?(DiffViewer::Text) && (ignore_errors || simple_viewer.render_error.nil?) + end + + private + + def simple_viewer_class + return DiffViewer::NotDiffable unless diffable? + + if content_changed? + if raw_text? + DiffViewer::Text + else + DiffViewer::NoPreview + end + elsif new_file? + if raw_text? + DiffViewer::Text + else + DiffViewer::Added + end + elsif deleted_file? + if raw_text? + DiffViewer::Text + else + DiffViewer::Deleted + end + elsif renamed_file? + DiffViewer::Renamed + elsif mode_changed? + DiffViewer::ModeChanged + end + end + + def rich_viewer_class + viewer_class_from(RICH_VIEWERS) + end + + def viewer_class_from(classes) + return unless diffable? + return if different_type? || external_storage_error? + return unless new_file? || deleted_file? || content_changed? + + verify_binary = !stored_externally? + + classes.find { |viewer_class| viewer_class.can_render?(self, verify_binary: verify_binary) } + end end end end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index bd52ae47e9f..2d89ccfc354 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -42,25 +42,25 @@ module Gitlab end def added? - type == 'new' || type == 'new-nonewline' + %w[new new-nonewline].include?(type) end def removed? - type == 'old' || type == 'old-nonewline' - end - - def rich_text - @parent_file.highlight_lines! if @parent_file && !@rich_text - - @rich_text + %w[old old-nonewline].include?(type) end def meta? - type == 'match' + %w[match new-nonewline old-nonewline].include?(type) end def discussable? - !['match', 'new-nonewline', 'old-nonewline'].include?(type) + !meta? + end + + def rich_text + @parent_file.highlight_lines! if @parent_file && !@rich_text + + @rich_text end def as_json(opts = nil) diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb index 481536a380b..0cb26fa45c8 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -14,16 +14,7 @@ module Gitlab lines = [] highlighted_diff_lines = diff_file.highlighted_diff_lines highlighted_diff_lines.each do |line| - if line.meta? || line.unchanged? - # line in the right panel is the same as in the left one - lines << { - left: line, - right: line - } - - free_right_index = nil - i += 1 - elsif line.removed? + if line.removed? lines << { left: line, right: nil @@ -51,6 +42,15 @@ module Gitlab free_right_index = nil i += 1 end + elsif line.meta? || line.unchanged? + # line in the right panel is the same as in the left one + lines << { + left: line, + right: line + } + + free_right_index = nil + i += 1 end end diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb index 7f884183bb1..1d6f5bb5e1c 100644 --- a/lib/gitlab/etag_caching/middleware.rb +++ b/lib/gitlab/etag_caching/middleware.rb @@ -7,7 +7,7 @@ module Gitlab def call(env) request = Rack::Request.new(env) - route = Gitlab::EtagCaching::Router.match(request) + route = Gitlab::EtagCaching::Router.match(request.path_info) return @app.call(env) unless route track_event(:etag_caching_middleware_used, route) diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb index dccc66b3918..75167a6b088 100644 --- a/lib/gitlab/etag_caching/router.rb +++ b/lib/gitlab/etag_caching/router.rb @@ -53,8 +53,8 @@ module Gitlab ) ].freeze - def self.match(request) - ROUTES.find { |route| route.regexp.match(request.path_info) } + def self.match(path) + ROUTES.find { |route| route.regexp.match(path) } end end end diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 0039fc01c8f..072fcfc65e6 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -25,6 +25,8 @@ module Gitlab end def redis_key(key) + raise 'Invalid key' if !Rails.env.production? && !Gitlab::EtagCaching::Router.match(key) + "#{REDIS_NAMESPACE}#{key}" end end diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb new file mode 100644 index 00000000000..bb14a8cd9e7 --- /dev/null +++ b/lib/gitlab/fake_application_settings.rb @@ -0,0 +1,27 @@ +# This class extends an OpenStruct object by adding predicate methods to mimic +# ActiveRecord access. We rely on the initial values being true or false to +# determine whether to define a predicate method because for a newly-added +# column that has not been migrated yet, there is no way to determine the +# column type without parsing db/schema.rb. +module Gitlab + class FakeApplicationSettings < OpenStruct + def initialize(options = {}) + super + + FakeApplicationSettings.define_predicate_methods(options) + end + + # Mimic ActiveRecord predicate methods for boolean values + def self.define_predicate_methods(options) + options.each do |key, value| + next if key.to_s.end_with?('?') + next unless [true, false].include?(value) + + define_method "#{key}?" do + actual_key = key.to_s.chomp('?') + self[actual_key] + end + end + end + end +end diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb index 88ad760bea3..f825568f194 100644 --- a/lib/gitlab/git/diff.rb +++ b/lib/gitlab/git/diff.rb @@ -16,9 +16,11 @@ module Gitlab alias_method :renamed_file?, :renamed_file attr_accessor :expanded + attr_writer :too_large - # We need this accessor because of `to_hash` and `init_from_hash` - attr_accessor :too_large + alias_method :expanded?, :expanded + + SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze class << self # The maximum size of a diff to display. @@ -229,16 +231,10 @@ module Gitlab end end - def serialize_keys - @serialize_keys ||= %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large) - end - def to_hash hash = {} - keys = serialize_keys - - keys.each do |key| + SERIALIZE_KEYS.each do |key| hash[key] = send(key) end @@ -265,6 +261,9 @@ module Gitlab end end + # This is used by `to_hash` and `init_from_hash`. + alias_method :too_large, :too_large? + def too_large! @diff = '' @line_count = 0 @@ -313,7 +312,7 @@ module Gitlab def init_from_hash(hash) raw_diff = hash.symbolize_keys - serialize_keys.each do |key| + SERIALIZE_KEYS.each do |key| send(:"#{key}=", raw_diff[key.to_sym]) end end diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb index 334e06a6eca..555894907cc 100644 --- a/lib/gitlab/git/diff_collection.rb +++ b/lib/gitlab/git/diff_collection.rb @@ -97,7 +97,7 @@ module Gitlab diff = Gitlab::Git::Diff.new(raw, expanded: expanded) - if !expanded && over_safe_limits?(i) + if !expanded && over_safe_limits?(i) && diff.line_count > 0 diff.collapse! end diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb new file mode 100644 index 00000000000..f4e3b5e5129 --- /dev/null +++ b/lib/gitlab/git/gitmodules_parser.rb @@ -0,0 +1,77 @@ +module Gitlab + module Git + class GitmodulesParser + def initialize(content) + @content = content + end + + # Parses the contents of a .gitmodules file and returns a hash of + # submodule information, indexed by path. + def parse + reindex_by_path(get_submodules_by_name) + end + + private + + class State + def initialize + @result = {} + @current_submodule = nil + end + + def start_section(section) + # In some .gitmodules files (e.g. nodegit's), a header + # with the same name appears multiple times; we want to + # accumulate the configs across these + @current_submodule = @result[section] || { 'name' => section } + @result[section] = @current_submodule + end + + def set_attribute(attr, value) + @current_submodule[attr] = value + end + + def section_started? + !@current_submodule.nil? + end + + def submodules_by_name + @result + end + end + + def get_submodules_by_name + iterator = State.new + + @content.split("\n").each_with_object(iterator) do |text, iterator| + next if text =~ /^\s*#/ + + if text =~ /\A\[submodule "(?<name>[^"]+)"\]\z/ + iterator.start_section($~[:name]) + else + next unless iterator.section_started? + + next unless text =~ /\A\s*(?<key>\w+)\s*=\s*(?<value>.*)\z/ + + value = $~[:value].chomp + iterator.set_attribute($~[:key], value) + end + end + + iterator.submodules_by_name + end + + def reindex_by_path(submodules_by_name) + # Convert from an indexed by name to an array indexed by path + # If a submodule doesn't have a path, it is considered bogus + # and is ignored + submodules_by_name.each_with_object({}) do |(name, data), results| + path = data.delete 'path' + next unless path + + results[path] = data + end + end + end + end +end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 85695d0a4df..c1f942f931a 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -617,9 +617,9 @@ module Gitlab # # Ex. # { - # "rack" => { + # "current_path/rack" => { + # "name" => "original_path/rack", # "id" => "c67be4624545b4263184c4a0e8f887efd0a66320", - # "path" => "rack", # "url" => "git://github.com/chneukirchen/rack.git" # }, # "encoding" => { @@ -637,7 +637,8 @@ module Gitlab return {} end - parse_gitmodules(commit, content) + parser = GitmodulesParser.new(content) + fill_submodule_ids(commit, parser.parse) end # Return total commits count accessible from passed ref @@ -998,42 +999,19 @@ module Gitlab end end - # Parses the contents of a .gitmodules file and returns a hash of - # submodule information. - def parse_gitmodules(commit, content) - modules = {} - - name = nil - content.each_line do |line| - case line.strip - when /\A\[submodule "(?<name>[^"]+)"\]\z/ # Submodule header - name = $~[:name] - modules[name] = {} - when /\A(?<key>\w+)\s*=\s*(?<value>.*)\z/ # Key/value pair - key = $~[:key] - value = $~[:value].chomp - - next unless name && modules[name] - - modules[name][key] = value - - if key == 'path' - begin - modules[name]['id'] = blob_content(commit, value) - rescue InvalidBlobName - # The current entry is invalid - modules.delete(name) - name = nil - end - end - when /\A#/ # Comment - next - else # Invalid line - name = nil + # Fill in the 'id' field of a submodule hash from its values + # as-of +commit+. Return a Hash consisting only of entries + # from the submodule hash for which the 'id' field is filled. + def fill_submodule_ids(commit, submodule_data) + submodule_data.each do |path, data| + id = begin + blob_content(commit, path) + rescue InvalidBlobName + nil end + data['id'] = id end - - modules + submodule_data.select { |path, data| data['id'] } end # Returns true if +commit+ introduced changes to +path+, using commit diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 0a19d24eb20..0b62911958d 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -22,12 +22,13 @@ module Gitlab PUSH_COMMANDS = %w{ git-receive-pack }.freeze ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS - attr_reader :actor, :project, :protocol, :authentication_abilities + attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path - def initialize(actor, project, protocol, authentication_abilities:) + def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil) @actor = actor @project = project @protocol = protocol + @redirected_path = redirected_path @authentication_abilities = authentication_abilities end @@ -35,6 +36,7 @@ module Gitlab check_protocol! check_active_user! check_project_accessibility! + check_project_moved! check_command_disabled!(cmd) check_command_existence!(cmd) check_repository_existence! @@ -87,6 +89,21 @@ module Gitlab end end + def check_project_moved! + if redirected_path + url = protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo + message = <<-MESSAGE.strip_heredoc + Project '#{redirected_path}' was moved to '#{project.full_path}'. + + Please update your Git remote and try again: + + git remote set-url origin #{url} + MESSAGE + + raise NotFoundError, message + end + end + def check_command_disabled!(cmd) if upload_pack?(cmd) check_upload_pack_disabled! diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb index 86d055d3533..f5a4c5493ef 100644 --- a/lib/gitlab/gitaly_client/util.rb +++ b/lib/gitlab/gitaly_client/util.rb @@ -4,7 +4,6 @@ module Gitlab class << self def repository(repository_storage, relative_path) Gitaly::Repository.new( - path: File.join(Gitlab.config.repositories.storages[repository_storage]['path'], relative_path), storage_name: repository_storage, relative_path: relative_path ) diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/group_hierarchy.rb index e9d5d52cabb..357c076e874 100644 --- a/lib/gitlab/group_hierarchy.rb +++ b/lib/gitlab/group_hierarchy.rb @@ -3,33 +3,38 @@ module Gitlab # # This class uses recursive CTEs and as a result will only work on PostgreSQL. class GroupHierarchy - attr_reader :base, :model - - # base - An instance of ActiveRecord::Relation for which to get parent or - # child groups. - def initialize(base) - @base = base - @model = base.model + attr_reader :ancestors_base, :descendants_base, :model + + # ancestors_base - An instance of ActiveRecord::Relation for which to + # get parent groups. + # descendants_base - An instance of ActiveRecord::Relation for which to + # get child groups. If omitted, ancestors_base is used. + def initialize(ancestors_base, descendants_base = ancestors_base) + raise ArgumentError.new("Model of ancestors_base does not match model of descendants_base") if ancestors_base.model != descendants_base.model + + @ancestors_base = ancestors_base + @descendants_base = descendants_base + @model = ancestors_base.model end - # Returns a relation that includes the base set of groups and all their - # ancestors (recursively). + # Returns a relation that includes the ancestors_base set of groups + # and all their ancestors (recursively). def base_and_ancestors - return model.none unless Group.supports_nested_groups? + return ancestors_base unless Group.supports_nested_groups? base_and_ancestors_cte.apply_to(model.all) end - # Returns a relation that includes the base set of groups and all their - # descendants (recursively). + # Returns a relation that includes the descendants_base set of groups + # and all their descendants (recursively). def base_and_descendants - return model.none unless Group.supports_nested_groups? + return descendants_base unless Group.supports_nested_groups? base_and_descendants_cte.apply_to(model.all) end - # Returns a relation that includes the base groups, their ancestors, and the - # descendants of the base groups. + # Returns a relation that includes the base groups, their ancestors, + # and the descendants of the base groups. # # The resulting query will roughly look like the following: # @@ -48,8 +53,10 @@ module Gitlab # # Using this approach allows us to further add criteria to the relation with # Rails thinking it's selecting data the usual way. + # + # If nested groups are not supported, ancestors_base is returned. def all_groups - return base unless Group.supports_nested_groups? + return ancestors_base unless Group.supports_nested_groups? ancestors = base_and_ancestors_cte descendants = base_and_descendants_cte @@ -72,7 +79,7 @@ module Gitlab def base_and_ancestors_cte cte = SQL::RecursiveCTE.new(:base_and_ancestors) - cte << base.except(:order) + cte << ancestors_base.except(:order) # Recursively get all the ancestors of the base set. cte << model. @@ -86,7 +93,7 @@ module Gitlab def base_and_descendants_cte cte = SQL::RecursiveCTE.new(:base_and_descendants) - cte << base.except(:order) + cte << descendants_base.except(:order) # Recursively get all the descendants of the base set. cte << model. diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 328dd17e452..a5ad2f952d3 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,6 +6,7 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', + 'fr' => 'Français', 'pt_BR' => 'Português(Brasil)', 'zh_CN' => '简体ä¸æ–‡', 'zh_HK' => 'ç¹é«”ä¸æ–‡(香港)', diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 27d5a9198b6..3470a09eaf0 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,7 +3,7 @@ module Gitlab extend self # For every version update, the version history in import_export.md has to be kept up to date. - VERSION = '0.1.7'.freeze + VERSION = '0.1.8'.freeze FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index ff2b1d08c3c..72183e8aad4 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -26,7 +26,8 @@ project_tree: - notes: - :author - :events - - :merge_request_diff + - merge_request_diff: + - :merge_request_diff_files - :events - :timelogs - label_links: @@ -92,6 +93,8 @@ excluded_attributes: - :expired_at merge_request_diff: - :st_diffs + merge_request_diff_files: + - :diff issues: - :milestone_id merge_requests: @@ -113,6 +116,8 @@ methods: - :type merge_request_diff: - :utf8_st_diffs + merge_request_diff_files: + - :utf8_diff merge_requests: - :diff_head_sha project: diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb index 48c09dafcb6..b48f63bcd7e 100644 --- a/lib/gitlab/import_export/json_hash_builder.rb +++ b/lib/gitlab/import_export/json_hash_builder.rb @@ -83,7 +83,9 @@ module Gitlab # +value+ existing model to be included in the hash # +json_config_hash+ the original hash containing the root model def add_model_value(current_key, value, json_config_hash) - @attributes_finder.parse(value) { |hash| value = { value => hash } } + @attributes_finder.parse(value) do |hash| + value = { value => hash } unless value.is_a?(Hash) + end add_to_array(current_key, json_config_hash, value) end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 695852526cb..20580459046 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -71,6 +71,7 @@ module Gitlab @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] set_st_diff_commits if @relation_name == :merge_request_diff + set_diff if @relation_name == :merge_request_diff_files end def update_user_references @@ -202,6 +203,10 @@ module Gitlab HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits']) end + def set_diff + @relation_hash['diff'] = @relation_hash.delete('utf8_diff') + end + def existing_or_new_object # Only find existing records to avoid mapping tables such as milestones # Otherwise always create the record, skipping the extra SELECT clause. diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb index 8db91d25a4b..208f0e1bbea 100644 --- a/lib/gitlab/job_waiter.rb +++ b/lib/gitlab/job_waiter.rb @@ -14,7 +14,7 @@ module Gitlab # timeout - The maximum amount of seconds to block the caller for. This # ensures we don't indefinitely block a caller in case a job takes # long to process, or is never processed. - def wait(timeout = 60) + def wait(timeout = 10) start = Time.current while (Time.current - start) <= timeout diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index 4a6091488c8..c56c1a4322f 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -8,13 +8,13 @@ module Gitlab ) # Filters an array of pods (as returned by the kubernetes API) by their labels - def filter_pods(pods, labels = {}) - pods.select do |pod| - metadata = pod.fetch("metadata", {}) - pod_labels = metadata.fetch("labels", nil) - next unless pod_labels + def filter_by_label(items, labels = {}) + items.select do |item| + metadata = item.fetch("metadata", {}) + item_labels = metadata.fetch("labels", nil) + next unless item_labels - labels.all? { |k, v| pod_labels[k.to_s] == v } + labels.all? { |k, v| item_labels[k.to_s] == v } end end diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb index caab8856014..3937d9c153a 100644 --- a/lib/gitlab/slash_commands/command_definition.rb +++ b/lib/gitlab/quick_actions/command_definition.rb @@ -1,5 +1,5 @@ module Gitlab - module SlashCommands + module QuickActions class CommandDefinition attr_accessor :name, :aliases, :description, :explanation, :params, :condition_block, :parse_params_block, :action_block diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/quick_actions/dsl.rb index 1b5b4566d81..a4a97236ffc 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/quick_actions/dsl.rb @@ -1,5 +1,5 @@ module Gitlab - module SlashCommands + module QuickActions module Dsl extend ActiveSupport::Concern @@ -14,7 +14,7 @@ module Gitlab end class_methods do - # Allows to give a description to the next slash command. + # Allows to give a description to the next quick action. # This description is shown in the autocomplete menu. # It accepts a block that will be evaluated with the context given to # `CommandDefintion#to_h`. @@ -31,7 +31,7 @@ module Gitlab @description = block_given? ? block : text end - # Allows to define params for the next slash command. + # Allows to define params for the next quick action. # These params are shown in the autocomplete menu. # # Example: diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/quick_actions/extractor.rb index 6dbb467d70d..09576be7156 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/quick_actions/extractor.rb @@ -1,10 +1,10 @@ module Gitlab - module SlashCommands + module QuickActions # This class takes an array of commands that should be extracted from a # given text. # # ``` - # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) + # extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels]) # ``` class Extractor attr_reader :command_definitions @@ -24,7 +24,7 @@ module Gitlab # # Usage: # ``` - # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) + # extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels]) # msg = %(hello\n/labels ~foo ~"bar baz"\nworld) # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # msg #=> "hello\nworld" diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 878e03f61d7..3591fa9145e 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -3,16 +3,18 @@ module Gitlab NotFoundError = Class.new(StandardError) def self.parse(repo_path) + wiki = false project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false) - project = Project.find_by_full_path(project_path) - if project_path.end_with?('.wiki') && !project - project = Project.find_by_full_path(project_path.chomp('.wiki')) + project, was_redirected = find_project(project_path) + + if project_path.end_with?('.wiki') && project.nil? + project, was_redirected = find_project(project_path.chomp('.wiki')) wiki = true - else - wiki = false end - [project, wiki] + redirected_path = project_path if was_redirected + + [project, wiki, redirected_path] end def self.strip_storage_path(repo_path, fail_on_not_found: true) @@ -30,5 +32,12 @@ module Gitlab result.sub(/\A\/*/, '') end + + def self.find_project(project_path) + project = Project.find_by_full_path(project_path, follow_redirects: true) + was_redirected = project && project.full_path.casecmp(project_path) != 0 + + [project, was_redirected] + end end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/slash_commands/base_command.rb index 25da8474e95..cc3c9a50555 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/slash_commands/base_command.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class BaseCommand QUERY_LIMIT = 5 diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/slash_commands/command.rb index 3e0c30c33b7..a78408b0519 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/slash_commands/command.rb @@ -1,11 +1,11 @@ module Gitlab - module ChatCommands + module SlashCommands class Command < BaseCommand COMMANDS = [ - Gitlab::ChatCommands::IssueShow, - Gitlab::ChatCommands::IssueNew, - Gitlab::ChatCommands::IssueSearch, - Gitlab::ChatCommands::Deploy + Gitlab::SlashCommands::IssueShow, + Gitlab::SlashCommands::IssueNew, + Gitlab::SlashCommands::IssueSearch, + Gitlab::SlashCommands::Deploy ].freeze def execute @@ -15,10 +15,10 @@ module Gitlab if command.allowed?(project, current_user) command.new(project, current_user, params).execute(match) else - Gitlab::ChatCommands::Presenters::Access.new.access_denied + Gitlab::SlashCommands::Presenters::Access.new.access_denied end else - Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text]) + Gitlab::SlashCommands::Help.new(project, current_user, params).execute(available_commands, params[:text]) end end diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/slash_commands/deploy.rb index 458d90f84e8..e71eb15d604 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/slash_commands/deploy.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class Deploy < BaseCommand def self.match(text) /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text) @@ -24,12 +24,12 @@ module Gitlab actions = find_actions(from, to) if actions.none? - Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions + Gitlab::SlashCommands::Presenters::Deploy.new(nil).no_actions elsif actions.one? action = play!(from, to, actions.first) - Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to) + Gitlab::SlashCommands::Presenters::Deploy.new(action).present(from, to) else - Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions + Gitlab::SlashCommands::Presenters::Deploy.new(actions).too_many_actions end end diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/slash_commands/help.rb index 6c0e4d304a4..81f3707e03e 100644 --- a/lib/gitlab/chat_commands/help.rb +++ b/lib/gitlab/slash_commands/help.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class Help < BaseCommand # This class has to be used last, as it always matches. It has to match # because other commands were not triggered and we want to show the help @@ -17,7 +17,7 @@ module Gitlab end def execute(commands, text) - Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text) + Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, text) end def trigger diff --git a/lib/gitlab/chat_commands/issue_command.rb b/lib/gitlab/slash_commands/issue_command.rb index 84de3e44c70..87ea19b8806 100644 --- a/lib/gitlab/chat_commands/issue_command.rb +++ b/lib/gitlab/slash_commands/issue_command.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class IssueCommand < BaseCommand def self.available?(project) project.issues_enabled? && project.default_issues_tracker? diff --git a/lib/gitlab/chat_commands/issue_new.rb b/lib/gitlab/slash_commands/issue_new.rb index 016054ecd46..25f965e843d 100644 --- a/lib/gitlab/chat_commands/issue_new.rb +++ b/lib/gitlab/slash_commands/issue_new.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class IssueNew < IssueCommand def self.match(text) # we can not match \n with the dot by passing the m modifier as than @@ -35,7 +35,7 @@ module Gitlab end def presenter(issue) - Gitlab::ChatCommands::Presenters::IssueNew.new(issue) + Gitlab::SlashCommands::Presenters::IssueNew.new(issue) end end end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/slash_commands/issue_search.rb index 3491b53093e..acba84b54b4 100644 --- a/lib/gitlab/chat_commands/issue_search.rb +++ b/lib/gitlab/slash_commands/issue_search.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class IssueSearch < IssueCommand def self.match(text) /\Aissue\s+search\s+(?<query>.*)/.match(text) diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/slash_commands/issue_show.rb index d6013f4d10c..ffa5184e5cb 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/slash_commands/issue_show.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands class IssueShow < IssueCommand def self.match(text) /\Aissue\s+show\s+#{Issue.reference_prefix}?(?<iid>\d+)/.match(text) @@ -13,9 +13,9 @@ module Gitlab issue = find_by_iid(match[:iid]) if issue - Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present + Gitlab::SlashCommands::Presenters::IssueShow.new(issue).present else - Gitlab::ChatCommands::Presenters::Access.new.not_found + Gitlab::SlashCommands::Presenters::Access.new.not_found end end end diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/slash_commands/presenters/access.rb index 92f4fa17f78..1a817eb735b 100644 --- a/lib/gitlab/chat_commands/presenters/access.rb +++ b/lib/gitlab/slash_commands/presenters/access.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class Access < Presenters::Base def access_denied diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/slash_commands/presenters/base.rb index 05994bee79d..27696436574 100644 --- a/lib/gitlab/chat_commands/presenters/base.rb +++ b/lib/gitlab/slash_commands/presenters/base.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class Base include Gitlab::Routing.url_helpers diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/slash_commands/presenters/deploy.rb index 863d0bf99ca..b8dc77bd37b 100644 --- a/lib/gitlab/chat_commands/presenters/deploy.rb +++ b/lib/gitlab/slash_commands/presenters/deploy.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class Deploy < Presenters::Base def present(from, to) diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/slash_commands/presenters/help.rb index cd47b7f4c6a..ea611a4d629 100644 --- a/lib/gitlab/chat_commands/presenters/help.rb +++ b/lib/gitlab/slash_commands/presenters/help.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class Help < Presenters::Base def present(trigger, text) diff --git a/lib/gitlab/chat_commands/presenters/issue_base.rb b/lib/gitlab/slash_commands/presenters/issue_base.rb index 25bc82994ba..341f2aabdd0 100644 --- a/lib/gitlab/chat_commands/presenters/issue_base.rb +++ b/lib/gitlab/slash_commands/presenters/issue_base.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters module IssueBase def color(issuable) diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/slash_commands/presenters/issue_new.rb index 3674ba25641..86490a39cc1 100644 --- a/lib/gitlab/chat_commands/presenters/issue_new.rb +++ b/lib/gitlab/slash_commands/presenters/issue_new.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class IssueNew < Presenters::Base include Presenters::IssueBase diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/slash_commands/presenters/issue_search.rb index 73788cf9662..4e27d668685 100644 --- a/lib/gitlab/chat_commands/presenters/issue_search.rb +++ b/lib/gitlab/slash_commands/presenters/issue_search.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class IssueSearch < Presenters::Base include Presenters::IssueBase diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/slash_commands/presenters/issue_show.rb index bd784ad241e..c99316df667 100644 --- a/lib/gitlab/chat_commands/presenters/issue_show.rb +++ b/lib/gitlab/slash_commands/presenters/issue_show.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands module Presenters class IssueShow < Presenters::Base include Presenters::IssueBase diff --git a/lib/gitlab/chat_commands/result.rb b/lib/gitlab/slash_commands/result.rb index 324d7ef43a3..7021b4b01b2 100644 --- a/lib/gitlab/chat_commands/result.rb +++ b/lib/gitlab/slash_commands/result.rb @@ -1,5 +1,5 @@ module Gitlab - module ChatCommands + module SlashCommands Result = Struct.new(:type, :message) end end diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake index 3c5bc0146a1..e88111c3725 100644 --- a/lib/tasks/gitlab/gitaly.rake +++ b/lib/tasks/gitlab/gitaly.rake @@ -30,11 +30,7 @@ namespace :gitlab do puts "# Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)}" puts "# This is in TOML format suitable for use in Gitaly's config.toml file." - config = Gitlab.config.repositories.storages.map do |key, val| - { name: key, path: val['path'] } - end - - puts TOML.dump(storage: config) + puts gitaly_configuration_toml end private @@ -42,10 +38,10 @@ namespace :gitlab do # We cannot create config.toml files for all possible Gitaly configuations. # For instance, if Gitaly is running on another machine then it makes no # sense to write a config.toml file on the current machine. This method will - # only write a config.toml file in the most common and simplest case: the - # case where we have exactly one Gitaly process and we are sure it is - # running locally because it uses a Unix socket. - def create_gitaly_configuration + # only generate a configuration for the most common and simplest case: when + # we have exactly one Gitaly process and we are sure it is running locally + # because it uses a Unix socket. + def gitaly_configuration_toml storages = [] address = nil @@ -63,8 +59,12 @@ namespace :gitlab do storages << { name: key, path: val['path'] } end + TOML.dump(socket_path: address.sub(%r{\Aunix:}, ''), storage: storages) + end + + def create_gitaly_configuration File.open("config.toml", "w") do |f| - f.puts TOML.dump(socket_path: address.sub(%r{\Aunix:}, ''), storages: storages) + f.puts gitaly_configuration_toml end rescue ArgumentError => e puts "Skipping config.toml generation:" diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake index 761f275d42a..151f42a2222 100644 --- a/lib/tasks/migrate/add_limits_mysql.rake +++ b/lib/tasks/migrate/add_limits_mysql.rake @@ -1,9 +1,11 @@ require Rails.root.join('db/migrate/limits_to_mysql') require Rails.root.join('db/migrate/markdown_cache_limits_to_mysql') +require Rails.root.join('db/migrate/merge_request_diff_file_limits_to_mysql') desc "GitLab | Add limits to strings in mysql database" task add_limits_mysql: :environment do puts "Adding limits to schema.rb for mysql" LimitsToMysql.new.up MarkdownCacheLimitsToMysql.new.up + MergeRequestDiffFileLimitsToMysql.new.up end diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index e6caf83252d..43a5de65c43 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -250,6 +250,9 @@ msgstr "ИÑкате ли да видите данните? Помолете аРmsgid "We don't have enough data to show this stage." msgstr "ÐÑма доÑтатъчно данни за този етап." +msgid "You have reached your project limit" +msgstr "" + msgid "You need permission." msgstr "Ðуждаете Ñе от разрешение." diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po index 9a660571db9..ea864091b10 100644 --- a/locale/de/gitlab.po +++ b/locale/de/gitlab.po @@ -291,6 +291,9 @@ msgstr "Um diese Daten einsehen zu können, wenden Sie sich bitte an Ihren Admin msgid "We don't have enough data to show this stage." msgstr "Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen." +msgid "You have reached your project limit" +msgstr "" + msgid "You need permission." msgstr "Sie benötigen Zugriffsrechte." diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po index 4e44731fc5a..afb8fb3176f 100644 --- a/locale/en/gitlab.po +++ b/locale/en/gitlab.po @@ -17,23 +17,217 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "" + +msgid "About auto deploy" +msgstr "" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "" + +msgid "Add Changelog" +msgstr "" + +msgid "Add Contribution guide" +msgstr "" + +msgid "Add License" +msgstr "" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" + +msgid "Add new directory" +msgstr "" + +msgid "Archived project! Repository is read-only" +msgstr "" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "" +msgstr[1] "" + +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 "" + +msgid "Branches" +msgstr "" + +msgid "Browse files" +msgstr "" + msgid "ByAuthor|by" msgstr "" +msgid "CI configuration" +msgstr "" + msgid "Cancel" msgstr "" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "" + +msgid "Changelog" +msgstr "" + +msgid "Charts" +msgstr "" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "" + +msgid "CiStatusLabel|created" +msgstr "" + +msgid "CiStatusLabel|failed" +msgstr "" + +msgid "CiStatusLabel|manual action" +msgstr "" + +msgid "CiStatusLabel|passed" +msgstr "" + +msgid "CiStatusLabel|passed with warnings" +msgstr "" + +msgid "CiStatusLabel|pending" +msgstr "" + +msgid "CiStatusLabel|skipped" +msgstr "" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "" + +msgid "CiStatusText|blocked" +msgstr "" + +msgid "CiStatusText|canceled" +msgstr "" + +msgid "CiStatusText|created" +msgstr "" + +msgid "CiStatusText|failed" +msgstr "" + +msgid "CiStatusText|manual" +msgstr "" + +msgid "CiStatusText|passed" +msgstr "" + +msgid "CiStatusText|pending" +msgstr "" + +msgid "CiStatusText|skipped" +msgstr "" + +msgid "CiStatus|running" +msgstr "" + msgid "Commit" msgid_plural "Commits" msgstr[0] "" msgstr[1] "" +msgid "Commit message" +msgstr "" + +msgid "CommitBoxTitle|Commit" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "" + +msgid "Commits" +msgstr "" + +msgid "Commits|History" +msgstr "" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "" + +msgid "Contribution guide" +msgstr "" + +msgid "Contributors" +msgstr "" + +msgid "Copy URL to clipboard" +msgstr "" + +msgid "Copy commit SHA to clipboard" +msgstr "" + +msgid "Create New Directory" +msgstr "" + +msgid "Create directory" +msgstr "" + +msgid "Create empty bare repository" +msgstr "" + +msgid "Create merge request" +msgstr "" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "" + +msgid "CreateTag|Tag" +msgstr "" + msgid "Cron Timezone" msgstr "" +msgid "Cron syntax" +msgstr "" + +msgid "Custom notification events" +msgstr "" + +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 "" + +msgid "Cycle Analytics" +msgstr "" + msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." msgstr "" @@ -58,6 +252,9 @@ msgstr "" msgid "CycleAnalyticsStage|Test" msgstr "" +msgid "Define a custom pattern with cron syntax" +msgstr "" + msgid "Delete" msgstr "" @@ -69,19 +266,67 @@ msgstr[1] "" msgid "Description" msgstr "" +msgid "Directory name" +msgstr "" + +msgid "Don't show again" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Download tar" +msgstr "" + +msgid "Download tar.bz2" +msgstr "" + +msgid "Download tar.gz" +msgstr "" + +msgid "Download zip" +msgstr "" + +msgid "DownloadArtifacts|Download" +msgstr "" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "" + msgid "Edit" msgstr "" msgid "Edit Pipeline Schedule %{id}" msgstr "" +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + msgid "Failed to change the owner" msgstr "" msgid "Failed to remove the pipeline schedule" msgstr "" -msgid "Filter" +msgid "Files" +msgstr "" + +msgid "Find by path" +msgstr "" + +msgid "Find file" msgstr "" msgid "FirstPushedBy|First" @@ -90,18 +335,47 @@ msgstr "" msgid "FirstPushedBy|pushed by" msgstr "" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "" + msgid "From issue creation until deploy to production" msgstr "" msgid "From merge request merge until deploy to production" msgstr "" +msgid "Go to your fork" +msgstr "" + +msgid "GoToYourFork|Fork" +msgstr "" + +msgid "Home" +msgstr "" + +msgid "Housekeeping successfully started" +msgstr "" + +msgid "Import repository" +msgstr "" + msgid "Interval Pattern" msgstr "" msgid "Introducing Cycle Analytics" msgstr "" +msgid "LFSStatus|Disabled" +msgstr "" + +msgid "LFSStatus|Enabled" +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -110,6 +384,24 @@ msgstr[1] "" msgid "Last Pipeline" msgstr "" +msgid "Last Update" +msgstr "" + +msgid "Last commit" +msgstr "" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "" + +msgid "Leave group" +msgstr "" + +msgid "Leave project" +msgstr "" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "" @@ -118,6 +410,9 @@ msgstr[1] "" msgid "Median" msgstr "" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "" @@ -126,6 +421,33 @@ msgstr[1] "" msgid "New Pipeline Schedule" msgstr "" +msgid "New branch" +msgstr "" + +msgid "New directory" +msgstr "" + +msgid "New file" +msgstr "" + +msgid "New issue" +msgstr "" + +msgid "New merge request" +msgstr "" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "" + +msgid "New tag" +msgstr "" + +msgid "No repository" +msgstr "" + msgid "No schedules" msgstr "" @@ -135,12 +457,75 @@ msgstr "" msgid "Not enough data" msgstr "" +msgid "Notification events" +msgstr "" + +msgid "NotificationEvent|Close issue" +msgstr "" + +msgid "NotificationEvent|Close merge request" +msgstr "" + +msgid "NotificationEvent|Failed pipeline" +msgstr "" + +msgid "NotificationEvent|Merge merge request" +msgstr "" + +msgid "NotificationEvent|New issue" +msgstr "" + +msgid "NotificationEvent|New merge request" +msgstr "" + +msgid "NotificationEvent|New note" +msgstr "" + +msgid "NotificationEvent|Reassign issue" +msgstr "" + +msgid "NotificationEvent|Reassign merge request" +msgstr "" + +msgid "NotificationEvent|Reopen issue" +msgstr "" + +msgid "NotificationEvent|Successful pipeline" +msgstr "" + +msgid "NotificationLevel|Custom" +msgstr "" + +msgid "NotificationLevel|Disabled" +msgstr "" + +msgid "NotificationLevel|Global" +msgstr "" + +msgid "NotificationLevel|On mention" +msgstr "" + +msgid "NotificationLevel|Participate" +msgstr "" + +msgid "NotificationLevel|Watch" +msgstr "" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + msgid "OpenedNDaysAgo|Opened" msgstr "" +msgid "Options" +msgstr "" + msgid "Owner" msgstr "" +msgid "Pipeline" +msgstr "" + msgid "Pipeline Health" msgstr "" @@ -177,12 +562,78 @@ msgstr "" msgid "PipelineSchedules|Target" msgstr "" +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "" + +msgid "Pipeline|with stages" +msgstr "" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "" + +msgid "Project '%{project_name}' will be deleted." +msgstr "" + +msgid "Project access must be granted explicitly to each user." +msgstr "" + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "Project export link has expired. Please generate a new export from your project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" + +msgid "Project home" +msgstr "" + +msgid "ProjectFeature|Disabled" +msgstr "" + +msgid "ProjectFeature|Everyone with access" +msgstr "" + +msgid "ProjectFeature|Only team members" +msgstr "" + +msgid "ProjectFileTree|Name" +msgstr "" + +msgid "ProjectLastActivity|Never" +msgstr "" + msgid "ProjectLifecycle|Stage" msgstr "" +msgid "ProjectNetworkGraph|Graph" +msgstr "" + msgid "Read more" msgstr "" +msgid "Readme" +msgstr "" + +msgid "RefSwitcher|Branches" +msgstr "" + +msgid "RefSwitcher|Tags" +msgstr "" + msgid "Related Commits" msgstr "" @@ -201,23 +652,85 @@ msgstr "" msgid "Related Merged Requests" msgstr "" +msgid "Remind later" +msgstr "" + +msgid "Remove project" +msgstr "" + +msgid "Request Access" +msgstr "" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge request" +msgstr "" + msgid "Save pipeline schedule" msgstr "" msgid "Schedule a new pipeline" msgstr "" +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Search branches and tags" +msgstr "" + +msgid "Select Archive Format" +msgstr "" + msgid "Select a timezone" msgstr "" msgid "Select target branch" msgstr "" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "" + +msgid "Set up CI" +msgstr "" + +msgid "Set up Koding" +msgstr "" + +msgid "Set up auto deploy" +msgstr "" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "" msgstr[1] "" +msgid "Source code" +msgstr "" + +msgid "StarProject|Star" +msgstr "" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Start a <strong>new merge request</strong> with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "" +msgstr[1] "" + +msgid "Tags" +msgstr "" + msgid "Target Branch" msgstr "" @@ -227,18 +740,33 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "" +msgid "The fork relationship has been removed." +msgstr "" + 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 "" msgid "The phase of the development lifecycle." msgstr "" +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 "" + 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 "" 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 "" +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "" + 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 "" @@ -254,6 +782,9 @@ msgstr "" 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 "" +msgid "This means you can not push code until you create an empty repository or import existing one." +msgstr "" + msgid "Time before an issue gets scheduled" msgstr "" @@ -266,6 +797,129 @@ msgstr "" msgid "Time until first merge request" msgstr "" +msgid "Timeago|%s days ago" +msgstr "" + +msgid "Timeago|%s days remaining" +msgstr "" + +msgid "Timeago|%s hours remaining" +msgstr "" + +msgid "Timeago|%s minutes ago" +msgstr "" + +msgid "Timeago|%s minutes remaining" +msgstr "" + +msgid "Timeago|%s months ago" +msgstr "" + +msgid "Timeago|%s months remaining" +msgstr "" + +msgid "Timeago|%s seconds remaining" +msgstr "" + +msgid "Timeago|%s weeks ago" +msgstr "" + +msgid "Timeago|%s weeks remaining" +msgstr "" + +msgid "Timeago|%s years ago" +msgstr "" + +msgid "Timeago|%s years remaining" +msgstr "" + +msgid "Timeago|1 day remaining" +msgstr "" + +msgid "Timeago|1 hour remaining" +msgstr "" + +msgid "Timeago|1 minute remaining" +msgstr "" + +msgid "Timeago|1 month remaining" +msgstr "" + +msgid "Timeago|1 week remaining" +msgstr "" + +msgid "Timeago|1 year remaining" +msgstr "" + +msgid "Timeago|Past due" +msgstr "" + +msgid "Timeago|a day ago" +msgstr "" + +msgid "Timeago|a month ago" +msgstr "" + +msgid "Timeago|a week ago" +msgstr "" + +msgid "Timeago|a while" +msgstr "" + +msgid "Timeago|a year ago" +msgstr "" + +msgid "Timeago|about %s hours ago" +msgstr "" + +msgid "Timeago|about a minute ago" +msgstr "" + +msgid "Timeago|about an hour ago" +msgstr "" + +msgid "Timeago|in %s days" +msgstr "" + +msgid "Timeago|in %s hours" +msgstr "" + +msgid "Timeago|in %s minutes" +msgstr "" + +msgid "Timeago|in %s months" +msgstr "" + +msgid "Timeago|in %s seconds" +msgstr "" + +msgid "Timeago|in %s weeks" +msgstr "" + +msgid "Timeago|in %s years" +msgstr "" + +msgid "Timeago|in 1 day" +msgstr "" + +msgid "Timeago|in 1 hour" +msgstr "" + +msgid "Timeago|in 1 minute" +msgstr "" + +msgid "Timeago|in 1 month" +msgstr "" + +msgid "Timeago|in 1 week" +msgstr "" + +msgid "Timeago|in 1 year" +msgstr "" + +msgid "Timeago|less than a minute ago" +msgstr "" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "" @@ -285,16 +939,96 @@ msgstr "" msgid "Total test time for all commits/merges" msgstr "" +msgid "Unstar" +msgstr "" + +msgid "Upload New File" +msgstr "" + +msgid "Upload file" +msgstr "" + +msgid "Use your global notification setting" +msgstr "" + +msgid "VisibilityLevel|Internal" +msgstr "" + +msgid "VisibilityLevel|Private" +msgstr "" + +msgid "VisibilityLevel|Public" +msgstr "" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" msgid "We don't have enough data to show this stage." msgstr "" +msgid "Withdraw Access Request" +msgstr "" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You have reached your project limit" +msgstr "" + +msgid "You must sign in to star a project" +msgstr "" + msgid "You need permission." msgstr "" +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "" + +msgid "You will receive notifications only for comments in which you were @mentioned" +msgstr "" + +msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile" +msgstr "" + +msgid "Your name" +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" + +msgid "new merge request" +msgstr "" + +msgid "notification emails" +msgstr "" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 78d28d69885..6946a256308 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-06-07 12:29-0500\n" +"PO-Revision-Date: 2017-06-15 21:59-0500\n" "Language-Team: Spanish\n" "Language: es\n" "MIME-Version: 1.0\n" @@ -17,9 +17,15 @@ msgstr "" "Last-Translator: Bob Van Landuyt <bob@gitlab.com>\n" "X-Generator: Poedit 2.0.2\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} cambió %{commit_timeago}" + msgid "About auto deploy" msgstr "Acerca del auto despliegue" +msgid "Active" +msgstr "Activo" + msgid "Activity" msgstr "Actividad" @@ -39,7 +45,13 @@ msgid "Add new directory" msgstr "Agregar nuevo directorio" msgid "Archived project! Repository is read-only" -msgstr "¡Proyecto archivado! El repositorio es de sólo lectura" +msgstr "¡Proyecto archivado! El repositorio es de solo lectura" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "¿Estás seguro que deseas eliminar esta programación del pipeline?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Adjunte un archivo arrastrando & soltando o %{upload_link}" msgid "Branch" msgid_plural "Branches" @@ -52,18 +64,42 @@ msgstr "La rama <strong>%{branch_name}</strong> fue creada. Para configurar el a msgid "Branches" msgstr "Ramas" +msgid "Browse files" +msgstr "Examinar los archivos" + msgid "ByAuthor|by" msgstr "por" msgid "CI configuration" msgstr "Configuración de CI" +msgid "Cancel" +msgstr "Cancelar" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "Escoger en la rama" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "Revertir en la rama" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "Cherry-pick" + +msgid "ChangeTypeAction|Revert" +msgstr "Revertir" + msgid "Changelog" msgstr "Changelog" msgid "Charts" msgstr "Gráficos" +msgid "Cherry-pick this commit" +msgstr "Escoger este cambio" + +msgid "Cherry-pick this merge request" +msgstr "Escoger esta solicitud de fusión" + msgid "CiStatusLabel|canceled" msgstr "cancelado" @@ -71,7 +107,7 @@ msgid "CiStatusLabel|created" msgstr "creado" msgid "CiStatusLabel|failed" -msgstr "fallado" +msgstr "fallido" msgid "CiStatusLabel|manual action" msgstr "acción manual" @@ -123,6 +159,12 @@ msgid_plural "Commits" msgstr[0] "Cambio" msgstr[1] "Cambios" +msgid "Commit message" +msgstr "Mensaje del cambio" + +msgid "CommitBoxTitle|Commit" +msgstr "Cambio" + msgid "CommitMessage|Add %{file_name}" msgstr "Agregar %{file_name}" @@ -132,6 +174,9 @@ msgstr "Cambios" msgid "Commits|History" msgstr "Historial" +msgid "Committed by" +msgstr "Enviado por" + msgid "Compare" msgstr "Comparar" @@ -159,9 +204,21 @@ msgstr "Crear repositorio vacÃo" msgid "Create merge request" msgstr "Crear solicitud de fusión" +msgid "Create new..." +msgstr "Crear nuevo..." + msgid "CreateNewFork|Fork" msgstr "Bifurcar" +msgid "CreateTag|Tag" +msgstr "Etiqueta" + +msgid "Cron Timezone" +msgstr "Zona horaria del Cron" + +msgid "Cron syntax" +msgstr "Sintaxis de Cron" + msgid "Custom notification events" msgstr "Eventos de notificaciones personalizadas" @@ -195,17 +252,29 @@ msgstr "Puesta en escena" msgid "CycleAnalyticsStage|Test" msgstr "Pruebas" +msgid "Define a custom pattern with cron syntax" +msgstr "Definir un patrón personalizado con la sintaxis de cron" + +msgid "Delete" +msgstr "Eliminar" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "Despliegue" msgstr[1] "Despliegues" +msgid "Description" +msgstr "Descripción" + msgid "Directory name" msgstr "Nombre del directorio" msgid "Don't show again" msgstr "No mostrar de nuevo" +msgid "Download" +msgstr "Descargar" + msgid "Download tar" msgstr "Descargar tar" @@ -221,9 +290,36 @@ msgstr "Descargar zip" msgid "DownloadArtifacts|Download" msgstr "Descargar" +msgid "DownloadCommit|Email Patches" +msgstr "Parches por correo electrónico" + +msgid "DownloadCommit|Plain Diff" +msgstr "Diferencias en texto plano" + msgid "DownloadSource|Download" msgstr "Descargar" +msgid "Edit" +msgstr "Editar" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Editar Programación del Pipeline %{id}" + +msgid "Every day (at 4:00am)" +msgstr "Todos los dÃas (a las 4:00 am)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Todos los meses (el dÃa 1 a las 4:00 am)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Todas las semanas (domingos a las 4:00 am)" + +msgid "Failed to change the owner" +msgstr "Error al cambiar el propietario" + +msgid "Failed to remove the pipeline schedule" +msgstr "Error al eliminar la programación del pipeline" + msgid "Files" msgstr "Archivos" @@ -239,12 +335,14 @@ msgstr "Primer" msgid "FirstPushedBy|pushed by" msgstr "enviado por" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Bifurcación" +msgstr[1] "Bifurcaciones" + msgid "ForkedFromProjectPath|Forked from" msgstr "Bifurcado de" -msgid "Forks" -msgstr "Bifurcaciones" - msgid "From issue creation until deploy to production" msgstr "Desde la creación de la incidencia hasta el despliegue a producción" @@ -266,6 +364,9 @@ msgstr "Servicio de limpieza iniciado con éxito" msgid "Import repository" msgstr "Importar repositorio" +msgid "Interval Pattern" +msgstr "Patrón de intervalo" + msgid "Introducing Cycle Analytics" msgstr "Introducción a Cycle Analytics" @@ -280,12 +381,21 @@ msgid_plural "Last %d days" msgstr[0] "Último %d dÃa" msgstr[1] "Últimos %d dÃas" +msgid "Last Pipeline" +msgstr "Último Pipeline" + msgid "Last Update" msgstr "Última actualización" msgid "Last commit" msgstr "Último cambio" +msgid "Learn more in the" +msgstr "Más información en la" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "documentación sobre la programación de pipelines" + msgid "Leave group" msgstr "Abandonar grupo" @@ -308,6 +418,9 @@ msgid_plural "New Issues" msgstr[0] "Nueva incidencia" msgstr[1] "Nuevas incidencias" +msgid "New Pipeline Schedule" +msgstr "Nueva Programación del Pipeline" + msgid "New branch" msgstr "Nueva rama" @@ -323,6 +436,9 @@ msgstr "Nueva incidencia" msgid "New merge request" msgstr "Nueva solicitud de fusión" +msgid "New schedule" +msgstr "Nueva programación" + msgid "New snippet" msgstr "Nuevo fragmento de código" @@ -332,6 +448,9 @@ msgstr "Nueva etiqueta" msgid "No repository" msgstr "No hay repositorio" +msgid "No schedules" +msgstr "No hay programaciones" + msgid "Not available" msgstr "No disponible" @@ -392,12 +511,66 @@ msgstr "Participación" msgid "NotificationLevel|Watch" msgstr "Vigilancia" +msgid "OfSearchInADropdown|Filter" +msgstr "Filtrar" + msgid "OpenedNDaysAgo|Opened" msgstr "Abierto" +msgid "Options" +msgstr "Opciones" + +msgid "Owner" +msgstr "Propietario" + +msgid "Pipeline" +msgstr "Pipeline" + msgid "Pipeline Health" msgstr "Estado del Pipeline" +msgid "Pipeline Schedule" +msgstr "Programación del Pipeline" + +msgid "Pipeline Schedules" +msgstr "Programaciones de los Pipelines" + +msgid "PipelineSchedules|Activated" +msgstr "Activado" + +msgid "PipelineSchedules|Active" +msgstr "Activos" + +msgid "PipelineSchedules|All" +msgstr "Todos" + +msgid "PipelineSchedules|Inactive" +msgstr "Inactivos" + +msgid "PipelineSchedules|Next Run" +msgstr "Próxima Ejecución" + +msgid "PipelineSchedules|None" +msgstr "Ninguno" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Proporcione una breve descripción para este pipeline" + +msgid "PipelineSchedules|Take ownership" +msgstr "Tomar posesión" + +msgid "PipelineSchedules|Target" +msgstr "Destino" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "Personalizado" + +msgid "Pipeline|with stage" +msgstr "con etapa" + +msgid "Pipeline|with stages" +msgstr "con etapas" + msgid "Project '%{project_name}' queued for deletion." msgstr "Proyecto ‘%{project_name}’ en cola para eliminación." @@ -453,7 +626,7 @@ msgid "Read more" msgstr "Leer más" msgid "Readme" -msgstr "Readme" +msgstr "Léeme" msgid "RefSwitcher|Branches" msgstr "Ramas" @@ -488,14 +661,35 @@ msgstr "Eliminar proyecto" msgid "Request Access" msgstr "Solicitar acceso" +msgid "Revert this commit" +msgstr "Revertir este cambio" + +msgid "Revert this merge request" +msgstr "Revertir esta solicitud de fusión" + +msgid "Save pipeline schedule" +msgstr "Guardar programación del pipeline" + +msgid "Schedule a new pipeline" +msgstr "Programar un nuevo pipeline" + +msgid "Scheduling Pipelines" +msgstr "Programación de Pipelines" + msgid "Search branches and tags" msgstr "Buscar ramas y etiquetas" msgid "Select Archive Format" msgstr "Seleccionar formato de archivo" +msgid "Select a timezone" +msgstr "Selecciona una zona horaria" + +msgid "Select target branch" +msgstr "Selecciona una rama de destino" + msgid "Set a password on your account to pull or push via %{protocol}" -msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de% {protocol}" +msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}" msgid "Set up CI" msgstr "Configurar CI" @@ -520,6 +714,9 @@ msgstr "Código fuente" msgid "StarProject|Star" msgstr "Destacar" +msgid "Start a %{new_merge_request} with these changes" +msgstr "Iniciar una %{new_merge_request} con estos cambios" + msgid "Switch branch/tag" msgstr "Cambiar rama/etiqueta" @@ -531,6 +728,9 @@ msgstr[1] "Etiquetas" msgid "Tags" msgstr "Etiquetas" +msgid "Target Branch" +msgstr "Rama de destino" + 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 etapa de desarrollo muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquà una vez creada tu primera solicitud de fusión." @@ -546,6 +746,9 @@ msgstr "La etapa de incidencia muestra el tiempo que toma desde la creación de msgid "The phase of the development lifecycle." msgstr "La etapa del ciclo de vida de desarrollo." +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 programación de pipelines ejecuta pipelines en el futuro, repetidamente, para ramas o etiquetas especÃficas. Los pipelines programados heredarán acceso limitado al proyecto basado en su usuario asociado." + 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 etapa de planificación muestra el tiempo desde el paso anterior hasta el envÃo de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envÃe el primer cambio." @@ -652,16 +855,16 @@ msgid "Timeago|a day ago" msgstr "hace un dÃa" msgid "Timeago|a month ago" -msgstr "hace 1 mes" +msgstr "hace un mes" msgid "Timeago|a week ago" -msgstr "hace 1 semana" +msgstr "hace una semana" msgid "Timeago|a while" msgstr "hace un momento" msgid "Timeago|a year ago" -msgstr "hace 1 año" +msgstr "hace un año" msgid "Timeago|about %s hours ago" msgstr "hace alrededor de %s horas" @@ -772,18 +975,21 @@ msgstr "" "¡El proyecto eliminado NO puede ser restaurado!\n" "¿Estás TOTALMENTE seguro?" -msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" +msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?" msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?" msgid "You can only add files when you are on a branch" -msgstr "Sólo puede agregar archivos cuando estas en una rama" +msgstr "Solo puedes agregar archivos cuando estás en una rama" msgid "You must sign in to star a project" msgstr "Debes iniciar sesión para destacar un proyecto" +msgid "You have reached your project limit" +msgstr "" + msgid "You need permission." msgstr "Necesitas permisos." @@ -797,10 +1003,10 @@ msgid "You will only receive notifications for threads you have participated in" msgstr "Solo recibirás notificaciones de los temas en los que has participado" msgid "You will receive notifications for any activity" -msgstr "Recibirás notificaciones para cualquier actividad" +msgstr "Recibirás notificaciones por cualquier actividad" msgid "You will receive notifications only for comments in which you were @mentioned" -msgstr "Recibirás notificaciones sólo para los comentarios en los que se te mencionó" +msgstr "Recibirás notificaciones solo para los comentarios en los que se te mencionó" msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" msgstr "No podrás actualizar o enviar código al proyecto a través de %{protocol} hasta que %{set_password_link} en tu cuenta" @@ -811,13 +1017,18 @@ msgstr "No podrás actualizar o enviar código al proyecto a través de SSH hast msgid "Your name" msgstr "Tu nombre" -msgid "committed" -msgstr "cambió" - msgid "day" msgid_plural "days" msgstr[0] "dÃa" msgstr[1] "dÃas" +msgid "new merge request" +msgstr "nueva solicitud de fusión" + msgid "notification emails" msgstr "correos electrónicos de notificación" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "padre" +msgstr[1] "padres" diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po new file mode 100644 index 00000000000..2000fa433b4 --- /dev/null +++ b/locale/fr/gitlab.po @@ -0,0 +1,207 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gitlab package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# Dremor <egeorget@opmbx.org>, 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-06-14 04:21-0400\n" +"Last-Translator: Dremor <egeorget@opmbx.org>\n" +"Language-Team: French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Zanata 3.9.6\n" + +msgid "ByAuthor|by" +msgstr "par" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "Validation" +msgstr[1] "Validations" + +msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." +msgstr "L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet." + +msgid "CycleAnalyticsStage|Code" +msgstr "Code" + +msgid "CycleAnalyticsStage|Issue" +msgstr "Incident" + +msgid "CycleAnalyticsStage|Plan" +msgstr "Planification" + +msgid "CycleAnalyticsStage|Production" +msgstr "Production" + +msgid "CycleAnalyticsStage|Review" +msgstr "Examen" + +msgid "CycleAnalyticsStage|Staging" +msgstr "Pré-production" + +msgid "CycleAnalyticsStage|Test" +msgstr "Test" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "Déploiement" +msgstr[1] "Déploiements" + +msgid "FirstPushedBy|First" +msgstr "En premier" + +msgid "FirstPushedBy|pushed by" +msgstr "poussé par" + +msgid "From issue creation until deploy to production" +msgstr "Depuis la création de l'incident jusqu'au déploiement en production" + +msgid "From merge request merge until deploy to production" +msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en production" + +msgid "Introducing Cycle Analytics" +msgstr "Introduction à l'analyseur de cycle" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "Le dernier %d jour" +msgstr[1] "Les derniers %d jours" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "Limiter l'affichage au plus à %d évènement" +msgstr[1] "Limiter l'affichage au plus à %d évènements" + +msgid "Median" +msgstr "Médian" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "Nouvel incident" +msgstr[1] "Nouveaux incidents" + +msgid "Not available" +msgstr "Indisponible" + +msgid "Not enough data" +msgstr "Données insuffisantes" + +msgid "OpenedNDaysAgo|Opened" +msgstr "Ouvert" + +msgid "Pipeline Health" +msgstr "Santé du Pipeline" + +msgid "ProjectLifecycle|Stage" +msgstr "Étape" + +msgid "Read more" +msgstr "Lire plus" + +msgid "Related Commits" +msgstr "Validations liés" + +msgid "Related Deployed Jobs" +msgstr "Tâches de déploiement liés" + +msgid "Related Issues" +msgstr "Incidents liés" + +msgid "Related Jobs" +msgstr "Tâches liées" + +msgid "Related Merge Requests" +msgstr "Demandes de fusion liées" + +msgid "Related Merged Requests" +msgstr "Demandes fusionnées liées" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "Affichage de %d évènement" +msgstr[1] "Affichage de %d évènements" + +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 "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion." + +msgid "The collection of events added to the data gathered for that stage." +msgstr "L’ensemble d’évènements ajoutés aux données récupérées pour cette étape." + +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 "L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape." + +msgid "The phase of the development lifecycle." +msgstr "Les étapes du cycle de développement." + +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 "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation." + +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 "L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production." + +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 "L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion." + +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 "L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois." + +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 "L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera." + +msgid "The time taken by each data entry gathered by that stage." +msgstr "Le temps pris par chaque entrée récoltée durant cette étape." + +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 valeur située au point médian d’une série de valeur observée. C.à .d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6." + +msgid "Time before an issue gets scheduled" +msgstr "Temps avant qu’un incident ne soit planifié" + +msgid "Time before an issue starts implementation" +msgstr "Temps avant que résolution ne débute" + +msgid "Time between merge request creation and merge/close" +msgstr "Temps entre la création d'une demande de fusion et sa fusion/clôture" + +msgid "Time until first merge request" +msgstr "Temps jusqu’à la première demande de fusion" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "hr" +msgstr[1] "hrs" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "min" +msgstr[1] "mins" + +msgid "Time|s" +msgstr "s" + +msgid "Total Time" +msgstr "Temps total" + +msgid "Total test time for all commits/merges" +msgstr "Temps total de test pour toutes les validations/fusions" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès." + +msgid "We don't have enough data to show this stage." +msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape." + +msgid "You need permission." +msgstr "Vous avez besoin d’une autorisation." + +msgid "day" +msgid_plural "days" +msgstr[0] "jour" +msgstr[1] "jours" diff --git a/locale/fr/gitlab.po.time_stamp b/locale/fr/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/locale/fr/gitlab.po.time_stamp diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 050f6c446c1..2d8076f5567 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 21:22+0200\n" -"PO-Revision-Date: 2017-06-07 21:22+0200\n" +"POT-Creation-Date: 2017-06-15 21:59-0500\n" +"PO-Revision-Date: 2017-06-15 21:59-0500\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -18,23 +18,217 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "" + +msgid "About auto deploy" +msgstr "" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "" + +msgid "Add Changelog" +msgstr "" + +msgid "Add Contribution guide" +msgstr "" + +msgid "Add License" +msgstr "" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" + +msgid "Add new directory" +msgstr "" + +msgid "Archived project! Repository is read-only" +msgstr "" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "" +msgstr[1] "" + +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 "" + +msgid "Branches" +msgstr "" + +msgid "Browse files" +msgstr "" + msgid "ByAuthor|by" msgstr "" +msgid "CI configuration" +msgstr "" + msgid "Cancel" msgstr "" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "" + +msgid "Changelog" +msgstr "" + +msgid "Charts" +msgstr "" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "" + +msgid "CiStatusLabel|created" +msgstr "" + +msgid "CiStatusLabel|failed" +msgstr "" + +msgid "CiStatusLabel|manual action" +msgstr "" + +msgid "CiStatusLabel|passed" +msgstr "" + +msgid "CiStatusLabel|passed with warnings" +msgstr "" + +msgid "CiStatusLabel|pending" +msgstr "" + +msgid "CiStatusLabel|skipped" +msgstr "" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "" + +msgid "CiStatusText|blocked" +msgstr "" + +msgid "CiStatusText|canceled" +msgstr "" + +msgid "CiStatusText|created" +msgstr "" + +msgid "CiStatusText|failed" +msgstr "" + +msgid "CiStatusText|manual" +msgstr "" + +msgid "CiStatusText|passed" +msgstr "" + +msgid "CiStatusText|pending" +msgstr "" + +msgid "CiStatusText|skipped" +msgstr "" + +msgid "CiStatus|running" +msgstr "" + msgid "Commit" msgid_plural "Commits" msgstr[0] "" msgstr[1] "" +msgid "Commit message" +msgstr "" + +msgid "CommitBoxTitle|Commit" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "" + +msgid "Commits" +msgstr "" + +msgid "Commits|History" +msgstr "" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "" + +msgid "Contribution guide" +msgstr "" + +msgid "Contributors" +msgstr "" + +msgid "Copy URL to clipboard" +msgstr "" + +msgid "Copy commit SHA to clipboard" +msgstr "" + +msgid "Create New Directory" +msgstr "" + +msgid "Create directory" +msgstr "" + +msgid "Create empty bare repository" +msgstr "" + +msgid "Create merge request" +msgstr "" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "" + +msgid "CreateTag|Tag" +msgstr "" + msgid "Cron Timezone" msgstr "" +msgid "Cron syntax" +msgstr "" + +msgid "Custom notification events" +msgstr "" + +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 "" + +msgid "Cycle Analytics" +msgstr "" + msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." msgstr "" @@ -59,6 +253,9 @@ msgstr "" msgid "CycleAnalyticsStage|Test" msgstr "" +msgid "Define a custom pattern with cron syntax" +msgstr "" + msgid "Delete" msgstr "" @@ -70,19 +267,67 @@ msgstr[1] "" msgid "Description" msgstr "" +msgid "Directory name" +msgstr "" + +msgid "Don't show again" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Download tar" +msgstr "" + +msgid "Download tar.bz2" +msgstr "" + +msgid "Download tar.gz" +msgstr "" + +msgid "Download zip" +msgstr "" + +msgid "DownloadArtifacts|Download" +msgstr "" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "" + msgid "Edit" msgstr "" msgid "Edit Pipeline Schedule %{id}" msgstr "" +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + msgid "Failed to change the owner" msgstr "" msgid "Failed to remove the pipeline schedule" msgstr "" -msgid "Filter" +msgid "Files" +msgstr "" + +msgid "Find by path" +msgstr "" + +msgid "Find file" msgstr "" msgid "FirstPushedBy|First" @@ -91,18 +336,47 @@ msgstr "" msgid "FirstPushedBy|pushed by" msgstr "" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "" + msgid "From issue creation until deploy to production" msgstr "" msgid "From merge request merge until deploy to production" msgstr "" +msgid "Go to your fork" +msgstr "" + +msgid "GoToYourFork|Fork" +msgstr "" + +msgid "Home" +msgstr "" + +msgid "Housekeeping successfully started" +msgstr "" + +msgid "Import repository" +msgstr "" + msgid "Interval Pattern" msgstr "" msgid "Introducing Cycle Analytics" msgstr "" +msgid "LFSStatus|Disabled" +msgstr "" + +msgid "LFSStatus|Enabled" +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -111,6 +385,24 @@ msgstr[1] "" msgid "Last Pipeline" msgstr "" +msgid "Last Update" +msgstr "" + +msgid "Last commit" +msgstr "" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "" + +msgid "Leave group" +msgstr "" + +msgid "Leave project" +msgstr "" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "" @@ -119,6 +411,9 @@ msgstr[1] "" msgid "Median" msgstr "" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "" @@ -127,6 +422,33 @@ msgstr[1] "" msgid "New Pipeline Schedule" msgstr "" +msgid "New branch" +msgstr "" + +msgid "New directory" +msgstr "" + +msgid "New file" +msgstr "" + +msgid "New issue" +msgstr "" + +msgid "New merge request" +msgstr "" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "" + +msgid "New tag" +msgstr "" + +msgid "No repository" +msgstr "" + msgid "No schedules" msgstr "" @@ -136,12 +458,75 @@ msgstr "" msgid "Not enough data" msgstr "" +msgid "Notification events" +msgstr "" + +msgid "NotificationEvent|Close issue" +msgstr "" + +msgid "NotificationEvent|Close merge request" +msgstr "" + +msgid "NotificationEvent|Failed pipeline" +msgstr "" + +msgid "NotificationEvent|Merge merge request" +msgstr "" + +msgid "NotificationEvent|New issue" +msgstr "" + +msgid "NotificationEvent|New merge request" +msgstr "" + +msgid "NotificationEvent|New note" +msgstr "" + +msgid "NotificationEvent|Reassign issue" +msgstr "" + +msgid "NotificationEvent|Reassign merge request" +msgstr "" + +msgid "NotificationEvent|Reopen issue" +msgstr "" + +msgid "NotificationEvent|Successful pipeline" +msgstr "" + +msgid "NotificationLevel|Custom" +msgstr "" + +msgid "NotificationLevel|Disabled" +msgstr "" + +msgid "NotificationLevel|Global" +msgstr "" + +msgid "NotificationLevel|On mention" +msgstr "" + +msgid "NotificationLevel|Participate" +msgstr "" + +msgid "NotificationLevel|Watch" +msgstr "" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + msgid "OpenedNDaysAgo|Opened" msgstr "" +msgid "Options" +msgstr "" + msgid "Owner" msgstr "" +msgid "Pipeline" +msgstr "" + msgid "Pipeline Health" msgstr "" @@ -178,12 +563,78 @@ msgstr "" msgid "PipelineSchedules|Target" msgstr "" +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "" + +msgid "Pipeline|with stages" +msgstr "" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "" + +msgid "Project '%{project_name}' will be deleted." +msgstr "" + +msgid "Project access must be granted explicitly to each user." +msgstr "" + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "Project export link has expired. Please generate a new export from your project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" + +msgid "Project home" +msgstr "" + +msgid "ProjectFeature|Disabled" +msgstr "" + +msgid "ProjectFeature|Everyone with access" +msgstr "" + +msgid "ProjectFeature|Only team members" +msgstr "" + +msgid "ProjectFileTree|Name" +msgstr "" + +msgid "ProjectLastActivity|Never" +msgstr "" + msgid "ProjectLifecycle|Stage" msgstr "" +msgid "ProjectNetworkGraph|Graph" +msgstr "" + msgid "Read more" msgstr "" +msgid "Readme" +msgstr "" + +msgid "RefSwitcher|Branches" +msgstr "" + +msgid "RefSwitcher|Tags" +msgstr "" + msgid "Related Commits" msgstr "" @@ -202,23 +653,82 @@ msgstr "" msgid "Related Merged Requests" msgstr "" +msgid "Remind later" +msgstr "" + +msgid "Remove project" +msgstr "" + +msgid "Request Access" +msgstr "" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge request" +msgstr "" + msgid "Save pipeline schedule" msgstr "" msgid "Schedule a new pipeline" msgstr "" +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Search branches and tags" +msgstr "" + +msgid "Select Archive Format" +msgstr "" + msgid "Select a timezone" msgstr "" msgid "Select target branch" msgstr "" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "" + +msgid "Set up CI" +msgstr "" + +msgid "Set up Koding" +msgstr "" + +msgid "Set up auto deploy" +msgstr "" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "" msgstr[1] "" +msgid "Source code" +msgstr "" + +msgid "StarProject|Star" +msgstr "" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "" +msgstr[1] "" + +msgid "Tags" +msgstr "" + msgid "Target Branch" msgstr "" @@ -228,18 +738,33 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "" +msgid "The fork relationship has been removed." +msgstr "" + 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 "" msgid "The phase of the development lifecycle." msgstr "" +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 "" + 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 "" 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 "" +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "" + 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 "" @@ -255,6 +780,9 @@ msgstr "" 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 "" +msgid "This means you can not push code until you create an empty repository or import existing one." +msgstr "" + msgid "Time before an issue gets scheduled" msgstr "" @@ -267,6 +795,129 @@ msgstr "" msgid "Time until first merge request" msgstr "" +msgid "Timeago|%s days ago" +msgstr "" + +msgid "Timeago|%s days remaining" +msgstr "" + +msgid "Timeago|%s hours remaining" +msgstr "" + +msgid "Timeago|%s minutes ago" +msgstr "" + +msgid "Timeago|%s minutes remaining" +msgstr "" + +msgid "Timeago|%s months ago" +msgstr "" + +msgid "Timeago|%s months remaining" +msgstr "" + +msgid "Timeago|%s seconds remaining" +msgstr "" + +msgid "Timeago|%s weeks ago" +msgstr "" + +msgid "Timeago|%s weeks remaining" +msgstr "" + +msgid "Timeago|%s years ago" +msgstr "" + +msgid "Timeago|%s years remaining" +msgstr "" + +msgid "Timeago|1 day remaining" +msgstr "" + +msgid "Timeago|1 hour remaining" +msgstr "" + +msgid "Timeago|1 minute remaining" +msgstr "" + +msgid "Timeago|1 month remaining" +msgstr "" + +msgid "Timeago|1 week remaining" +msgstr "" + +msgid "Timeago|1 year remaining" +msgstr "" + +msgid "Timeago|Past due" +msgstr "" + +msgid "Timeago|a day ago" +msgstr "" + +msgid "Timeago|a month ago" +msgstr "" + +msgid "Timeago|a week ago" +msgstr "" + +msgid "Timeago|a while" +msgstr "" + +msgid "Timeago|a year ago" +msgstr "" + +msgid "Timeago|about %s hours ago" +msgstr "" + +msgid "Timeago|about a minute ago" +msgstr "" + +msgid "Timeago|about an hour ago" +msgstr "" + +msgid "Timeago|in %s days" +msgstr "" + +msgid "Timeago|in %s hours" +msgstr "" + +msgid "Timeago|in %s minutes" +msgstr "" + +msgid "Timeago|in %s months" +msgstr "" + +msgid "Timeago|in %s seconds" +msgstr "" + +msgid "Timeago|in %s weeks" +msgstr "" + +msgid "Timeago|in %s years" +msgstr "" + +msgid "Timeago|in 1 day" +msgstr "" + +msgid "Timeago|in 1 hour" +msgstr "" + +msgid "Timeago|in 1 minute" +msgstr "" + +msgid "Timeago|in 1 month" +msgstr "" + +msgid "Timeago|in 1 week" +msgstr "" + +msgid "Timeago|in 1 year" +msgstr "" + +msgid "Timeago|less than a minute ago" +msgstr "" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "" @@ -286,16 +937,96 @@ msgstr "" msgid "Total test time for all commits/merges" msgstr "" +msgid "Unstar" +msgstr "" + +msgid "Upload New File" +msgstr "" + +msgid "Upload file" +msgstr "" + +msgid "Use your global notification setting" +msgstr "" + +msgid "VisibilityLevel|Internal" +msgstr "" + +msgid "VisibilityLevel|Private" +msgstr "" + +msgid "VisibilityLevel|Public" +msgstr "" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" msgid "We don't have enough data to show this stage." msgstr "" +msgid "Withdraw Access Request" +msgstr "" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You have reached your project limit" +msgstr "" + +msgid "You must sign in to star a project" +msgstr "" + msgid "You need permission." msgstr "" +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "" + +msgid "You will receive notifications only for comments in which you were @mentioned" +msgstr "" + +msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile" +msgstr "" + +msgid "Your name" +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" + +msgid "new merge request" +msgstr "" + +msgid "notification emails" +msgstr "" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index 5ad41f92b64..fe6d51c36ac 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -250,6 +250,9 @@ msgstr "Precisa visualizar os dados? Solicite acesso ao administrador." msgid "We don't have enough data to show this stage." msgstr "Não temos dados suficientes para mostrar esta fase." +msgid "You have reached your project limit" +msgstr "" + msgid "You need permission." msgstr "Você precisa de permissão." diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 11434460207..8d994ff3c4f 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -283,6 +283,9 @@ msgstr "æƒé™ä¸è¶³ã€‚如需查看相关数æ®ï¼Œè¯·å‘管ç†å‘˜ç”³è¯·æƒé™ã€‚ msgid "We don't have enough data to show this stage." msgstr "该阶段的数æ®ä¸è¶³ï¼Œæ— 法显示。" +msgid "You have reached your project limit" +msgstr "" + msgid "You need permission." msgstr "您需è¦ç›¸å…³çš„æƒé™ã€‚" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 8277c8d8e27..f4f70d80d34 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,18 +1,20 @@ # Huang Tao <htve@outlook.com>, 2017. #zanata +# Victor Wu <anonymous@domain.com>, 2017. +# Hazel Yang <anonymous@domain.com>, 2017. msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-15 14:57+0200\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-15 10:34-0400\n" +"PO-Revision-Date: 2017-06-19 09:57-0400\n" "Last-Translator: Huang Tao <htve@outlook.com>\n" "Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" -"Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=1; plural=0\n" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}" @@ -299,7 +301,7 @@ msgid "DownloadCommit|Email Patches" msgstr "é›»å郵件補ä¸" msgid "DownloadCommit|Plain Diff" -msgstr "Diff 文件" +msgstr "差異文件" msgid "DownloadSource|Download" msgstr "下載" @@ -311,13 +313,13 @@ msgid "Edit Pipeline Schedule %{id}" msgstr "編輯 %{id} æµæ°´ç·šè¨ˆåŠƒ" msgid "Every day (at 4:00am)" -msgstr "æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨4點)" +msgstr "æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨ 4 點)" msgid "Every month (on the 1st at 4:00am)" -msgstr "æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆ1日淩晨4點)" +msgstr "æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆ 1 日淩晨 4 點)" msgid "Every week (Sundays at 4:00am)" -msgstr "æ¯é€±åŸ·è¡Œï¼ˆå‘¨æ—¥æ·©æ™¨4點)" +msgstr "æ¯é€±åŸ·è¡Œï¼ˆå‘¨æ—¥æ·©æ™¨ 4 點)" msgid "Failed to change the owner" msgstr "無法變更所有者" @@ -369,7 +371,7 @@ msgid "Import repository" msgstr "å°Žå…¥å˜å„²åº«" msgid "Interval Pattern" -msgstr "é‡è¤‡é€±æœŸ" +msgstr "循環週期" msgid "Introducing Cycle Analytics" msgstr "週期分æžç°¡ä»‹" @@ -489,7 +491,7 @@ msgid "NotificationEvent|Reassign merge request" msgstr "é‡æ–°æŒ‡æ´¾åˆä½µè«‹æ±‚" msgid "NotificationEvent|Reopen issue" -msgstr "é‡æ–°æ‰“é–‹è°é¡Œ" +msgstr "é‡å•Ÿè°é¡Œ" msgid "NotificationEvent|Successful pipeline" msgstr "æµæ°´ç·šæˆåŠŸå®Œæˆ" @@ -662,7 +664,7 @@ msgid "Remove project" msgstr "åˆªé™¤é …ç›®" msgid "Request Access" -msgstr "申請訪å•" +msgstr "申請權é™" msgid "Revert this commit" msgstr "還原æ¤æ交" @@ -716,12 +718,9 @@ msgstr "æºä»£ç¢¼" msgid "StarProject|Star" msgstr "星標" -msgid "Start a %{new_merge_request} with these changes" +msgid "Start a %{new_merge_request} with these changes" msgstr "ç”±æ¤æ›´æ”¹ %{new_merge_request}" -msgid "Start a <strong>new merge request</strong> with these changes" -msgstr "ç”±æ¤æ›´æ”¹å‰µå»º<strong>æ–°åˆä½µè«‹æ±‚</strong>" - msgid "Switch branch/tag" msgstr "切æ›åˆ†æ”¯/標籤" @@ -751,7 +750,7 @@ 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 "è°é¡ŒéšŽæ®µæ¦‚述了從創建è°é¡Œåˆ°å°‡è°é¡Œæ·»åŠ 到è£ç¨‹ç¢‘或è°é¡Œçœ‹æ¿çš„時間。創建第壹個è°é¡Œå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•.。" +msgstr "è°é¡ŒéšŽæ®µæ¦‚述了從創建è°é¡Œåˆ°å°‡è°é¡Œæ·»åŠ 到è£ç¨‹ç¢‘或è°é¡Œçœ‹æ¿æ‰€èŠ±è²»çš„時間。創建第壹個è°é¡Œå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•.。" msgid "The phase of the development lifecycle." msgstr "é …ç›®ç”Ÿå‘½é€±æœŸä¸çš„å„個階段。" @@ -828,7 +827,7 @@ msgid "Time until first merge request" msgstr "創建第壹個åˆä½µè«‹æ±‚之å‰çš„時間" msgid "Timeago|%s days ago" -msgstr "%s 天å‰" +msgstr " %s 天å‰" msgid "Timeago|%s days remaining" msgstr "剩餘 %s 天" @@ -837,13 +836,13 @@ msgid "Timeago|%s hours remaining" msgstr "剩餘 %s å°æ™‚" msgid "Timeago|%s minutes ago" -msgstr "%s 分é˜å‰" +msgstr " %s 分é˜å‰" msgid "Timeago|%s minutes remaining" msgstr "剩餘 %s 分é˜" msgid "Timeago|%s months ago" -msgstr "%s 個月å‰" +msgstr " %s 個月å‰" msgid "Timeago|%s months remaining" msgstr "剩餘 %s 月" @@ -852,13 +851,13 @@ msgid "Timeago|%s seconds remaining" msgstr "剩餘 %s 秒" msgid "Timeago|%s weeks ago" -msgstr "%s 星期å‰" +msgstr " %s 星期å‰" msgid "Timeago|%s weeks remaining" msgstr "剩餘 %s 星期" msgid "Timeago|%s years ago" -msgstr "%s å¹´å‰" +msgstr " %s å¹´å‰" msgid "Timeago|%s years remaining" msgstr "剩餘 %s å¹´" @@ -885,19 +884,19 @@ msgid "Timeago|Past due" msgstr "逾期" msgid "Timeago|a day ago" -msgstr "1 天å‰" +msgstr " 1 天å‰" msgid "Timeago|a month ago" -msgstr "1 個月å‰" +msgstr " 1 個月å‰" msgid "Timeago|a week ago" -msgstr "1 星期å‰" +msgstr " 1 星期å‰" msgid "Timeago|a while" -msgstr "剛剛" +msgstr " 剛剛" msgid "Timeago|a year ago" -msgstr "1 å¹´å‰" +msgstr " 1 å¹´å‰" msgid "Timeago|about %s hours ago" msgstr "ç´„ %s å°æ™‚å‰" @@ -995,7 +994,7 @@ msgid "We don't have enough data to show this stage." msgstr "該階段的數據ä¸è¶³ï¼Œç„¡æ³•é¡¯ç¤ºã€‚" msgid "Withdraw Access Request" -msgstr "å–消訪å•è«‹æ±‚" +msgstr "å–消權é™ç”³è¯·" msgid "" "You are going to remove %{project_name_with_namespace}.\n" @@ -1018,6 +1017,9 @@ msgstr "å³å°‡ %{project_name_with_namespace} 轉義給å¦å£¹å€‹æ‰€æœ‰è€…。確å msgid "You can only add files when you are on a branch" msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ 文件" +msgid "You have reached your project limit" +msgstr "您已é”åˆ°é …ç›®æ•¸é‡é™åˆ¶" + msgid "You must sign in to star a project" msgstr "å¿…é ˆç™»éŒ„æ‰èƒ½å°é …ç›®åŠ æ˜Ÿæ¨™" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index e40723a9d8d..5130572d7ed 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -283,6 +283,9 @@ msgstr "權é™ä¸è¶³ã€‚如需查看相關資料,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚ msgid "We don't have enough data to show this stage." msgstr "å› è©²éšŽæ®µçš„è³‡æ–™ä¸è¶³è€Œç„¡æ³•é¡¯ç¤ºç›¸é—œè³‡è¨Š" +msgid "You have reached your project limit" +msgstr "" + msgid "You need permission." msgstr "您需è¦ç›¸é—œçš„權é™ã€‚" diff --git a/package.json b/package.json index 29165fd4182..045f07ee2f9 100644 --- a/package.json +++ b/package.json @@ -72,13 +72,13 @@ "eslint-plugin-jasmine": "^2.1.0", "eslint-plugin-promise": "^3.5.0", "istanbul": "^0.4.5", - "jasmine-core": "^2.5.2", + "jasmine-core": "^2.6.3", "jasmine-jquery": "^2.1.1", - "karma": "^1.4.1", + "karma": "^1.7.0", + "karma-chrome-launcher": "^2.1.1", "karma-coverage-istanbul-reporter": "^0.2.0", "karma-jasmine": "^1.1.0", "karma-mocha-reporter": "^2.2.2", - "karma-phantomjs-launcher": "^1.0.2", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.2", "nodemon": "^1.11.0", diff --git a/rubocop/cop/migration/add_timestamps.rb b/rubocop/cop/migration/add_timestamps.rb new file mode 100644 index 00000000000..08ddd91e54d --- /dev/null +++ b/rubocop/cop/migration/add_timestamps.rb @@ -0,0 +1,25 @@ +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if 'add_timestamps' method is called with timezone information. + class AddTimestamps < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = 'Do not use `add_timestamps`, use `add_timestamps_with_timezone` instead'.freeze + + # Check methods. + def on_send(node) + return unless in_migration?(node) + + add_offense(node, :selector) if method_name(node) == :add_timestamps + end + + def method_name(node) + node.children[1] + end + end + end + end +end diff --git a/rubocop/cop/migration/datetime.rb b/rubocop/cop/migration/datetime.rb new file mode 100644 index 00000000000..651935dd53e --- /dev/null +++ b/rubocop/cop/migration/datetime.rb @@ -0,0 +1,36 @@ +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if datetime data type is added with timezone information. + class Datetime < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = 'Do not use the `datetime` data type, use `datetime_with_timezone` instead'.freeze + + # Check methods in table creation. + def on_def(node) + return unless in_migration?(node) + + node.each_descendant(:send) do |send_node| + add_offense(send_node, :selector) if method_name(send_node) == :datetime + end + end + + # Check methods. + def on_send(node) + return unless in_migration?(node) + + node.each_descendant do |descendant| + add_offense(node, :expression) if descendant.type == :sym && descendant.children.last == :datetime + end + end + + def method_name(node) + node.children[1] + end + end + end + end +end diff --git a/rubocop/cop/migration/timestamps.rb b/rubocop/cop/migration/timestamps.rb new file mode 100644 index 00000000000..71a9420cc3b --- /dev/null +++ b/rubocop/cop/migration/timestamps.rb @@ -0,0 +1,27 @@ +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if 'timestamps' method is called with timezone information. + class Timestamps < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = 'Do not use `timestamps`, use `timestamps_with_timezone` instead'.freeze + + # Check methods in table creation. + def on_def(node) + return unless in_migration?(node) + + node.each_descendant(:send) do |send_node| + add_offense(send_node, :selector) if method_name(send_node) == :timestamps + end + end + + def method_name(node) + node.children[1] + end + end + end + end +end diff --git a/rubocop/cop/rspec/single_line_hook.rb b/rubocop/cop/rspec/single_line_hook.rb new file mode 100644 index 00000000000..be611054323 --- /dev/null +++ b/rubocop/cop/rspec/single_line_hook.rb @@ -0,0 +1,38 @@ +require 'rubocop-rspec' + +module RuboCop + module Cop + module RSpec + # This cop checks for single-line hook blocks + # + # @example + # + # # bad + # before { do_something } + # after(:each) { undo_something } + # + # # good + # before do + # do_something + # end + # + # after(:each) do + # undo_something + # end + class SingleLineHook < Cop + MESSAGE = "Don't use single-line hook blocks.".freeze + + def_node_search :rspec_hook?, <<~PATTERN + (send nil {:after :around :before} ...) + PATTERN + + def on_block(node) + return unless rspec_hook?(node) + return unless node.single_line? + + add_offense(node, :expression, MESSAGE) + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index dae30969abf..55d7708fa8c 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -8,7 +8,11 @@ require_relative 'cop/migration/add_column_with_default_to_large_table' require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_index' +require_relative 'cop/migration/add_timestamps' +require_relative 'cop/migration/datetime' require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_index' require_relative 'cop/migration/reversible_add_column_with_default' +require_relative 'cop/migration/timestamps' require_relative 'cop/migration/update_column_in_batches' +require_relative 'cop/rspec/single_line_hook' diff --git a/spec/controllers/admin/identities_controller_spec.rb b/spec/controllers/admin/identities_controller_spec.rb index c131d22a30a..a29853bf8df 100644 --- a/spec/controllers/admin/identities_controller_spec.rb +++ b/spec/controllers/admin/identities_controller_spec.rb @@ -2,7 +2,10 @@ require 'spec_helper' describe Admin::IdentitiesController do let(:admin) { create(:admin) } - before { sign_in(admin) } + + before do + sign_in(admin) + end describe 'UPDATE identity' do let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') } diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index c94616d8508..4ca0cfc74e9 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -3,7 +3,9 @@ require 'spec_helper' describe Admin::ServicesController do let(:admin) { create(:admin) } - before { sign_in(admin) } + before do + sign_in(admin) + end describe 'GET #edit' do let!(:project) { create(:empty_project) } diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 4c3a5ec49ef..b40f647644d 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -200,7 +200,9 @@ describe AutocompleteController do end context 'skip_users parameter included' do - before { sign_in(user) } + before do + sign_in(user) + end it 'skips the user IDs passed' do get(:users, skip_users: [user, user2].map(&:id)) diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index ed4ad7b600e..cce53f6697c 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -16,10 +16,14 @@ describe Groups::GroupMembersController do describe 'POST create' do let(:group_user) { create(:user) } - before { sign_in(user) } + before do + sign_in(user) + end context 'when user does not have enough rights' do - before { group.add_developer(user) } + before do + group.add_developer(user) + end it 'returns 403' do post :create, group_id: group, @@ -32,7 +36,9 @@ describe Groups::GroupMembersController do end context 'when user has enough rights' do - before { group.add_owner(user) } + before do + group.add_owner(user) + end it 'adds user to members' do post :create, group_id: group, @@ -59,7 +65,9 @@ describe Groups::GroupMembersController do describe 'DELETE destroy' do let(:member) { create(:group_member, :developer, group: group) } - before { sign_in(user) } + before do + sign_in(user) + end context 'when member is not found' do it 'returns 403' do @@ -71,7 +79,9 @@ describe Groups::GroupMembersController do context 'when member is found' do context 'when user does not have enough rights' do - before { group.add_developer(user) } + before do + group.add_developer(user) + end it 'returns 403' do delete :destroy, group_id: group, id: member @@ -82,7 +92,9 @@ describe Groups::GroupMembersController do end context 'when user has enough rights' do - before { group.add_owner(user) } + before do + group.add_owner(user) + end it '[HTML] removes user from members' do delete :destroy, group_id: group, id: member @@ -103,7 +115,9 @@ describe Groups::GroupMembersController do end describe 'DELETE leave' do - before { sign_in(user) } + before do + sign_in(user) + end context 'when member is not found' do it 'returns 404' do @@ -115,7 +129,9 @@ describe Groups::GroupMembersController do context 'when member is found' do context 'and is not an owner' do - before { group.add_developer(user) } + before do + group.add_developer(user) + end it 'removes user from members' do delete :leave, group_id: group @@ -134,7 +150,9 @@ describe Groups::GroupMembersController do end context 'and is an owner' do - before { group.add_owner(user) } + before do + group.add_owner(user) + end it 'cannot removes himself from the group' do delete :leave, group_id: group @@ -144,7 +162,9 @@ describe Groups::GroupMembersController do end context 'and is a requester' do - before { group.request_access(user) } + before do + group.request_access(user) + end it 'removes user from members' do delete :leave, group_id: group @@ -159,7 +179,9 @@ describe Groups::GroupMembersController do end describe 'POST request_access' do - before { sign_in(user) } + before do + sign_in(user) + end it 'creates a new GroupMember that is not a team member' do post :request_access, group_id: group @@ -174,7 +196,9 @@ describe Groups::GroupMembersController do describe 'POST approve_access_request' do let(:member) { create(:group_member, :access_request, group: group) } - before { sign_in(user) } + before do + sign_in(user) + end context 'when member is not found' do it 'returns 403' do @@ -186,7 +210,9 @@ describe Groups::GroupMembersController do context 'when member is found' do context 'when user does not have enough rights' do - before { group.add_developer(user) } + before do + group.add_developer(user) + end it 'returns 403' do post :approve_access_request, group_id: group, id: member @@ -197,7 +223,9 @@ describe Groups::GroupMembersController do end context 'when user has enough rights' do - before { group.add_owner(user) } + before do + group.add_owner(user) + end it 'adds user to members' do post :approve_access_request, group_id: group, id: member diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index b0b24b1de1b..c4092303a67 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe GroupsController do let(:user) { create(:user) } - let(:group) { create(:group) } + let(:group) { create(:group, :public) } let(:project) { create(:empty_project, namespace: group) } let!(:group_member) { create(:group_member, group: group, user: user) } @@ -35,14 +35,15 @@ describe GroupsController do sign_in(user) end - it 'shows the public subgroups' do + it 'shows all subgroups' do get :subgroups, id: group.to_param - expect(assigns(:nested_groups)).to contain_exactly(public_subgroup) + expect(assigns(:nested_groups)).to contain_exactly(public_subgroup, private_subgroup) end - context 'being member' do + context 'being member of private subgroup' do it 'shows public and private subgroups the user is member of' do + group_member.destroy! private_subgroup.add_guest(user) get :subgroups, id: group.to_param diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 9e3a31e1a6b..6b690407ce3 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -58,7 +58,10 @@ describe NotificationSettingsController do expect(response.status).to eq 200 expect(notification_setting.level).to eq("custom") - expect(notification_setting.events).to eq(custom_events) + + custom_events.each do |event, value| + expect(notification_setting.event_enabled?(event)).to eq(value) + end end end end @@ -86,7 +89,10 @@ describe NotificationSettingsController do expect(response.status).to eq 200 expect(notification_setting.level).to eq("custom") - expect(notification_setting.events).to eq(custom_events) + + custom_events.each do |event, value| + expect(notification_setting.event_enabled?(event)).to eq(value) + end end end end @@ -94,7 +100,10 @@ describe NotificationSettingsController do context 'not authorized' do let(:private_project) { create(:empty_project, :private) } - before { sign_in(user) } + + before do + sign_in(user) + end it 'returns 404' do post :create, @@ -120,7 +129,9 @@ describe NotificationSettingsController do end context 'when authorized' do - before{ sign_in(user) } + before do + sign_in(user) + end it 'returns success' do put :update, @@ -152,7 +163,9 @@ describe NotificationSettingsController do context 'not authorized' do let(:other_user) { create(:user) } - before { sign_in(other_user) } + before do + sign_in(other_user) + end it 'returns 404' do put :update, diff --git a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb index 98a43e278b2..ed08a4c1bf2 100644 --- a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb +++ b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb @@ -4,7 +4,9 @@ describe Profiles::PersonalAccessTokensController do let(:user) { create(:user) } let(:token_attributes) { attributes_for(:personal_access_token) } - before { sign_in(user) } + before do + sign_in(user) + end describe '#create' do def created_token @@ -38,7 +40,9 @@ describe Profiles::PersonalAccessTokensController do let!(:inactive_personal_access_token) { create(:personal_access_token, :revoked, user: user) } let!(:impersonation_personal_access_token) { create(:personal_access_token, :impersonation, user: user) } - before { get :index } + before do + get :index + end it "retrieves active personal access tokens" do expect(assigns(:active_personal_access_tokens)).to include(active_personal_access_token) diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 69e4706dc71..7fb08df1950 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -281,7 +281,9 @@ describe Projects::CommitController do end context 'when the path does not exist in the diff' do - before { diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) } + before do + diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) + end it 'returns a 404' do expect(response).to have_http_status(404) @@ -302,7 +304,9 @@ describe Projects::CommitController do end context 'when the commit does not exist' do - before { diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) } + before do + diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) + end it 'returns a 404' do expect(response).to have_http_status(404) diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 15ac4e0925a..8f4694c9854 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -128,7 +128,9 @@ describe Projects::CompareController do end context 'when the path does not exist in the diff' do - before { diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) } + before do + diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) + end it 'returns a 404' do expect(response).to have_http_status(404) @@ -149,7 +151,9 @@ describe Projects::CompareController do end context 'when the from ref does not exist' do - before { diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) } + before do + diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) + end it 'returns a 404' do expect(response).to have_http_status(404) @@ -157,7 +161,9 @@ describe Projects::CompareController do end context 'when the to ref does not exist' do - before { diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) } + before do + diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) + end it 'returns a 404' do expect(response).to have_http_status(404) diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 8282d79298f..dc8290c438e 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -14,7 +14,9 @@ describe Projects::ForksController do end context 'when fork is public' do - before { forked_project.update_attribute(:visibility_level, Project::PUBLIC) } + before do + forked_project.update_attribute(:visibility_level, Project::PUBLIC) + end it 'is visible for non logged in users' do get_forks @@ -35,7 +37,9 @@ describe Projects::ForksController do end context 'when user is logged in' do - before { sign_in(project.creator) } + before do + sign_in(project.creator) + end context 'when user is not a Project member neither a group member' do it 'does not see the Project listed' do @@ -46,7 +50,9 @@ describe Projects::ForksController do end context 'when user is a member of the Project' do - before { forked_project.team << [project.creator, :developer] } + before do + forked_project.team << [project.creator, :developer] + end it 'sees the project listed' do get_forks @@ -56,7 +62,9 @@ describe Projects::ForksController do end context 'when user is a member of the Group' do - before { forked_project.group.add_developer(project.creator) } + before do + forked_project.group.add_developer(project.creator) + end it 'sees the project listed' do get_forks diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index ca4a8e871c0..b5435357f53 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -22,7 +22,10 @@ describe Projects::GroupLinksController do end context 'when user has access to group he want to link project to' do - before { group.add_developer(user) } + before do + group.add_developer(user) + end + include_context 'link project to group' it 'links project with selected group' do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index b65e9e0dfc0..eed3c3a9098 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -212,7 +212,9 @@ describe Projects::IssuesController do let(:another_project) { create(:empty_project, :private) } context 'when user has access to move issue' do - before { another_project.team << [user, :reporter] } + before do + another_project.team << [user, :reporter] + end it 'moves issue to another project' do move_issue @@ -250,14 +252,18 @@ describe Projects::IssuesController do end context 'when an issue is identified as spam' do - before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) } + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end context 'when captcha is not verified' do def update_spam_issue update_issue(title: 'Spam Title', description: 'Spam lives here') end - before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) } + before do + allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) + end it 'rejects an issue recognized as a spam' do expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true) @@ -620,14 +626,18 @@ describe Projects::IssuesController do end context 'when an issue is identified as spam' do - before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) } + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end context 'when captcha is not verified' do def post_spam_issue post_new_issue(title: 'Spam Title', description: 'Spam lives here') end - before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) } + before do + allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) + end it 'rejects an issue recognized as a spam' do expect { post_spam_issue }.not_to change(Issue, :count) @@ -692,7 +702,7 @@ describe Projects::IssuesController do end end - context 'when description has slash commands' do + context 'when description has quick actions' do before do sign_in(user) end @@ -739,7 +749,10 @@ describe Projects::IssuesController do describe "DELETE #destroy" do context "when the user is a developer" do - before { sign_in(user) } + before do + sign_in(user) + end + it "rejects a developer to destroy an issue" do delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid expect(response).to have_http_status(404) @@ -751,7 +764,9 @@ describe Projects::IssuesController do let(:namespace) { create(:namespace, owner: owner) } let(:project) { create(:empty_project, namespace: namespace) } - before { sign_in(owner) } + before do + sign_in(owner) + end it "deletes the issue" do delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 4a737587899..472e5fc51a0 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -28,7 +28,7 @@ describe Projects::JobsController do get_index(scope: 'running') end - it 'has only running builds' do + it 'has only running jobs' do expect(response).to have_http_status(:ok) expect(assigns(:builds).first.status).to eq('running') end @@ -41,7 +41,7 @@ describe Projects::JobsController do get_index(scope: 'finished') end - it 'has only finished builds' do + it 'has only finished jobs' do expect(response).to have_http_status(:ok) expect(assigns(:builds).first.status).to eq('success') end @@ -67,7 +67,7 @@ describe Projects::JobsController do context 'number of queries' do before do Ci::Build::AVAILABLE_STATUSES.each do |status| - create_build(status, status) + create_job(status, status) end end @@ -76,7 +76,7 @@ describe Projects::JobsController do expect(recorded.count).to be_within(5).of(7) end - def create_build(name, status) + def create_job(name, status) pipeline = create(:ci_pipeline, project: project) create(:ci_build, :tags, :triggered, :artifacts, pipeline: pipeline, name: name, status: status) @@ -94,21 +94,21 @@ describe Projects::JobsController do end describe 'GET show' do - let!(:build) { create(:ci_build, :failed, pipeline: pipeline) } + let!(:job) { create(:ci_build, :failed, pipeline: pipeline) } context 'when requesting HTML' do - context 'when build exists' do + context 'when job exists' do before do - get_show(id: build.id) + get_show(id: job.id) end - it 'has a build' do + it 'has a job' do expect(response).to have_http_status(:ok) - expect(assigns(:build).id).to eq(build.id) + expect(assigns(:build).id).to eq(job.id) end end - context 'when build does not exist' do + context 'when job does not exist' do before do get_show(id: 1234) end @@ -128,12 +128,12 @@ describe Projects::JobsController do allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request) - get_show(id: build.id, format: :json) + get_show(id: job.id, format: :json) end it 'exposes needed information' do expect(response).to have_http_status(:ok) - expect(json_response['raw_path']).to match(/builds\/\d+\/raw\z/) + expect(json_response['raw_path']).to match(/jobs\/\d+\/raw\z/) expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/) expect(json_response['new_issue_path']) .to include('/issues/new') @@ -155,35 +155,35 @@ describe Projects::JobsController do get_trace end - context 'when build has a trace' do - let(:build) { create(:ci_build, :trace, pipeline: pipeline) } + context 'when job has a trace' do + let(:job) { create(:ci_build, :trace, pipeline: pipeline) } it 'returns a trace' do expect(response).to have_http_status(:ok) - expect(json_response['id']).to eq build.id - expect(json_response['status']).to eq build.status + expect(json_response['id']).to eq job.id + expect(json_response['status']).to eq job.status expect(json_response['html']).to eq('BUILD TRACE') end end - context 'when build has no traces' do - let(:build) { create(:ci_build, pipeline: pipeline) } + context 'when job has no traces' do + let(:job) { create(:ci_build, pipeline: pipeline) } it 'returns no traces' do expect(response).to have_http_status(:ok) - expect(json_response['id']).to eq build.id - expect(json_response['status']).to eq build.status + expect(json_response['id']).to eq job.id + expect(json_response['status']).to eq job.status expect(json_response['html']).to be_nil end end - context 'when build has a trace with ANSI sequence and Unicode' do - let(:build) { create(:ci_build, :unicode_trace, pipeline: pipeline) } + context 'when job has a trace with ANSI sequence and Unicode' do + let(:job) { create(:ci_build, :unicode_trace, pipeline: pipeline) } it 'returns a trace with Unicode' do expect(response).to have_http_status(:ok) - expect(json_response['id']).to eq build.id - expect(json_response['status']).to eq build.status + expect(json_response['id']).to eq job.id + expect(json_response['status']).to eq job.status expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ") end end @@ -191,23 +191,23 @@ describe Projects::JobsController do def get_trace get :trace, namespace_id: project.namespace, project_id: project, - id: build.id, + id: job.id, format: :json end end describe 'GET status.json' do - let(:build) { create(:ci_build, pipeline: pipeline) } - let(:status) { build.detailed_status(double('user')) } + let(:job) { create(:ci_build, pipeline: pipeline) } + let(:status) { job.detailed_status(double('user')) } before do get :status, namespace_id: project.namespace, project_id: project, - id: build.id, + id: job.id, format: :json end - it 'return a detailed build status in json' do + it 'return a detailed job status in json' do expect(response).to have_http_status(:ok) expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label @@ -224,17 +224,17 @@ describe Projects::JobsController do post_retry end - context 'when build is retryable' do - let(:build) { create(:ci_build, :retryable, pipeline: pipeline) } + context 'when job is retryable' do + let(:job) { create(:ci_build, :retryable, pipeline: pipeline) } - it 'redirects to the retried build page' do + it 'redirects to the retried job page' do expect(response).to have_http_status(:found) expect(response).to redirect_to(namespace_project_job_path(id: Ci::Build.last.id)) end end - context 'when build is not retryable' do - let(:build) { create(:ci_build, pipeline: pipeline) } + context 'when job is not retryable' do + let(:job) { create(:ci_build, pipeline: pipeline) } it 'renders unprocessable_entity' do expect(response).to have_http_status(:unprocessable_entity) @@ -244,7 +244,7 @@ describe Projects::JobsController do def post_retry post :retry, namespace_id: project.namespace, project_id: project, - id: build.id + id: job.id end end @@ -260,21 +260,21 @@ describe Projects::JobsController do post_play end - context 'when build is playable' do - let(:build) { create(:ci_build, :playable, pipeline: pipeline) } + context 'when job is playable' do + let(:job) { create(:ci_build, :playable, pipeline: pipeline) } - it 'redirects to the played build page' do + it 'redirects to the played job page' do expect(response).to have_http_status(:found) - expect(response).to redirect_to(namespace_project_job_path(id: build.id)) + expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end it 'transits to pending' do - expect(build.reload).to be_pending + expect(job.reload).to be_pending end end - context 'when build is not playable' do - let(:build) { create(:ci_build, pipeline: pipeline) } + context 'when job is not playable' do + let(:job) { create(:ci_build, pipeline: pipeline) } it 'renders unprocessable_entity' do expect(response).to have_http_status(:unprocessable_entity) @@ -284,7 +284,7 @@ describe Projects::JobsController do def post_play post :play, namespace_id: project.namespace, project_id: project, - id: build.id + id: job.id end end @@ -296,21 +296,21 @@ describe Projects::JobsController do post_cancel end - context 'when build is cancelable' do - let(:build) { create(:ci_build, :cancelable, pipeline: pipeline) } + context 'when job is cancelable' do + let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } - it 'redirects to the canceled build page' do + it 'redirects to the canceled job page' do expect(response).to have_http_status(:found) - expect(response).to redirect_to(namespace_project_job_path(id: build.id)) + expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end it 'transits to canceled' do - expect(build.reload).to be_canceled + expect(job.reload).to be_canceled end end - context 'when build is not cancelable' do - let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } + context 'when job is not cancelable' do + let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } it 'returns unprocessable_entity' do expect(response).to have_http_status(:unprocessable_entity) @@ -320,7 +320,7 @@ describe Projects::JobsController do def post_cancel post :cancel, namespace_id: project.namespace, project_id: project, - id: build.id + id: job.id end end @@ -330,7 +330,7 @@ describe Projects::JobsController do sign_in(user) end - context 'when builds are cancelable' do + context 'when jobs are cancelable' do before do create_list(:ci_build, 2, :cancelable, pipeline: pipeline) @@ -347,7 +347,7 @@ describe Projects::JobsController do end end - context 'when builds are not cancelable' do + context 'when jobs are not cancelable' do before do create_list(:ci_build, 2, :canceled, pipeline: pipeline) @@ -374,26 +374,26 @@ describe Projects::JobsController do post_erase end - context 'when build is erasable' do - let(:build) { create(:ci_build, :erasable, :trace, pipeline: pipeline) } + context 'when job is erasable' do + let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline) } - it 'redirects to the erased build page' do + it 'redirects to the erased job page' do expect(response).to have_http_status(:found) - expect(response).to redirect_to(namespace_project_job_path(id: build.id)) + expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end it 'erases artifacts' do - expect(build.artifacts_file.exists?).to be_falsey - expect(build.artifacts_metadata.exists?).to be_falsey + expect(job.artifacts_file.exists?).to be_falsey + expect(job.artifacts_metadata.exists?).to be_falsey end it 'erases trace' do - expect(build.trace.exist?).to be_falsey + expect(job.trace.exist?).to be_falsey end end - context 'when build is not erasable' do - let(:build) { create(:ci_build, :erased, pipeline: pipeline) } + context 'when job is not erasable' do + let(:job) { create(:ci_build, :erased, pipeline: pipeline) } it 'returns unprocessable_entity' do expect(response).to have_http_status(:unprocessable_entity) @@ -403,7 +403,7 @@ describe Projects::JobsController do def post_erase post :erase, namespace_id: project.namespace, project_id: project, - id: build.id + id: job.id end end @@ -412,8 +412,8 @@ describe Projects::JobsController do get_raw end - context 'when build has a trace file' do - let(:build) { create(:ci_build, :trace, pipeline: pipeline) } + context 'when job has a trace file' do + let(:job) { create(:ci_build, :trace, pipeline: pipeline) } it 'send a trace file' do expect(response).to have_http_status(:ok) @@ -422,8 +422,8 @@ describe Projects::JobsController do end end - context 'when build does not have a trace file' do - let(:build) { create(:ci_build, pipeline: pipeline) } + context 'when job does not have a trace file' do + let(:job) { create(:ci_build, pipeline: pipeline) } it 'returns not_found' do expect(response).to have_http_status(:not_found) @@ -433,7 +433,7 @@ describe Projects::JobsController do def get_raw post :raw, namespace_id: project.namespace, project_id: project, - id: build.id + id: job.id end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6e1c91738db..d8a3a510f97 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -19,7 +19,10 @@ describe Projects::MergeRequestsController do render_views let(:fork_project) { create(:forked_project_with_submodules) } - before { fork_project.team << [user, :master] } + + before do + fork_project.team << [user, :master] + end context 'when rendering HTML response' do it 'renders new merge request widget template' do @@ -328,7 +331,9 @@ describe Projects::MergeRequestsController do end context 'when the sha parameter does not match the source SHA' do - before { post :merge, base_params.merge(sha: 'foo') } + before do + post :merge, base_params.merge(sha: 'foo') + end it 'returns :sha_mismatch' do expect(json_response).to eq('status' => 'sha_mismatch') @@ -473,7 +478,9 @@ describe Projects::MergeRequestsController do let(:namespace) { create(:namespace, owner: owner) } let(:project) { create(:project, namespace: namespace) } - before { sign_in owner } + before do + sign_in owner + end it "deletes the merge request" do delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid @@ -505,7 +512,9 @@ describe Projects::MergeRequestsController do context 'with default params' do context 'as html' do - before { go(format: 'html') } + before do + go(format: 'html') + end it 'renders the diff template' do expect(response).to render_template('diffs') @@ -513,7 +522,9 @@ describe Projects::MergeRequestsController do end context 'as json' do - before { go(format: 'json') } + before do + go(format: 'json') + end it 'renders the diffs template to a string' do expect(response).to render_template('projects/merge_requests/show/_diffs') @@ -544,7 +555,9 @@ describe Projects::MergeRequestsController do context 'with ignore_whitespace_change' do context 'as html' do - before { go(format: 'html', w: 1) } + before do + go(format: 'html', w: 1) + end it 'renders the diff template' do expect(response).to render_template('diffs') @@ -552,7 +565,9 @@ describe Projects::MergeRequestsController do end context 'as json' do - before { go(format: 'json', w: 1) } + before do + go(format: 'json', w: 1) + end it 'renders the diffs template to a string' do expect(response).to render_template('projects/merge_requests/show/_diffs') @@ -562,7 +577,9 @@ describe Projects::MergeRequestsController do end context 'with view' do - before { go(view: 'parallel') } + before do + go(view: 'parallel') + end it 'saves the preferred diff view in a cookie' do expect(response.cookies['diff_view']).to eq('parallel') @@ -605,7 +622,9 @@ describe Projects::MergeRequestsController do end context 'when the path does not exist in the diff' do - before { diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') } + before do + diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') + end it 'returns a 404' do expect(response).to have_http_status(404) @@ -626,7 +645,9 @@ describe Projects::MergeRequestsController do end context 'when the merge request does not exist' do - before { diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) } + before do + diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) + end it 'returns a 404' do expect(response).to have_http_status(404) @@ -670,7 +691,9 @@ describe Projects::MergeRequestsController do context 'when the source branch is in a different project to the target' do let(:other_project) { create(:project) } - before { other_project.team << [user, :master] } + before do + other_project.team << [user, :master] + end context 'when the path exists in the diff' do it 'disables diff notes' do @@ -690,7 +713,9 @@ describe Projects::MergeRequestsController do end context 'when the path does not exist in the diff' do - before { diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) } + before do + diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + end it 'returns a 404' do expect(response).to have_http_status(404) @@ -913,7 +938,9 @@ describe Projects::MergeRequestsController do end context 'when the file does not exist cannot be resolved in the UI' do - before { conflict_for_path('files/ruby/regexp.rb') } + before do + conflict_for_path('files/ruby/regexp.rb') + end it 'returns a 404 status code' do expect(response).to have_http_status(:not_found) @@ -923,7 +950,9 @@ describe Projects::MergeRequestsController do context 'with an existing file' do let(:path) { 'files/ruby/regex.rb' } - before { conflict_for_path(path) } + before do + conflict_for_path(path) + end it 'returns a 200 status code' do expect(response).to have_http_status(:ok) @@ -1195,7 +1224,9 @@ describe Projects::MergeRequestsController do end context 'when head_pipeline does not exist' do - before { get_pipeline_status } + before do + get_pipeline_status + end it 'return empty' do expect(response).to have_http_status(:ok) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 954f89e3854..734532668d3 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -5,9 +5,12 @@ describe Projects::PipelinesController do let(:user) { create(:user) } let(:project) { create(:empty_project, :public) } + let(:feature) { ProjectFeature::DISABLED } before do project.add_developer(user) + project.project_feature.update( + builds_access_level: feature) sign_in(user) end @@ -153,16 +156,26 @@ describe Projects::PipelinesController do format: :json end - it 'retries a pipeline without returning any content' do - expect(response).to have_http_status(:no_content) - expect(build.reload).to be_retried + context 'when builds are enabled' do + let(:feature) { ProjectFeature::ENABLED } + + it 'retries a pipeline without returning any content' do + expect(response).to have_http_status(:no_content) + expect(build.reload).to be_retried + end + end + + context 'when builds are disabled' do + it 'fails to retry pipeline' do + expect(response).to have_http_status(:not_found) + end end end describe 'POST cancel.json' do let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:build) { create(:ci_build, :running, pipeline: pipeline) } - + before do post :cancel, namespace_id: project.namespace, project_id: project, @@ -170,9 +183,19 @@ describe Projects::PipelinesController do format: :json end - it 'cancels a pipeline without returning any content' do - expect(response).to have_http_status(:no_content) - expect(pipeline.reload).to be_canceled + context 'when builds are enabled' do + let(:feature) { ProjectFeature::ENABLED } + + it 'cancels a pipeline without returning any content' do + expect(response).to have_http_status(:no_content) + expect(pipeline.reload).to be_canceled + end + end + + context 'when builds are disabled' do + it 'fails to retry pipeline' do + expect(response).to have_http_status(:not_found) + end end end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 2294d5df581..f2b59ba82ca 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -16,10 +16,14 @@ describe Projects::ProjectMembersController do describe 'POST create' do let(:project_user) { create(:user) } - before { sign_in(user) } + before do + sign_in(user) + end context 'when user does not have enough rights' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'returns 404' do post :create, namespace_id: project.namespace, @@ -33,7 +37,9 @@ describe Projects::ProjectMembersController do end context 'when user has enough rights' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'adds user to members' do expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :success) @@ -64,7 +70,9 @@ describe Projects::ProjectMembersController do describe 'DELETE destroy' do let(:member) { create(:project_member, :developer, project: project) } - before { sign_in(user) } + before do + sign_in(user) + end context 'when member is not found' do it 'returns 404' do @@ -78,7 +86,9 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'when user does not have enough rights' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'returns 404' do delete :destroy, namespace_id: project.namespace, @@ -91,7 +101,9 @@ describe Projects::ProjectMembersController do end context 'when user has enough rights' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it '[HTML] removes user from members' do delete :destroy, namespace_id: project.namespace, @@ -117,7 +129,9 @@ describe Projects::ProjectMembersController do end describe 'DELETE leave' do - before { sign_in(user) } + before do + sign_in(user) + end context 'when member is not found' do it 'returns 404' do @@ -130,7 +144,9 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'and is not an owner' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'removes user from members' do delete :leave, namespace_id: project.namespace, @@ -145,7 +161,9 @@ describe Projects::ProjectMembersController do context 'and is an owner' do let(:project) { create(:empty_project, namespace: user.namespace) } - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'cannot remove himself from the project' do delete :leave, namespace_id: project.namespace, @@ -156,7 +174,9 @@ describe Projects::ProjectMembersController do end context 'and is a requester' do - before { project.request_access(user) } + before do + project.request_access(user) + end it 'removes user from members' do delete :leave, namespace_id: project.namespace, @@ -172,7 +192,9 @@ describe Projects::ProjectMembersController do end describe 'POST request_access' do - before { sign_in(user) } + before do + sign_in(user) + end it 'creates a new ProjectMember that is not a team member' do post :request_access, namespace_id: project.namespace, @@ -190,7 +212,9 @@ describe Projects::ProjectMembersController do describe 'POST approve' do let(:member) { create(:project_member, :access_request, project: project) } - before { sign_in(user) } + before do + sign_in(user) + end context 'when member is not found' do it 'returns 404' do @@ -204,7 +228,9 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'when user does not have enough rights' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'returns 404' do post :approve_access_request, namespace_id: project.namespace, @@ -217,7 +243,9 @@ describe Projects::ProjectMembersController do end context 'when user has enough rights' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'adds user to members' do post :approve_access_request, namespace_id: project.namespace, @@ -252,7 +280,10 @@ describe Projects::ProjectMembersController do end context 'when user can access source project members' do - before { another_project.team << [user, :guest] } + before do + another_project.team << [user, :guest] + end + include_context 'import applied' it 'imports source project members' do diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 8c23c46798e..2434f822c6f 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -46,7 +46,9 @@ describe Projects::SnippetsController do end context 'when signed in as the author' do - before { sign_in(user) } + before do + sign_in(user) + end it 'renders the snippet' do get :index, namespace_id: project.namespace, project_id: project @@ -57,7 +59,9 @@ describe Projects::SnippetsController do end context 'when signed in as a project member' do - before { sign_in(user2) } + before do + sign_in(user2) + end it 'renders the snippet' do get :index, namespace_id: project.namespace, project_id: project @@ -317,7 +321,9 @@ describe Projects::SnippetsController do end context 'when signed in as the author' do - before { sign_in(user) } + before do + sign_in(user) + end it 'renders the snippet' do get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param @@ -328,7 +334,9 @@ describe Projects::SnippetsController do end context 'when signed in as a project member' do - before { sign_in(user2) } + before do + sign_in(user2) + end it 'renders the snippet' do get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param @@ -349,7 +357,9 @@ describe Projects::SnippetsController do end context 'when signed in' do - before { sign_in(user) } + before do + sign_in(user) + end it 'responds with status 404' do get action, namespace_id: project.namespace, project_id: project, id: 42 diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index fc97bac64cd..c48f41ca12e 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -6,7 +6,9 @@ describe Projects::TagsController do let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') } describe 'GET index' do - before { get :index, namespace_id: project.namespace.to_param, project_id: project } + before do + get :index, namespace_id: project.namespace.to_param, project_id: project + end it 'returns the tags for the page' do expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0']) @@ -19,7 +21,9 @@ describe Projects::TagsController do end describe 'GET show' do - before { get :show, namespace_id: project.namespace.to_param, project_id: project, id: id } + before do + get :show, namespace_id: project.namespace.to_param, project_id: project, id: id + end context "valid tag" do let(:id) { 'v1.0.0' } diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4f6fc6691be..240a81367d0 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -29,7 +29,9 @@ describe ProjectsController do describe "GET show" do context "user not project member" do - before { sign_in(user) } + before do + sign_in(user) + end context "user does not have access to project" do let(:private_project) { create(:empty_project, :private) } @@ -108,7 +110,9 @@ describe ProjectsController do context "project with empty repo" do let(:empty_project) { create(:project_empty_repo, :public) } - before { sign_in(user) } + before do + sign_in(user) + end User.project_views.keys.each do |project_view| context "with #{project_view} view set" do @@ -128,7 +132,9 @@ describe ProjectsController do context "project with broken repo" do let(:empty_project) { create(:project_broken_repo, :public) } - before { sign_in(user) } + before do + sign_in(user) + end User.project_views.keys.each do |project_view| context "with #{project_view} view set" do diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 3173aae664c..a3708ad0908 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -18,7 +18,9 @@ describe SearchController do context 'on restricted projects' do context 'when signed out' do - before { sign_out(user) } + before do + sign_out(user) + end it "doesn't expose comments on issues" do project = create(:empty_project, :public, :issues_private) diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 954fc2eaf21..0cc8a3b68eb 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -14,7 +14,9 @@ describe SentNotificationsController, type: :controller do describe 'GET unsubscribe' do context 'when the user is not logged in' do context 'when the force param is passed' do - before { get(:unsubscribe, id: sent_notification.reply_key, force: true) } + before do + get(:unsubscribe, id: sent_notification.reply_key, force: true) + end it 'unsubscribes the user' do expect(issue.subscribed?(user, project)).to be_falsey @@ -30,7 +32,9 @@ describe SentNotificationsController, type: :controller do end context 'when the force param is not passed' do - before { get(:unsubscribe, id: sent_notification.reply_key) } + before do + get(:unsubscribe, id: sent_notification.reply_key) + end it 'does not unsubscribe the user' do expect(issue.subscribed?(user, project)).to be_truthy @@ -47,10 +51,14 @@ describe SentNotificationsController, type: :controller do end context 'when the user is logged in' do - before { sign_in(user) } + before do + sign_in(user) + end context 'when the ID passed does not exist' do - before { get(:unsubscribe, id: sent_notification.reply_key.reverse) } + before do + get(:unsubscribe, id: sent_notification.reply_key.reverse) + end it 'does not unsubscribe the user' do expect(issue.subscribed?(user, project)).to be_truthy @@ -66,7 +74,9 @@ describe SentNotificationsController, type: :controller do end context 'when the force param is passed' do - before { get(:unsubscribe, id: sent_notification.reply_key, force: true) } + before do + get(:unsubscribe, id: sent_notification.reply_key, force: true) + end it 'unsubscribes the user' do expect(issue.subscribed?(user, project)).to be_falsey @@ -89,7 +99,10 @@ describe SentNotificationsController, type: :controller do end end let(:sent_notification) { create(:sent_notification, project: project, noteable: merge_request, recipient: user) } - before { get(:unsubscribe, id: sent_notification.reply_key) } + + before do + get(:unsubscribe, id: sent_notification.reply_key) + end it 'unsubscribes the user' do expect(merge_request.subscribed?(user, project)).to be_falsey diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index e87e24a33a1..03f4b0ba343 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -142,7 +142,9 @@ describe SessionsController do end context 'when OTP is invalid' do - before { authenticate_2fa(otp_attempt: 'invalid') } + before do + authenticate_2fa(otp_attempt: 'invalid') + end it 'does not authenticate' do expect(subject.current_user).not_to eq user @@ -169,7 +171,9 @@ describe SessionsController do end context 'when OTP is invalid' do - before { authenticate_2fa(otp_attempt: 'invalid') } + before do + authenticate_2fa(otp_attempt: 'invalid') + end it 'does not authenticate' do expect(subject.current_user).not_to eq user diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 9073c39f562..b1339b2a185 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -437,7 +437,9 @@ describe SnippetsController do end context 'when signed in user is the author' do - before { get :raw, id: personal_snippet.to_param } + before do + get :raw, id: personal_snippet.to_param + end it 'responds with status 200' do expect(assigns(:snippet)).to eq(personal_snippet) diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index d33e2ba1e53..842d82cdbe9 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -43,7 +43,9 @@ describe UsersController do end context 'when logged in' do - before { sign_in(user) } + before do + sign_in(user) + end it 'renders show' do get :show, username: user.username @@ -62,7 +64,9 @@ describe UsersController do end context 'when logged in' do - before { sign_in(user) } + before do + sign_in(user) + end it 'renders 404' do get :show, username: 'nonexistent' diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb new file mode 100644 index 00000000000..aef65e724c2 --- /dev/null +++ b/spec/factories/application_settings.rb @@ -0,0 +1,4 @@ +FactoryGirl.define do + factory :application_setting do + end +end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 0bb5a86d9b9..0cc498f0ce9 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -194,8 +194,8 @@ FactoryGirl.define do trait :extended_options do options do { - image: 'ruby:2.1', - services: ['postgres'], + image: { name: 'ruby:2.1', entrypoint: '/bin/sh' }, + services: ['postgres', { name: 'docker:dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }], after_script: %w(ls date), artifacts: { name: 'artifacts_file', diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index 1e11fb756b2..5e6cd64c5c1 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -4,7 +4,7 @@ feature 'Abuse reports', feature: true do let(:another_user) { create(:user) } before do - login_as :user + gitlab_sign_in :user end scenario 'Report abuse' do diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 340884fc986..3a6e356b0b0 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -5,7 +5,7 @@ describe "Admin::AbuseReports", feature: true, js: true do context 'as an admin' do before do - login_as :admin + gitlab_sign_in :admin end describe 'if a user has been reported for abuse' do diff --git a/spec/features/admin/admin_active_tab_spec.rb b/spec/features/admin/admin_active_tab_spec.rb index 16064d60ce2..c74336d8221 100644 --- a/spec/features/admin/admin_active_tab_spec.rb +++ b/spec/features/admin/admin_active_tab_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe 'admin active tab' do before do - login_as :admin + gitlab_sign_in :admin end shared_examples 'page has active tab' do |title| diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 595366ce352..d8fd4319328 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -4,7 +4,7 @@ feature 'Admin Appearance', feature: true do let!(:appearance) { create(:appearance) } scenario 'Create new appearance' do - login_as :admin + gitlab_sign_in :admin visit admin_appearances_path fill_in 'appearance_title', with: 'MyCompany' @@ -20,7 +20,7 @@ feature 'Admin Appearance', feature: true do end scenario 'Preview appearance' do - login_as :admin + gitlab_sign_in :admin visit admin_appearances_path click_link "Preview" @@ -34,7 +34,7 @@ feature 'Admin Appearance', feature: true do end scenario 'Appearance logo' do - login_as :admin + gitlab_sign_in :admin visit admin_appearances_path attach_file(:appearance_logo, logo_fixture) @@ -46,7 +46,7 @@ feature 'Admin Appearance', feature: true do end scenario 'Header logos' do - login_as :admin + gitlab_sign_in :admin visit admin_appearances_path attach_file(:appearance_header_logo, logo_fixture) diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb index d6c63f66a9b..da063bf7b74 100644 --- a/spec/features/admin/admin_broadcast_messages_spec.rb +++ b/spec/features/admin/admin_broadcast_messages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Admin Broadcast Messages', feature: true do before do - login_as :admin + gitlab_sign_in :admin create(:broadcast_message, :expired, message: 'Migration to new server') visit admin_broadcast_messages_path end diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb index bee57472270..d9c4fc686b1 100644 --- a/spec/features/admin/admin_browse_spam_logs_spec.rb +++ b/spec/features/admin/admin_browse_spam_logs_spec.rb @@ -4,7 +4,7 @@ describe 'Admin browse spam logs' do let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) } before do - login_as :admin + gitlab_sign_in :admin end scenario 'Browse spam logs' do diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb index d880f3f07db..c734a2ef16d 100644 --- a/spec/features/admin/admin_browses_logs_spec.rb +++ b/spec/features/admin/admin_browses_logs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Admin browses logs' do before do - login_as :admin + gitlab_sign_in :admin end it 'shows available log files' do diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb index 999ce3611b5..e767081f3e5 100644 --- a/spec/features/admin/admin_builds_spec.rb +++ b/spec/features/admin/admin_builds_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Admin Builds' do before do - login_as :admin + gitlab_sign_in :admin end describe 'GET /admin/builds' do diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb index dd14ffdb2ce..952e5475213 100644 --- a/spec/features/admin/admin_cohorts_spec.rb +++ b/spec/features/admin/admin_cohorts_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Admin cohorts page', feature: true do before do - login_as :admin + gitlab_sign_in :admin end scenario 'See users count per month' do diff --git a/spec/features/admin/admin_conversational_development_index_spec.rb b/spec/features/admin/admin_conversational_development_index_spec.rb index 739ab907a29..b484677a6df 100644 --- a/spec/features/admin/admin_conversational_development_index_spec.rb +++ b/spec/features/admin/admin_conversational_development_index_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Admin Conversational Development Index' do before do - login_as :admin + gitlab_sign_in :admin end context 'when usage ping is disabled' do diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb index 5f5fa4e932a..81cddd03f80 100644 --- a/spec/features/admin/admin_deploy_keys_spec.rb +++ b/spec/features/admin/admin_deploy_keys_spec.rb @@ -5,7 +5,7 @@ RSpec.describe 'admin deploy keys', type: :feature do let!(:another_deploy_key) { create(:another_deploy_key, public: true) } before do - login_as(:admin) + gitlab_sign_in(:admin) end it 'show all public deploy keys' do diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index e8e080ce3e2..679bf63e0fd 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -8,7 +8,7 @@ feature 'Admin disables Git access protocol', feature: true do background do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - login_as(admin) + gitlab_sign_in(admin) end context 'with HTTP disabled' do diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb index 71be66303d2..5437da29979 100644 --- a/spec/features/admin/admin_disables_two_factor_spec.rb +++ b/spec/features/admin/admin_disables_two_factor_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Admin disables 2FA for a user', feature: true do scenario 'successfully', js: true do - login_as(:admin) + gitlab_sign_in(:admin) user = create(:user, :two_factor) edit_user(user) @@ -17,7 +17,7 @@ feature 'Admin disables 2FA for a user', feature: true do end scenario 'for a user without 2FA enabled' do - login_as(:admin) + gitlab_sign_in(:admin) user = create(:user) edit_user(user) diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index cf9d7bca255..8b0fafc5f07 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -6,7 +6,7 @@ feature 'Admin Groups', feature: true do let(:internal) { Gitlab::VisibilityLevel::INTERNAL } let(:user) { create :user } let!(:group) { create :group } - let!(:current_user) { login_as :admin } + let!(:current_user) { gitlab_sign_in :admin } before do stub_application_setting(default_group_visibility: internal) diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index 523afa2318f..75093aa4167 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -5,7 +5,7 @@ feature "Admin Health Check", feature: true do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - login_as :admin + gitlab_sign_in :admin end describe '#show' do diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb index 5b67f4de6ac..ec80c420c79 100644 --- a/spec/features/admin/admin_hook_logs_spec.rb +++ b/spec/features/admin/admin_hook_logs_spec.rb @@ -6,7 +6,7 @@ feature 'Admin::HookLogs', feature: true do let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') } before do - login_as :admin + gitlab_sign_in :admin end scenario 'show list of hook logs' do diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 80f7ec43c06..c07c21bd6a1 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Admin::Hooks', feature: true do before do @project = create(:project) - login_as :admin + gitlab_sign_in :admin @system_hook = create(:system_hook) end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index a9251db13e5..bb40918bd22 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -5,7 +5,7 @@ RSpec.describe 'admin issues labels' do let!(:feature_label) { Label.create(title: 'feature', template: true) } before do - login_as :admin + gitlab_sign_in :admin end describe 'list' do diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb index 0079125889b..ae41267e5fc 100644 --- a/spec/features/admin/admin_manage_applications_spec.rb +++ b/spec/features/admin/admin_manage_applications_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe 'admin manage applications', feature: true do before do - login_as :admin + gitlab_sign_in :admin end it do diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 9d205104ebe..ad8f1d496f2 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -6,7 +6,7 @@ describe "Admin::Projects", feature: true do let(:user) { create :user } let!(:project) { create(:project) } let!(:current_user) do - login_as :admin + gitlab_sign_in :admin end describe "GET /admin/projects" do diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb index e8ecb70306b..2bfe401521b 100644 --- a/spec/features/admin/admin_requests_profiles_spec.rb +++ b/spec/features/admin/admin_requests_profiles_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Admin::RequestsProfilesController', feature: true do before do FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR) - login_as(:admin) + gitlab_sign_in(:admin) end after do diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 5dcc7d35d82..5b3323fed13 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -5,7 +5,7 @@ describe "Admin Runners" do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - login_as :admin + gitlab_sign_in :admin end describe "Runners page" do @@ -134,7 +134,10 @@ describe "Admin Runners" do describe 'runners registration token' do let!(:token) { current_application_settings.runners_registration_token } - before { visit admin_runners_path } + + before do + visit admin_runners_path + end it 'has a registration token' do expect(page).to have_content("Registration token is #{token}") diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 5099441dce2..2d6565e6d3b 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -5,7 +5,7 @@ feature 'Admin updates settings', feature: true do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - login_as :admin + gitlab_sign_in :admin visit admin_application_settings_path end @@ -20,10 +20,15 @@ feature 'Admin updates settings', feature: true do uncheck 'Gravatar enabled' fill_in 'Home page URL', with: 'https://about.gitlab.com/' fill_in 'Help page text', with: 'Example text' + check 'Hide marketing-related entries from help' + fill_in 'Support page URL', with: 'http://example.com/help' click_button 'Save' expect(current_application_settings.gravatar_enabled).to be_falsey expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/" + expect(current_application_settings.help_page_text).to eq "Example text" + expect(current_application_settings.help_page_hide_commercial_content).to be_truthy + expect(current_application_settings.help_page_support_url).to eq "http://example.com/help" expect(page).to have_content "Application settings saved successfully" end diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb index 15482347886..4efc7f0eb48 100644 --- a/spec/features/admin/admin_system_info_spec.rb +++ b/spec/features/admin/admin_system_info_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Admin System Info' do before do - login_as :admin + gitlab_sign_in :admin end describe 'GET /admin/system_info' do diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index 0fb4baeb71c..231c094c91d 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -12,7 +12,9 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do find(".table.inactive-tokens") end - before { login_as(admin) } + before do + gitlab_sign_in(admin) + end describe "token creation" do it "allows creation of a token" do diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 301a47169a4..2d5f0987ea2 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -5,7 +5,7 @@ describe "Admin::Users", feature: true do create(:omniauth_user, provider: 'twitter', extern_uid: '123456') end - let!(:current_user) { login_as :admin } + let!(:current_user) { gitlab_sign_in :admin } describe "GET /admin/users" do before do @@ -124,7 +124,10 @@ describe "Admin::Users", feature: true do describe 'Impersonation' do let(:another_user) { create(:user) } - before { visit admin_user_path(another_user) } + + before do + visit admin_user_path(another_user) + end context 'before impersonating' do it 'shows impersonate button for other users' do @@ -149,7 +152,9 @@ describe "Admin::Users", feature: true do end context 'when impersonating' do - before { click_link 'Impersonate' } + before do + click_link 'Impersonate' + end it 'logs in as the user when impersonate is clicked' do expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username) diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index ab5c42365fe..91d70435db8 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -5,7 +5,7 @@ feature 'Admin uses repository checks', feature: true do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - login_as :admin + gitlab_sign_in :admin end scenario 'to trigger a single check' do diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index a61231ea254..c8ef4533b98 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -15,7 +15,7 @@ describe 'Issues Feed', feature: true do context 'when authenticated' do it 'renders atom feed' do - login_with user + gitlab_sign_in user visit namespace_project_issues_path(project.namespace, project, :atom) expect(response_headers['Content-Type']). diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb index 1cf7396bbac..74f5f70702a 100644 --- a/spec/features/auto_deploy_spec.rb +++ b/spec/features/auto_deploy_spec.rb @@ -7,7 +7,7 @@ describe 'Auto deploy' do before do create :kubernetes_service, project: project project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'when no deployment service is active' do diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index 2b8edac4f10..ba58af22841 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -14,7 +14,7 @@ describe 'Issue Boards add issue modal', :feature, :js do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_board_path(project.namespace, project, board) wait_for_requests diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index c80453b8227..87fc31d414c 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -12,7 +12,7 @@ describe 'Issue Boards', feature: true, js: true do project.team << [user, :master] project.team << [user2, :master] - login_as(user) + gitlab_sign_in(user) end context 'no lists' do @@ -247,13 +247,13 @@ describe 'Issue Boards', feature: true, js: true do end it 'issue moves from closed' do - drag(list_from_index: 3, list_to_index: 2) - - expect(find('.board:nth-child(3)')).to have_content(issue8.title) + drag(list_from_index: 2, list_to_index: 3) wait_for_board_cards(2, 8) - wait_for_board_cards(3, 3) - wait_for_board_cards(4, 0) + wait_for_board_cards(3, 1) + wait_for_board_cards(4, 2) + + expect(find('.board:nth-child(4)')).to have_content(issue8.title) end context 'issue card' do @@ -519,7 +519,7 @@ describe 'Issue Boards', feature: true, js: true do context 'signed out user' do before do - logout + gitlab_sign_out visit namespace_project_board_path(project.namespace, project, board) wait_for_requests end @@ -542,8 +542,8 @@ describe 'Issue Boards', feature: true, js: true do before do project.team << [user_guest, :guest] - logout - login_as(user_guest) + gitlab_sign_out + gitlab_sign_in(user_guest) visit namespace_project_board_path(project.namespace, project, board) wait_for_requests end diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index 1c289993e28..1e620061e5e 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -15,7 +15,7 @@ describe 'Issue Boards', :feature, :js do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'un-ordered issues' do diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index c2167ba12cd..ed3b38e6a7e 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -6,7 +6,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do before do create(:board, project: project) - login_as :admin + gitlab_sign_in :admin visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index b6de6143354..8899e1ef5e5 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -12,7 +12,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end it 'shows empty state when no results found' do diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 056224dc436..77cd87d6601 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -10,7 +10,7 @@ describe 'Issue Boards new issue', feature: true, js: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_board_path(project.namespace, project, board) wait_for_requests @@ -19,18 +19,18 @@ describe 'Issue Boards new issue', feature: true, js: true do end it 'displays new issue button' do - expect(first('.board')).to have_selector('.board-issue-count-holder .btn', count: 1) + expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1) end it 'does not display new issue button in closed list' do page.within('.board:nth-child(3)') do - expect(page).not_to have_selector('.board-issue-count-holder .btn') + expect(page).not_to have_selector('.issue-count-badge-add-button') end end it 'shows form when clicking button' do page.within(first('.board')) do - find('.board-issue-count-holder .btn').click + find('.issue-count-badge-add-button').click expect(page).to have_selector('.board-new-issue-form') end @@ -38,7 +38,7 @@ describe 'Issue Boards new issue', feature: true, js: true do it 'hides form when clicking cancel' do page.within(first('.board')) do - find('.board-issue-count-holder .btn').click + find('.issue-count-badge-add-button').click expect(page).to have_selector('.board-new-issue-form') @@ -50,7 +50,7 @@ describe 'Issue Boards new issue', feature: true, js: true do it 'creates new issue' do page.within(first('.board')) do - find('.board-issue-count-holder .btn').click + find('.issue-count-badge-add-button').click end page.within(first('.board-new-issue-form')) do @@ -60,14 +60,14 @@ describe 'Issue Boards new issue', feature: true, js: true do wait_for_requests - page.within(first('.board .board-issue-count')) do + page.within(first('.board .issue-count-badge-count')) do expect(page).to have_content('1') end end it 'shows sidebar when creating new issue' do page.within(first('.board')) do - find('.board-issue-count-holder .btn').click + find('.issue-count-badge-add-button').click end page.within(first('.board-new-issue-form')) do @@ -88,7 +88,7 @@ describe 'Issue Boards new issue', feature: true, js: true do end it 'does not display new issue button' do - expect(page).to have_selector('.board-issue-count-holder .btn', count: 0) + expect(page).to have_selector('.issue-count-badge-add-button', count: 0) end end end diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 235e4899707..301c243febd 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -20,7 +20,7 @@ describe 'Issue Boards', feature: true, js: true do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_board_path(project.namespace, project, board) wait_for_requests diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb index 4cd05010a93..d57ae6a71e7 100644 --- a/spec/features/boards/sub_group_project_spec.rb +++ b/spec/features/boards/sub_group_project_spec.rb @@ -13,7 +13,7 @@ describe 'Sub-group project issue boards', :feature, :js do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_board_path(project.namespace, project, board) wait_for_requests diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 1b6d8439f92..b2e72fc7dee 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -68,7 +68,7 @@ feature 'Contributions Calendar', :feature, :js do end before do - login_as user + gitlab_sign_in user end describe 'calendar day selection' do diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb index 3ebc432206a..de16ee3e567 100644 --- a/spec/features/ci_lint_spec.rb +++ b/spec/features/ci_lint_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'CI Lint', js: true do before do - login_as :user + gitlab_sign_in :user end describe 'YAML parsing' do diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 2772f05982a..ab2d85371bf 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -7,7 +7,7 @@ describe 'Commits' do describe 'CI' do before do - login_as :user + gitlab_sign_in :user stub_ci_pipeline_to_return_yaml_file end @@ -191,7 +191,7 @@ describe 'Commits' do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit namespace_project_commits_path(project.namespace, project, branch_name) end diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index fa7adbe71ea..80d16539d5a 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -9,7 +9,7 @@ describe "Container Registry" do end before do - login_as(user) + gitlab_sign_in(user) project.add_developer(user) stub_container_registry_config(enabled: true) stub_container_registry_tags(repository: :any, tags: []) diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index 740f60c05cc..005c88f6bab 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -6,7 +6,7 @@ describe 'Copy as GFM', feature: true, js: true do include ActionView::Helpers::JavaScriptHelper before do - login_as :admin + gitlab_sign_in :admin end describe 'Copying rendered GFM' do diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index b416bbd3c79..5a7ea975455 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -14,7 +14,7 @@ feature 'Cycle Analytics', feature: true, js: true do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_cycle_analytics_path(project.namespace, project) wait_for_requests @@ -38,7 +38,7 @@ feature 'Cycle Analytics', feature: true, js: true do create_cycle deploy_master - login_as(user) + gitlab_sign_in(user) visit namespace_project_cycle_analytics_path(project.namespace, project) end @@ -70,7 +70,7 @@ feature 'Cycle Analytics', feature: true, js: true do user.update_attribute(:preferred_language, 'es') project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_cycle_analytics_path(project.namespace, project) wait_for_requests end @@ -93,7 +93,7 @@ feature 'Cycle Analytics', feature: true, js: true do create_cycle deploy_master - login_as(guest) + gitlab_sign_in(guest) visit namespace_project_cycle_analytics_path(project.namespace, project) wait_for_requests end diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb index ae750be4d4a..f7ddded10c1 100644 --- a/spec/features/dashboard/active_tab_spec.rb +++ b/spec/features/dashboard/active_tab_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Active Tab', js: true, feature: true do before do - login_as :user + gitlab_sign_in :user end shared_examples 'page has active tab' do |title| diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index 0764044260e..1e9cabe7850 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Activity', feature: true do before do - login_as(create :user) + gitlab_sign_in(create :user) visit activity_dashboard_path end diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb index f33bcbb5318..a5ba3e7e3cf 100644 --- a/spec/features/dashboard/archived_projects_spec.rb +++ b/spec/features/dashboard/archived_projects_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Dashboard Archived Project', feature: true do project.team << [user, :master] archived_project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit dashboard_projects_path end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 1793e323588..6931d0a840e 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -4,7 +4,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:created_date) { Date.yesterday.to_time } - let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P') } + let(:expected_format) { created_date.in_time_zone.strftime('%b %-d, %Y %l:%M%P') } context 'on the activity tab' do before do @@ -13,7 +13,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do Event.create( project: project, author_id: user.id, action: Event::JOINED, updated_at: created_date, created_at: created_date) - login_as user + gitlab_sign_in user visit user_path(user) wait_for_requests() @@ -30,7 +30,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do project.team << [user, :master] create(:snippet, author: user, updated_at: created_date, created_at: created_date) - login_as user + gitlab_sign_in user visit user_snippets_path(user) wait_for_requests() diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb index 8e20fdec8ad..2f7245950ec 100644 --- a/spec/features/dashboard/group_spec.rb +++ b/spec/features/dashboard/group_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Group', feature: true do before do - login_as(:user) + gitlab_sign_in(:user) end it 'creates new group', js: true do diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index 7eb254f8451..e520027bc38 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -10,7 +10,7 @@ describe 'Dashboard Groups page', js: true, feature: true do group.add_owner(user) nested_group.add_owner(user) - login_as(user) + gitlab_sign_in(user) visit dashboard_groups_path expect(page).to have_content(group.full_name) @@ -23,7 +23,7 @@ describe 'Dashboard Groups page', js: true, feature: true do group.add_owner(user) nested_group.add_owner(user) - login_as(user) + gitlab_sign_in(user) visit dashboard_groups_path end @@ -58,7 +58,7 @@ describe 'Dashboard Groups page', js: true, feature: true do group.add_owner(user) subgroup.add_owner(user) - login_as(user) + gitlab_sign_in(user) visit dashboard_groups_path end @@ -98,7 +98,7 @@ describe 'Dashboard Groups page', js: true, feature: true do allow(Kaminari.config).to receive(:default_per_page).and_return(1) - login_as(user) + gitlab_sign_in(user) visit dashboard_groups_path end diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb index 2803f7ec62b..25b0f40c9cd 100644 --- a/spec/features/dashboard/help_spec.rb +++ b/spec/features/dashboard/help_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Help', feature: true do before do - login_as(:user) + gitlab_sign_in(:user) end it 'renders correctly markdown' do diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index 354267dbee7..8a8a20fd5b1 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -9,7 +9,7 @@ describe 'Navigation bar counter', feature: true, caching: true do before do issue.assignees = [user] merge_request.update(assignee: user) - login_as(user) + gitlab_sign_in(user) end it 'reflects dashboard issues count' do diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 2cea6b1563e..6f21cfd322d 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Dashboard Issues', feature: true do before do [project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] } - login_as(current_user) + gitlab_sign_in(current_user) visit issues_dashboard_path(assignee_id: current_user.id) end diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb index 4cff12de854..88bbb9e75b9 100644 --- a/spec/features/dashboard/label_filter_spec.rb +++ b/spec/features/dashboard/label_filter_spec.rb @@ -11,7 +11,7 @@ describe 'Dashboard > label filter', feature: true, js: true do project.labels << label project2.labels << label2 - login_as(user) + gitlab_sign_in(user) visit issues_dashboard_path end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 9cebe52c444..69d5500848e 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -8,11 +8,13 @@ describe 'Dashboard Merge Requests' do before do [project, project_with_merge_requests_disabled].each { |project| project.team << [current_user, :master] } - login_as(current_user) + gitlab_sign_in(current_user) end describe 'new merge request dropdown' do - before { visit merge_requests_dashboard_path } + before do + visit merge_requests_dashboard_path + end it 'shows projects only with merge requests feature enabled', js: true do find('.new-project-item-select-button').trigger('click') diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb index b5b92c36895..295262980a6 100644 --- a/spec/features/dashboard/milestone_filter_spec.rb +++ b/spec/features/dashboard/milestone_filter_spec.rb @@ -9,7 +9,7 @@ describe 'Dashboard > milestone filter', :feature, :js do let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 } before do - login_as(user) + gitlab_sign_in(user) visit issues_dashboard_path(author_id: user.id) end diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb index 0c7b992c500..cc4193b180f 100644 --- a/spec/features/dashboard/milestone_tabs_spec.rb +++ b/spec/features/dashboard/milestone_tabs_spec.rb @@ -15,7 +15,7 @@ describe 'Dashboard milestone tabs', :js, :feature do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit dashboard_milestone_path(milestone.safe_title, title: milestone.title) end @@ -23,7 +23,7 @@ describe 'Dashboard milestone tabs', :js, :feature do it 'loads merge requests async' do click_link 'Merge Requests' - expect(page).to have_selector('.merge_requests-sortable-list') + expect(page).to have_selector('.milestone-merge_requests-list') end it 'loads participants async' do diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index cdf919af9b5..0ba87d921d0 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -17,19 +17,25 @@ feature 'Project member activity', feature: true, js: true do subject { page.find(".event-title").text } context 'when a user joins the project' do - before { visit_activities_and_wait_with_event(Event::JOINED) } + before do + visit_activities_and_wait_with_event(Event::JOINED) + end it { is_expected.to eq("#{user.name} joined project") } end context 'when a user leaves the project' do - before { visit_activities_and_wait_with_event(Event::LEFT) } + before do + visit_activities_and_wait_with_event(Event::LEFT) + end it { is_expected.to eq("#{user.name} left project") } end context 'when a users membership expires for the project' do - before { visit_activities_and_wait_with_event(Event::EXPIRED) } + before do + visit_activities_and_wait_with_event(Event::EXPIRED) + end it "presents the correct message" do message = "#{user.name} removed due to membership expiration from project" diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 3568954a548..2a8185ca669 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Dashboard Projects', feature: true do before do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) end it 'shows the project the user in a member of in the list' do diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 349b948eaee..525b0e1b210 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Dashboard shortcuts', :feature, :js do context 'logged in' do before do - login_as :user + gitlab_sign_in :user visit root_dashboard_path end diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb index c6ba118220a..0c069ae5cf0 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -6,7 +6,7 @@ describe 'Dashboard snippets', feature: true do let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } before do allow(Snippet).to receive(:default_per_page).and_return(1) - login_as(project.owner) + gitlab_sign_in(project.owner) visit dashboard_snippets_path end @@ -25,7 +25,7 @@ describe 'Dashboard snippets', feature: true do end before do - login_as(user) + gitlab_sign_in(user) visit dashboard_snippets_path end diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index 34d6257f5fd..e9f34760143 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -9,7 +9,7 @@ describe 'Dashboard > User filters projects', :feature do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end describe 'filtering personal projects' do diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb index 1c53f6dff06..c4dbaad2895 100644 --- a/spec/features/dashboard_issues_spec.rb +++ b/spec/features/dashboard_issues_spec.rb @@ -8,7 +8,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do context 'filtering by milestone' do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) create(:issue, project: project, author: user, assignees: [user]) create(:issue, project: project, author: user, assignees: [user], milestone: milestone) diff --git a/spec/features/dashboard_milestones_spec.rb b/spec/features/dashboard_milestones_spec.rb index f32fddbc9fa..b308a2297b9 100644 --- a/spec/features/dashboard_milestones_spec.rb +++ b/spec/features/dashboard_milestones_spec.rb @@ -17,7 +17,7 @@ feature 'Dashboard > Milestones', feature: true do let!(:milestone) { create(:milestone, project: project) } before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit dashboard_milestones_path end diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb index 96e0b78f6b9..96128061e4d 100644 --- a/spec/features/discussion_comments/commit_spec.rb +++ b/spec/features/discussion_comments/commit_spec.rb @@ -9,7 +9,7 @@ describe 'Discussion Comments Merge Request', :feature, :js do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_commit_path(project.namespace, project, sample_commit.id) end diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb index ccc9efccd18..d7c1cd12fb5 100644 --- a/spec/features/discussion_comments/issue_spec.rb +++ b/spec/features/discussion_comments/issue_spec.rb @@ -7,7 +7,7 @@ describe 'Discussion Comments Issue', :feature, :js do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb index f99ebeb9cd9..31fb9c72d25 100644 --- a/spec/features/discussion_comments/merge_request_spec.rb +++ b/spec/features/discussion_comments/merge_request_spec.rb @@ -7,7 +7,7 @@ describe 'Discussion Comments Merge Request', :feature, :js do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb index 19a306511b2..998d633c83d 100644 --- a/spec/features/discussion_comments/snippets_spec.rb +++ b/spec/features/discussion_comments/snippets_spec.rb @@ -7,7 +7,7 @@ describe 'Discussion Comments Issue', :feature, :js do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_snippet_path(project.namespace, project, snippet) end diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index c4d5077e5e1..ea749528c11 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -10,7 +10,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes) allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes) - login_as :admin + gitlab_sign_in :admin # Ensure that undiffable.md is in .gitattributes project.repository.copy_gitattributes(branch) @@ -140,7 +140,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do end context 'reloading the page' do - before { refresh } + before do + refresh + end it 'collapses the large diff by default' do expect(large_diff).not_to have_selector('.code') @@ -262,7 +264,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do # Wait for elements to appear to ensure full page reload expect(page).to have_content('This diff was suppressed by a .gitattributes entry') - expect(page).to have_content('This diff could not be displayed because it is too large.') + expect(page).to have_content('This source diff could not be displayed because it is too large.') expect(page).to have_content('too_large_image.jpg') find('.note-textarea') diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index d4284ed099b..6be5dee0c3c 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -10,7 +10,7 @@ describe 'Explore Groups page', :js, :feature do before do group.add_owner(user) - login_as(user) + gitlab_sign_in(user) visit explore_groups_path end diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb index 15a6354211b..2d7e703688f 100644 --- a/spec/features/explore/new_menu_spec.rb +++ b/spec/features/explore/new_menu_spec.rb @@ -16,7 +16,7 @@ feature 'Top Plus Menu', feature: true, js: true do context 'used by full user' do before do - login_as(user) + gitlab_sign_in(user) end scenario 'click on New project shows new project page' do @@ -103,7 +103,7 @@ feature 'Top Plus Menu', feature: true, js: true do context 'used by guest user' do before do - login_as(guest_user) + gitlab_sign_in(guest_user) end scenario 'click on New issue shows new issue page' do diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 55092412340..7758f00da7d 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -10,7 +10,7 @@ describe "GitLab Flavored Markdown", feature: true do end before do - login_as(:user) + gitlab_sign_in(:user) project.add_developer(@user) end diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index 4b22b07494d..54ebfe6cf77 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -6,7 +6,7 @@ feature 'Global search', feature: true do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) end describe 'I search through the issues and I see pagination' do diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb index 81f9c103e95..9f66a3d8c72 100644 --- a/spec/features/groups/activity_spec.rb +++ b/spec/features/groups/activity_spec.rb @@ -7,7 +7,7 @@ feature 'Group activity page', feature: true do context 'when signed in' do before do user = create(:group_member, :developer, user: create(:user), group: group ).user - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb index fef8e41bffe..b1c7151dfa8 100644 --- a/spec/features/groups/empty_states_spec.rb +++ b/spec/features/groups/empty_states_spec.rb @@ -5,7 +5,7 @@ feature 'Groups Merge Requests Empty States' do let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user } before do - login_as(user) + gitlab_sign_in(user) end context 'group has a project' do diff --git a/spec/features/groups/group_name_toggle_spec.rb b/spec/features/groups/group_name_toggle_spec.rb index dfc3c84f29a..f450626c370 100644 --- a/spec/features/groups/group_name_toggle_spec.rb +++ b/spec/features/groups/group_name_toggle_spec.rb @@ -9,7 +9,7 @@ feature 'Group name toggle', feature: true, js: true do SMALL_SCREEN = 300 before do - login_as :user + gitlab_sign_in :user end it 'is not present if enough horizontal space' do diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index cc25db4ad60..5ad777248ec 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -6,7 +6,7 @@ feature 'Edit group settings', feature: true do background do group.add_owner(user) - login_as(user) + gitlab_sign_in(user) end describe 'when the group path is changed' do @@ -52,9 +52,14 @@ feature 'Edit group settings', feature: true do given!(:project) { create(:project, group: group, path: 'project') } given(:old_project_full_path) { "/#{group.path}/#{project.path}" } given(:new_project_full_path) { "/#{new_group_path}/#{project.path}" } - - before(:context) { TestEnv.clean_test_path } - after(:example) { TestEnv.clean_test_path } + + before(:context) do + TestEnv.clean_test_path + end + + after(:example) do + TestEnv.clean_test_path + end scenario 'the project is accessible via the new path' do update_path(new_group_path) diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb index 69281cecb7b..b33040ef843 100644 --- a/spec/features/groups/labels/edit_spec.rb +++ b/spec/features/groups/labels/edit_spec.rb @@ -7,7 +7,7 @@ feature 'Edit group label', feature: true do background do group.add_owner(user) - login_as(user) + gitlab_sign_in(user) visit edit_group_label_path(group, label) end diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb index be60b0489c7..5af94e4069b 100644 --- a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb +++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb @@ -6,7 +6,7 @@ feature 'Groups > Members > Last owner cannot leave group', feature: true do background do group.add_owner(owner) - login_as(owner) + gitlab_sign_in(owner) visit group_path(group) end diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb index f654fa16a06..5d00ed30c83 100644 --- a/spec/features/groups/members/list_spec.rb +++ b/spec/features/groups/members/list_spec.rb @@ -9,7 +9,7 @@ feature 'Groups members list', feature: true do let(:nested_group) { create(:group, parent: group) } background do - login_as(user1) + gitlab_sign_in(user1) end scenario 'show members from current group and parent', :nested_groups do diff --git a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb index 37c433cc09a..135bb3572bc 100644 --- a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb @@ -6,7 +6,7 @@ feature 'Groups > Members > Member cannot request access to his project', featur background do group.add_developer(member) - login_as(member) + gitlab_sign_in(member) visit group_path(group) end diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb index ac4d94658ae..40f3b166e74 100644 --- a/spec/features/groups/members/member_leaves_group_spec.rb +++ b/spec/features/groups/members/member_leaves_group_spec.rb @@ -8,7 +8,7 @@ feature 'Groups > Members > Member leaves group', feature: true do background do group.add_owner(owner) group.add_developer(user) - login_as(user) + gitlab_sign_in(user) visit group_path(group) end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index dbe150823ba..4e4cf12e8af 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -8,7 +8,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do background do group.request_access(user) group.add_owner(owner) - login_as(owner) + gitlab_sign_in(owner) end scenario 'owner can see access requests' do diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sorting_spec.rb index 902d3f789ff..719fa0b40b8 100644 --- a/spec/features/groups/members/sorting_spec.rb +++ b/spec/features/groups/members/sorting_spec.rb @@ -9,7 +9,7 @@ feature 'Groups > Members > Sorting', feature: true do create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago) create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago) - login_as(owner) + gitlab_sign_in(owner) end scenario 'sorts alphabetically by default' do diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb index e4b5ea91bd3..3813308c237 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -8,7 +8,7 @@ feature 'Groups > Members > User requests access', feature: true do background do group.add_owner(owner) - login_as(user) + gitlab_sign_in(user) visit group_path(group) end diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index daa2c6afd63..330310eae6b 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -8,7 +8,7 @@ feature 'Group milestones', :feature, :js do before do Timecop.freeze - login_as(user) + gitlab_sign_in(user) end after do diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index d3c49c37374..76575f61528 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -7,7 +7,7 @@ feature 'Group show page', feature: true do context 'when signed in' do before do user = create(:group_member, :developer, user: create(:user), group: group ).user - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 24ea7aba0cc..ecacca00a61 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Group', feature: true do before do - login_as(:admin) + gitlab_sign_in(:admin) end matcher :have_namespace_error_message do @@ -12,7 +12,9 @@ feature 'Group', feature: true do end describe 'create a group' do - before { visit new_group_path } + before do + visit new_group_path + end describe 'with space in group path' do it 'renders new group form with validation errors' do @@ -106,8 +108,8 @@ feature 'Group', feature: true do before do group.add_owner(user) - logout - login_as(user) + gitlab_sign_out + gitlab_sign_in(user) visit subgroups_group_path(group) click_link 'New Subgroup' @@ -126,8 +128,8 @@ feature 'Group', feature: true do it 'checks permissions to avoid exposing groups by parent_id' do group = create(:group, :private, path: 'secret-group') - logout - login_as(:user) + gitlab_sign_out + gitlab_sign_in(:user) visit new_group_path(parent_id: group.id) expect(page).not_to have_content('secret-group') @@ -138,7 +140,9 @@ feature 'Group', feature: true do let(:path) { edit_group_path(group) } let(:new_name) { 'new-name' } - before { visit path } + before do + visit path + end it 'saves new settings' do fill_in 'group_name', with: new_name diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 31014f5cad2..b01ee1cf491 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -37,10 +37,10 @@ describe 'Help Pages', feature: true do context 'in a production environment with version check enabled', :js do before do allow(Rails.env).to receive(:production?) { true } - allow(current_application_settings).to receive(:version_check_enabled) { true } + allow_any_instance_of(ApplicationSetting).to receive(:version_check_enabled) { true } allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' } - login_as :user + gitlab_sign_in :user visit help_path end @@ -53,4 +53,27 @@ describe 'Help Pages', feature: true do expect(find('.js-version-status-badge', visible: false)).not_to be_visible end end + + describe 'when help page is customized' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:help_page_hide_commercial_content?) { true } + allow_any_instance_of(ApplicationSetting).to receive(:help_page_text) { "My Custom Text" } + allow_any_instance_of(ApplicationSetting).to receive(:help_page_support_url) { "http://example.com/help" } + + gitlab_sign_in(:user) + visit help_path + end + + it 'should display custom help page text' do + expect(page).to have_text "My Custom Text" + end + + it 'should hide marketing content when enabled' do + expect(page).not_to have_link "Get a support subscription" + end + + it 'should use a custom support url' do + expect(page).to have_link "See our website for getting help", href: "http://example.com/help" + end + end end diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb index bfe43bff10f..56c9b10e757 100644 --- a/spec/features/issuables/default_sort_order_spec.rb +++ b/spec/features/issuables/default_sort_order_spec.rb @@ -153,7 +153,9 @@ describe 'Projects > Issuables > Default sort order', feature: true do context 'when the sort in the URL is id_desc' do let(:issuable_type) { :issue } - before { visit_issues(project, sort: 'id_desc') } + before do + visit_issues(project, sort: 'id_desc') + end it 'shows the sort order as last created' do expect(find('.issues-other-filters')).to have_content('Last created') @@ -165,7 +167,9 @@ describe 'Projects > Issuables > Default sort order', feature: true do context 'when the sort in the URL is id_asc' do let(:issuable_type) { :issue } - before { visit_issues(project, sort: 'id_asc') } + before do + visit_issues(project, sort: 'id_asc') + end it 'shows the sort order as oldest created' do expect(find('.issues-other-filters')).to have_content('Oldest created') diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index 414838fa22e..f3a5a8463d1 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -8,7 +8,7 @@ describe 'issuable list', feature: true do before do project.add_user(user, :developer) - login_as(user) + gitlab_sign_in(user) issuable_types.each { |type| create_issuables(type) } end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 81ae54c7a10..6698e2c79a1 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -12,7 +12,7 @@ describe 'Awards Emoji', feature: true do context 'authorized user' do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end describe 'visiting an issue with a legacy award emoji that is not valid anymore' do @@ -81,13 +81,13 @@ describe 'Awards Emoji', feature: true do end end - context 'execute /award slash command' do + context 'execute /award quick action' do it 'toggles the emoji award on noteable', js: true do - execute_slash_command('/award :100:') + execute_quick_action('/award :100:') expect(find(noteable_award_counter)).to have_text("1") - execute_slash_command('/award :100:') + execute_quick_action('/award :100:') expect(page).not_to have_selector(noteable_award_counter) end @@ -105,7 +105,7 @@ describe 'Awards Emoji', feature: true do end end - def execute_slash_command(cmd) + def execute_quick_action(cmd) within('.js-main-target-form') do fill_in 'note[note]', with: cmd click_button 'Comment' diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb index fcf22dd5033..a1c97caea20 100644 --- a/spec/features/issues/award_spec.rb +++ b/spec/features/issues/award_spec.rb @@ -7,7 +7,7 @@ feature 'Issue awards', js: true, feature: true do describe 'logged in' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) wait_for_requests end diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index 95b4930cd32..2eb04df3cb3 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -13,7 +13,7 @@ feature 'Issues > Labels bulk assignment', feature: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'can bulk assign' do @@ -331,7 +331,7 @@ feature 'Issues > Labels bulk assignment', feature: true do context 'as a guest' do before do - login_as user + gitlab_sign_in user visit namespace_project_issues_path(project.namespace, project) end diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb index 1d7d8d291b2..aa538803dd8 100644 --- a/spec/features/issues/create_branch_merge_request_spec.rb +++ b/spec/features/issues/create_branch_merge_request_spec.rb @@ -8,7 +8,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js: context 'for team members' do before do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) end it 'allows creating a merge request from the issue page' do diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 24e2419b5ce..5f631043e15 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -9,7 +9,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu describe 'as a user with access to the project' do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -82,7 +82,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu describe 'as a reporter' do before do project.team << [user, :reporter] - login_as user + gitlab_sign_in user visit new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid) end diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index 3a5a79e03f4..9e9e214060f 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -9,7 +9,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe describe 'As a user with access to the project' do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -66,7 +66,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe describe 'as a reporter' do before do project.team << [user, :reporter] - login_as user + gitlab_sign_in user visit new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 44353d880c2..96f6739af2d 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -23,7 +23,7 @@ describe 'Dropdown assignee', :feature, :js do project.team << [user, :master] project.team << [user_john, :master] project.team << [user_jacob, :master] - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 6b707c4be4a..5ee824c662a 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -31,7 +31,7 @@ describe 'Dropdown author', js: true, feature: true do project.team << [user, :master] project.team << [user_john, :master] project.team << [user_jacob, :master] - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index b9a37cfcc22..a05e4394ffd 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -14,7 +14,7 @@ describe 'Dropdown hint', :js, :feature do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index abe5d61e38c..aec9d7ceb5d 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -34,7 +34,7 @@ describe 'Dropdown label', js: true, feature: true do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 448259057b0..b21f41946b7 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -30,7 +30,7 @@ describe 'Dropdown milestone', :feature, :js do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 3ea95aed0a6..806c732b935 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -9,7 +9,7 @@ describe 'Search bar', js: true, feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index ff32b0c7d11..22488f34813 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -25,7 +25,7 @@ describe 'Visual tokens', js: true, feature: true do before do project.add_user(user, :master) project.add_user(user_rock, :master) - login_as(user) + gitlab_sign_in(user) create(:issue, project: project) visit namespace_project_issues_path(project.namespace, project) diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 96d37e33f3d..b369ef1ff79 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -16,7 +16,7 @@ describe 'New/edit issue', :feature, :js do before do project.team << [user, :master] project.team << [user2, :master] - login_as(user) + gitlab_sign_in(user) end context 'new issue' do @@ -210,6 +210,13 @@ describe 'New/edit issue', :feature, :js do expect(find('.js-assignee-search')).to have_content(user2.name) end + + it 'description has autocomplete' do + find('#issue_description').native.send_keys('') + fill_in 'issue_description', with: '@' + + expect(page).to have_selector('.atwho-view') + end end context 'edit issue' do @@ -258,6 +265,13 @@ describe 'New/edit issue', :feature, :js do end end end + + it 'description has autocomplete' do + find('#issue_description').native.send_keys('') + fill_in 'issue_description', with: '@' + + expect(page).to have_selector('.atwho-view') + end end describe 'sub-group project' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 350473437a8..e61eb5233d0 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -8,7 +8,7 @@ feature 'GFM autocomplete', feature: true, js: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) wait_for_requests @@ -208,7 +208,7 @@ feature 'GFM autocomplete', feature: true, js: true do expect(page).not_to have_selector('.atwho-view') end - it 'triggers autocomplete after selecting a slash command' do + it 'triggers autocomplete after selecting a quick action' do note = find('#note_note') page.within '.timeline-content-form' do note.native.send_keys('') diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 96c24750250..163bc4bb32f 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -10,7 +10,7 @@ feature 'Issue Sidebar', feature: true do let!(:label) { create(:label, project: project, title: 'bug') } before do - login_as(user) + gitlab_sign_in(user) end context 'assignee', js: true do diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb index c8c9c50396b..66d823ec9d0 100644 --- a/spec/features/issues/markdown_toolbar_spec.rb +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -6,7 +6,7 @@ feature 'Issue markdown toolbar', feature: true, js: true do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index e75bf059218..21a7637fe7f 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -9,7 +9,7 @@ feature 'issue move to another project' do create(:issue, description: text, project: old_project, author: user) end - background { login_as(user) } + background { gitlab_sign_in(user) } context 'user does not have permission to move issue' do background do diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb index 2c0a6ffd3cb..bd31e44ef33 100644 --- a/spec/features/issues/note_polling_spec.rb +++ b/spec/features/issues/note_polling_spec.rb @@ -27,7 +27,7 @@ feature 'Issue notes polling', :feature, :js do let!(:existing_note) { create(:note, noteable: issue, project: project, author: user, note: note_text) } before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end @@ -93,7 +93,7 @@ feature 'Issue notes polling', :feature, :js do let!(:existing_note) { create(:note, noteable: issue, project: project, author: user1, note: note_text) } before do - login_as(user2) + gitlab_sign_in(user2) visit namespace_project_issue_path(project.namespace, project, issue) end @@ -114,7 +114,7 @@ feature 'Issue notes polling', :feature, :js do let!(:system_note) { create(:system_note, noteable: issue, project: project, author: user, note: note_text) } before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb index 15c817cabac..f648295416f 100644 --- a/spec/features/issues/notes_on_issues_spec.rb +++ b/spec/features/issues/notes_on_issues_spec.rb @@ -9,7 +9,7 @@ describe 'Create notes on issues', :js, :feature do before do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) fill_in 'note[note]', with: note_text diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 6001476d0ca..57c783790b5 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -18,7 +18,7 @@ describe 'New issue', feature: true, js: true do ) project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'when identified as a spam' do diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 3fde85b0a5c..a1c00dd64f6 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -7,7 +7,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index 8595847d313..dc981406e4e 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -7,7 +7,7 @@ feature 'Multiple issue updating from issues#index', feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'status', js: true do diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index d14c319707c..168cdd08137 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -1,9 +1,9 @@ require 'rails_helper' -feature 'Issues > User uses slash commands', feature: true, js: true do - include SlashCommandsHelpers +feature 'Issues > User uses quick actions', feature: true, js: true do + include QuickActionsHelpers - it_behaves_like 'issuable record that supports slash commands in its description and notes', :issue do + it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do let(:issuable) { create(:issue, project: project) } end @@ -13,7 +13,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end @@ -41,8 +41,8 @@ feature 'Issues > User uses slash commands', feature: true, js: true do let(:guest) { create(:user) } before do project.team << [guest, :guest] - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit namespace_project_issue_path(project.namespace, project, issue) end @@ -81,8 +81,8 @@ feature 'Issues > User uses slash commands', feature: true, js: true do let(:guest) { create(:user) } before do project.team << [guest, :guest] - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index eecc565d2bd..ea637018617 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -8,7 +8,7 @@ describe 'Issues', feature: true do let(:project) { create(:empty_project, :public) } before do - login_as :user + gitlab_sign_in :user user2 = create(:user) project.team << [[@user, user2], :developer] @@ -246,7 +246,10 @@ describe 'Issues', feature: true do context 'with a filter on labels' do let(:label) { create(:label, project: project) } - before { create(:label_link, label: label, target: foo) } + + before do + create(:label_link, label: label, target: foo) + end it 'sorts by least recently due date by excluding nil due dates' do bar.update(due_date: nil) @@ -480,8 +483,8 @@ describe 'Issues', feature: true do end it 'shows assignee text', js: true do - logout - login_with guest + gitlab_sign_out + gitlab_sign_in guest visit namespace_project_issue_path(project.namespace, project, issue) expect(page).to have_content issue.assignees.first.name @@ -543,8 +546,8 @@ describe 'Issues', feature: true do end it 'shows milestone text', js: true do - logout - login_with guest + gitlab_sign_out + gitlab_sign_in guest visit namespace_project_issue_path(project.namespace, project, issue) expect(page).to have_content milestone.title @@ -557,7 +560,7 @@ describe 'Issues', feature: true do context 'by unauthenticated user' do before do - logout + gitlab_sign_out end it 'redirects to signin then back to new issue after signin' do @@ -567,7 +570,7 @@ describe 'Issues', feature: true do expect(current_path).to eq new_user_session_path - login_as :user + gitlab_sign_in :user expect(current_path).to eq new_namespace_project_issue_path(project.namespace, project) end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index c82e8c03343..b43e6a06a07 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -36,7 +36,7 @@ feature 'Login', feature: true do it 'prevents the user from logging in' do user = create(:user, :blocked) - login_with(user) + gitlab_sign_in(user) expect(page).to have_content('Your account has been blocked.') end @@ -44,19 +44,19 @@ feature 'Login', feature: true do it 'does not update Devise trackable attributes', :redis do user = create(:user, :blocked) - expect { login_with(user) }.not_to change { user.reload.sign_in_count } + expect { gitlab_sign_in(user) }.not_to change { user.reload.sign_in_count } end end describe 'with the ghost user' do it 'disallows login' do - login_with(User.ghost) + gitlab_sign_in(User.ghost) expect(page).to have_content('Invalid Login or password.') end it 'does not update Devise trackable attributes', :redis do - expect { login_with(User.ghost) }.not_to change { User.ghost.reload.sign_in_count } + expect { gitlab_sign_in(User.ghost) }.not_to change { User.ghost.reload.sign_in_count } end end @@ -70,7 +70,7 @@ feature 'Login', feature: true do let(:user) { create(:user, :two_factor) } before do - login_with(user, remember: true) + gitlab_sign_in(user, remember: true) expect(page).to have_content('Two-Factor Authentication') end @@ -167,7 +167,7 @@ feature 'Login', feature: true do 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]) user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') - login_via('saml', user, 'my-uid') + gitlab_sign_in_via('saml', user, 'my-uid') expect(page).to have_content('Two-Factor Authentication') enter_code(user.current_otp) @@ -180,19 +180,19 @@ feature 'Login', feature: true do let(:user) { create(:user) } it 'allows basic login' do - login_with(user) + gitlab_sign_in(user) expect(current_path).to eq root_path end it 'does not show a "You are already signed in." error message' do - login_with(user) + gitlab_sign_in(user) expect(page).not_to have_content('You are already signed in.') end it 'blocks invalid login' do user = create(:user, password: 'not-the-default') - login_with(user) + gitlab_sign_in(user) expect(page).to have_content('Invalid Login or password.') end end @@ -202,12 +202,14 @@ feature 'Login', feature: true do # TODO: otp_grace_period_started_at context 'global setting' do - before(:each) { stub_application_setting(require_two_factor_authentication: true) } + before do + stub_application_setting(require_two_factor_authentication: true) + end context 'with grace period defined' do - before(:each) do + before do stub_application_setting(two_factor_grace_period: 48) - login_with(user) + gitlab_sign_in(user) end context 'within the grace period' do @@ -242,9 +244,9 @@ feature 'Login', feature: true do end context 'without grace period defined' do - before(:each) do + before do stub_application_setting(two_factor_grace_period: 0) - login_with(user) + gitlab_sign_in(user) end it 'redirects to two-factor configuration page' do @@ -265,9 +267,9 @@ feature 'Login', feature: true do end context 'with grace period defined' do - before(:each) do + before do stub_application_setting(two_factor_grace_period: 48) - login_with(user) + gitlab_sign_in(user) end context 'within the grace period' do @@ -306,9 +308,9 @@ feature 'Login', feature: true do end context 'without grace period defined' do - before(:each) do + before do stub_application_setting(two_factor_grace_period: 0) - login_with(user) + gitlab_sign_in(user) end it 'redirects to two-factor configuration page' do diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb index b306e2f5f75..cb835f533e0 100644 --- a/spec/features/merge_requests/assign_issues_spec.rb +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -13,7 +13,7 @@ feature 'Merge request issue assignment', js: true, feature: true do end def visit_merge_request(current_user = nil) - login_as(current_user || user) + gitlab_sign_in(current_user || user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb index ac260e118d0..e9dd755b6af 100644 --- a/spec/features/merge_requests/award_spec.rb +++ b/spec/features/merge_requests/award_spec.rb @@ -7,7 +7,7 @@ feature 'Merge request awards', js: true, feature: true do describe 'logged in' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb index fa306c02a43..060cfb8fdd1 100644 --- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb @@ -6,7 +6,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) } before do - login_as user + gitlab_sign_in user project.team << [user, :master] end diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb index 6ba681e36f7..6ba96570e3d 100644 --- a/spec/features/merge_requests/cherry_pick_spec.rb +++ b/spec/features/merge_requests/cherry_pick_spec.rb @@ -7,7 +7,7 @@ describe 'Cherry-pick Merge Requests', js: true do let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) } before do - login_as user + gitlab_sign_in user project.team << [user, :master] end diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb index e627618042a..371aa2bdaa7 100644 --- a/spec/features/merge_requests/closes_issues_spec.rb +++ b/spec/features/merge_requests/closes_issues_spec.rb @@ -20,7 +20,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_merge_request_path(project.namespace, project, merge_request) wait_for_requests @@ -36,7 +36,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Closed issues #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -44,7 +44,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.") + expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but were not closed") end end @@ -52,8 +52,8 @@ feature 'Merge Request closing issues message', feature: true, js: true do let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issue #{issue_1.to_reference}.") - expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but will not be closed.") + expect(page).to have_content("Closed issue #{issue_1.to_reference}") + expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but was not closed") end end @@ -61,7 +61,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Closed issues #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -69,7 +69,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.") + expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but were not closed") end end @@ -77,8 +77,8 @@ feature 'Merge Request closing issues message', feature: true, js: true do let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.") - expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but will not be closed.") + expect(page).to have_content("Closed issue #{issue_1.to_reference}") + expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but was not closed") end end end diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 27e2d5d16f3..9c091befa27 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -79,20 +79,24 @@ feature 'Merge request conflict resolution', js: true, feature: true do context 'can be resolved in the UI' do before do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) end context 'the conflicts are resolvable' do let(:merge_request) { create_merge_request('conflict-resolvable') } - before { visit namespace_project_merge_request_path(project.namespace, project, merge_request) } + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end it 'shows a link to the conflict resolution page' do expect(page).to have_link('conflicts', href: /\/conflicts\Z/) end context 'in Inline view mode' do - before { click_link('conflicts', href: /\/conflicts\Z/) } + before do + click_link('conflicts', href: /\/conflicts\Z/) + end include_examples "conflicts are resolved in Interactive mode" include_examples "conflicts are resolved in Edit inline mode" @@ -160,7 +164,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do before do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 82987c768d1..8f7adbccaaa 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -7,7 +7,7 @@ feature 'Create New Merge Request', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end it 'selects the source branch sha when a tag with the same name exists' do diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index bf34c99b92a..69059dfa562 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -16,7 +16,7 @@ feature 'Merge request created from fork' do background do fork_project.team << [user, :master] - login_as user + gitlab_sign_in user end scenario 'user can access merge request' do @@ -56,7 +56,7 @@ feature 'Merge request created from fork' do visit_merge_request(merge_request) page.within('.merge-request-tabs') { click_link 'Pipelines' } - page.within('table.ci-table') do + page.within('.ci-table') do expect(page).to have_content pipeline.status expect(page).to have_content pipeline.id end diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb index 1723fb7d365..f2af3198319 100644 --- a/spec/features/merge_requests/deleted_source_branch_spec.rb +++ b/spec/features/merge_requests/deleted_source_branch_spec.rb @@ -8,7 +8,7 @@ describe 'Deleted source branch', feature: true, js: true do let(:merge_request) { create(:merge_request) } before do - login_as user + gitlab_sign_in user merge_request.project.team << [user, :master] merge_request.update!(source_branch: 'this-branch-does-not-exist') visit namespace_project_merge_request_path( diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb index e23dc2cd940..989dfb71d10 100644 --- a/spec/features/merge_requests/diff_notes_avatars_spec.rb +++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb @@ -20,7 +20,7 @@ feature 'Diff note avatars', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'discussion tab' do diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index 4d549f3bdbb..0f8ca6f90d1 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -19,7 +19,7 @@ feature 'Diff notes resolve', feature: true, js: true do context 'no discussions' do before do project.team << [user, :master] - login_as user + gitlab_sign_in user note.destroy visit_merge_request end @@ -33,7 +33,7 @@ feature 'Diff notes resolve', feature: true, js: true do context 'as authorized user' do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit_merge_request end @@ -402,7 +402,7 @@ feature 'Diff notes resolve', feature: true, js: true do before do project.team << [guest, :guest] - login_as guest + gitlab_sign_in guest end context 'someone elses merge request' do diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index 44013df3ea0..cb6cd6571a8 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -74,8 +74,7 @@ feature 'Diffs URL', js: true, feature: true do context 'as author' do it 'shows direct edit link' do - login_as(author_user) - + gitlab_sign_in(author_user) visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax @@ -85,8 +84,7 @@ feature 'Diffs URL', js: true, feature: true do context 'as user who needs to fork' do it 'shows fork/cancel confirmation' do - login_as(user) - + gitlab_sign_in(user) visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb index 9db235f35ba..88ae257236c 100644 --- a/spec/features/merge_requests/discussion_spec.rb +++ b/spec/features/merge_requests/discussion_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Merge Request Discussions', feature: true do before do - login_as :admin + gitlab_sign_in :admin end describe "Diff discussions" do diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index c77a5c68bc6..804bf6967d6 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -8,7 +8,7 @@ feature 'Edit Merge Request', feature: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb index 32a9082b9b9..9b677aeca0a 100644 --- a/spec/features/merge_requests/filter_by_labels_spec.rb +++ b/spec/features/merge_requests/filter_by_labels_spec.rb @@ -26,7 +26,7 @@ feature 'Issue filtering by Labels', feature: true, js: true do mr3.labels << feature project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_merge_requests_path(project.namespace, project) end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index 265a0cfc198..79bca0c9de2 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -15,7 +15,7 @@ feature 'Merge Request filtering by Milestone', feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end scenario 'filters by no Milestone', js: true do diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb index d086be70d69..c12edf1fdf3 100644 --- a/spec/features/merge_requests/filter_merge_requests_spec.rb +++ b/spec/features/merge_requests/filter_merge_requests_spec.rb @@ -14,7 +14,7 @@ describe 'Filter merge requests', feature: true do before do project.team << [user, :master] group.add_developer(user) - login_as(user) + gitlab_sign_in(user) create(:merge_request, source_project: project, target_project: project) visit namespace_project_merge_requests_path(project.namespace, project) diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 00ef1ffdddc..1996c2fa09a 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -18,7 +18,7 @@ describe 'New/edit merge request', feature: true, js: true do context 'owned projects' do before do - login_as(user) + gitlab_sign_in(user) end context 'new merge request' do @@ -96,6 +96,13 @@ describe 'New/edit merge request', feature: true, js: true do .to end_with(merge_request_path(merge_request)) end end + + it 'description has autocomplete' do + find('#merge_request_description').native.send_keys('') + fill_in 'merge_request_description', with: '@' + + expect(page).to have_selector('.atwho-view') + end end context 'edit merge request' do @@ -157,13 +164,20 @@ describe 'New/edit merge request', feature: true, js: true do end end end + + it 'description has autocomplete' do + find('#merge_request_description').native.send_keys('') + fill_in 'merge_request_description', with: '@' + + expect(page).to have_selector('.atwho-view') + end end end context 'forked project' do before do fork_project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'new merge request' do diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb index 221ddb5873c..27ba380b005 100644 --- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb +++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb @@ -34,7 +34,7 @@ feature 'Clicking toggle commit message link', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_merge_request_path(project.namespace, project, merge_request) diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb index c1d4d508e57..8af7d985036 100644 --- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -18,7 +18,9 @@ feature 'Merge immediately', :feature, :js do sha: project.repository.commit('master').id) end - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end context 'when there is active pipeline for merge request' do background do @@ -26,7 +28,7 @@ feature 'Merge immediately', :feature, :js do end before do - login_as user + gitlab_sign_in user visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) end diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb index 09f889d4dd6..bfadd7cb81a 100644 --- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -28,7 +28,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do end before do - login_as user + gitlab_sign_in user visit_merge_request(merge_request) end @@ -121,7 +121,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do end before do - login_as user + gitlab_sign_in user visit_merge_request(merge_request) end diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb index 3a11ea3c8b2..7664fbfbb4c 100644 --- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb +++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb @@ -11,7 +11,7 @@ feature 'Mini Pipeline Graph', :js, :feature do before do build.run - login_as(user) + gitlab_sign_in(user) visit_merge_request end diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index b1dc81a606a..5cd9a7fbe26 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -5,7 +5,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu let(:project) { merge_request.target_project } before do - login_as merge_request.author + gitlab_sign_in merge_request.author project.team << [merge_request.author, :master] end diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb index 4c76004cb93..c2241317e04 100644 --- a/spec/features/merge_requests/pipelines_spec.rb +++ b/spec/features/merge_requests/pipelines_spec.rb @@ -7,7 +7,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'with pipelines' do @@ -28,7 +28,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do end wait_for_requests - expect(page).to have_selector('.pipeline-actions') + expect(page).to have_selector('.stage-cell') end end diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb index c154cf8ade9..4328d66c748 100644 --- a/spec/features/merge_requests/target_branch_spec.rb +++ b/spec/features/merge_requests/target_branch_spec.rb @@ -13,7 +13,7 @@ describe 'Target branch', feature: true, js: true do end before do - login_as user + gitlab_sign_in user project.team << [user, :master] end diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb index 0f98737b700..cba9a2cda99 100644 --- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb +++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Toggle Whitespace Changes', js: true, feature: true do before do - login_as :admin + gitlab_sign_in :admin merge_request = create(:merge_request) project = merge_request.source_project visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb index 3acd3f6a8b3..c4c06e9a7a0 100644 --- a/spec/features/merge_requests/toggler_behavior_spec.rb +++ b/spec/features/merge_requests/toggler_behavior_spec.rb @@ -8,7 +8,7 @@ feature 'toggler_behavior', js: true, feature: true do let(:fragment_id) { "#note_#{note.id}" } before do - login_as :admin + gitlab_sign_in :admin project = merge_request.source_project page.current_window.resize_to(1000, 300) visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}" diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb index bcdfdf78a44..d0418c74699 100644 --- a/spec/features/merge_requests/update_merge_requests_spec.rb +++ b/spec/features/merge_requests/update_merge_requests_spec.rb @@ -7,7 +7,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'status', js: true do diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb index 14bc549c9f9..ac7e0eb2727 100644 --- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb @@ -7,7 +7,7 @@ feature 'Merge requests > User posts diff notes', :js do before do project.add_developer(user) - login_as(user) + gitlab_sign_in(user) end let(:comment_button_class) { '.add-diff-note' } diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb index 22552529b9e..70652fcce8c 100644 --- a/spec/features/merge_requests/user_posts_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_notes_spec.rb @@ -13,7 +13,7 @@ describe 'Merge requests > User posts notes', :js do end before do - login_as :admin + gitlab_sign_in :admin visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_requests/user_sees_system_notes_spec.rb index 55d0f9d728c..0d88a8172b0 100644 --- a/spec/features/merge_requests/user_sees_system_notes_spec.rb +++ b/spec/features/merge_requests/user_sees_system_notes_spec.rb @@ -11,7 +11,7 @@ feature 'Merge requests > User sees system notes' do before do user = create(:user) private_project.add_developer(user) - login_as(user) + gitlab_sign_in(user) end it 'shows the system note' do diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 0e64a3e1a4b..71aa71e380e 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -1,14 +1,14 @@ require 'rails_helper' -feature 'Merge Requests > User uses slash commands', feature: true, js: true do - include SlashCommandsHelpers +feature 'Merge Requests > User uses quick actions', feature: true, js: true do + include QuickActionsHelpers let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request, source_project: project) } let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } - it_behaves_like 'issuable record that supports slash commands in its description and notes', :merge_request do + it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do let(:issuable) { create(:merge_request, source_project: project) } let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } end @@ -16,7 +16,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do describe 'merge-request-only commands' do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -51,8 +51,8 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do let(:guest) { create(:user) } before do project.team << [guest, :guest] - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -97,8 +97,8 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do let(:guest) { create(:user) } before do project.team << [guest, :guest] - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -125,9 +125,9 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } before do - logout + gitlab_sign_out another_project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) end it 'changes target_branch in new merge_request' do @@ -181,8 +181,8 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do let(:guest) { create(:user) } before do project.team << [guest, :guest] - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb index aad522ee26e..04a72d3be34 100644 --- a/spec/features/merge_requests/versions_spec.rb +++ b/spec/features/merge_requests/versions_spec.rb @@ -8,7 +8,7 @@ feature 'Merge Request versions', js: true, feature: true do let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') } before do - login_as :admin + gitlab_sign_in :admin visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 118ecd9cba5..e82e69c5f4a 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -12,7 +12,7 @@ feature 'Widget Deployments Header', feature: true, js: true do given!(:manual) { } background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 4f3a5119915..3ac1f603de6 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -7,7 +7,7 @@ describe 'Merge request', :feature, :js do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'new merge request' do @@ -209,8 +209,8 @@ describe 'Merge request', :feature, :js do before do project.team << [user2, :master] - logout - login_as user2 + gitlab_sign_out + gitlab_sign_in user2 merge_request.update(target_project: fork_project) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb index 3311731b33b..72d001bf408 100644 --- a/spec/features/merge_requests/wip_message_spec.rb +++ b/spec/features/merge_requests/wip_message_spec.rb @@ -6,7 +6,7 @@ feature 'Work In Progress help message', feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'with WIP commits' do diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index c07de01c594..58989581ffe 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -6,7 +6,7 @@ feature 'Milestone', feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end feature 'Create a milestone' do diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb deleted file mode 100644 index b3dfd6d0e81..00000000000 --- a/spec/features/milestones/milestones_spec.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'rails_helper' - -describe 'Milestone draggable', feature: true, js: true do - include DragTo - - let(:milestone) { create(:milestone, project: project, title: 8.14) } - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - - context 'issues' do - let(:issue) { page.find_by_id('issues-list-unassigned').find('li') } - let(:issue_target) { page.find_by_id('issues-list-ongoing') } - - it 'does not allow guest to drag issue' do - create_and_drag_issue - - expect(issue_target).not_to have_selector('.issuable-row') - end - - it 'does not allow authorized user to drag issue' do - login_as(user) - create_and_drag_issue - - expect(issue_target).not_to have_selector('.issuable-row') - end - - it 'allows author to drag issue' do - login_as(user) - create_and_drag_issue(author: user) - - expect(issue_target).to have_selector('.issuable-row') - end - - it 'allows admin to drag issue' do - login_as(:admin) - create_and_drag_issue - - expect(issue_target).to have_selector('.issuable-row') - end - end - - context 'merge requests' do - let(:merge_request) { page.find_by_id('merge_requests-list-unassigned').find('li') } - let(:merge_request_target) { page.find_by_id('merge_requests-list-ongoing') } - - it 'does not allow guest to drag merge request' do - create_and_drag_merge_request - - expect(merge_request_target).not_to have_selector('.issuable-row') - end - - it 'does not allow authorized user to drag merge request' do - login_as(user) - create_and_drag_merge_request - - expect(merge_request_target).not_to have_selector('.issuable-row') - end - - it 'allows author to drag merge request' do - login_as(user) - create_and_drag_merge_request(author: user) - - expect(merge_request_target).to have_selector('.issuable-row') - end - - it 'allows admin to drag merge request' do - login_as(:admin) - create_and_drag_merge_request - - expect(merge_request_target).to have_selector('.issuable-row') - end - end - - def create_and_drag_issue(params = {}) - create(:issue, params.merge(title: 'Foo', project: project, milestone: milestone)) - - visit namespace_project_milestone_path(project.namespace, project, milestone) - scroll_into_view('.milestone-content') - drag_to(selector: '.issues-sortable-list', list_to_index: 1) - - wait_for_requests - end - - def create_and_drag_merge_request(params = {}) - create(:merge_request, params.merge(title: 'Foo', source_project: project, target_project: project, milestone: milestone)) - - visit namespace_project_milestone_path(project.namespace, project, milestone) - page.find("a[href='#tab-merge-requests']").click - - wait_for_requests - - scroll_into_view('.milestone-content') - drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1) - - wait_for_requests - end - - def scroll_into_view(selector) - page.evaluate_script("document.querySelector('#{selector}').scrollIntoView();") - end -end diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb index 227eb04ba72..cdf6cfba402 100644 --- a/spec/features/milestones/show_spec.rb +++ b/spec/features/milestones/show_spec.rb @@ -9,7 +9,7 @@ describe 'Milestone show', feature: true do before do project.add_user(user, :developer) - login_as(user) + gitlab_sign_in(user) end def visit_milestone diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index 449ce80bc71..b8966cf621c 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -8,7 +8,7 @@ feature 'Member autocomplete', :js do before do note # actually create the note - login_as(user) + gitlab_sign_in(user) end shared_examples "open suggestions when typing @" do diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 7df628fd7a0..bb4263d83f3 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -4,7 +4,7 @@ describe 'Profile account page', feature: true do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) end describe 'when signup is enabled' do diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb index 05a7587f8d4..33fd29b429b 100644 --- a/spec/features/profiles/account_spec.rb +++ b/spec/features/profiles/account_spec.rb @@ -4,7 +4,7 @@ feature 'Profile > Account', feature: true do given(:user) { create(:user, username: 'foo') } before do - login_as(user) + gitlab_sign_in(user) end describe 'Change username' do @@ -31,8 +31,13 @@ feature 'Profile > Account', feature: true do given(:new_project_path) { "/#{new_username}/#{project.path}" } given(:old_project_path) { "/#{user.username}/#{project.path}" } - before(:context) { TestEnv.clean_test_path } - after(:example) { TestEnv.clean_test_path } + before(:context) do + TestEnv.clean_test_path + end + + after(:example) do + TestEnv.clean_test_path + end scenario 'the project is accessible via the new path' do update_username(new_username) diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb index 6f6f7029c0b..1a162d6be0e 100644 --- a/spec/features/profiles/chat_names_spec.rb +++ b/spec/features/profiles/chat_names_spec.rb @@ -5,7 +5,7 @@ feature 'Profile > Chat', feature: true do given(:service) { create(:service) } before do - login_as(user) + gitlab_sign_in(user) end describe 'uses authorization link' do diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index 2f436f153aa..13f9afd4ce0 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -4,7 +4,7 @@ feature 'Profile > SSH Keys', feature: true do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) end describe 'User adds a key' do diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb index 1a5a9059dbd..a6f9beafe17 100644 --- a/spec/features/profiles/oauth_applications_spec.rb +++ b/spec/features/profiles/oauth_applications_spec.rb @@ -4,7 +4,7 @@ describe 'Profile > Applications', feature: true do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) end describe 'User manages applications', js: true do diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 4cbdd89d46f..2d36f3d020f 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -4,7 +4,7 @@ describe 'Profile > Password', feature: true do let(:user) { create(:user, password_automatically_set: true) } before do - login_as(user) + gitlab_sign_in(user) visit edit_profile_password_path end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 7e2e685df26..d7acaaf1eb8 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -23,7 +23,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do end before do - login_as(user) + gitlab_sign_in(user) end describe "token creation" do diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index d368bc4d753..8e7ef6bc110 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -4,7 +4,7 @@ describe 'Profile > Preferences', feature: true do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) visit profile_preferences_path end diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb index e05fbb3715c..c0092836e3b 100644 --- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb +++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb @@ -4,7 +4,7 @@ feature 'Profile > Notifications > User changes notified_of_own_activity setting let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) end scenario 'User opts into receiving notifications about their own activity' do diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb index 3c1de5c09b2..84c81d43448 100644 --- a/spec/features/projects/activity/rss_spec.rb +++ b/spec/features/projects/activity/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Project Activity RSS' do before do user = create(:user) project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index 01a95bf49ac..9624e1a71b0 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -7,7 +7,7 @@ feature 'test coverage badge' do context 'when user has access to view badge' do background do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) end scenario 'user requests coverage badge image for pipeline' do @@ -45,7 +45,7 @@ feature 'test coverage badge' do end context 'when user does not have access to view badge' do - background { login_as(user) } + background { gitlab_sign_in(user) } scenario 'user requests test coverage badge image' do show_test_coverage_badge diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index ae9db0c0d6e..348748152bb 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -5,7 +5,7 @@ feature 'list of badges' do user = create(:user) project = create(:project) project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_pipelines_settings_path(project.namespace, project) end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 45fdb36e506..71ffa352f80 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -17,6 +17,7 @@ feature 'File blob', :js, feature: true do it 'displays the blob' do aggregate_failures do # shows highlighted Ruby code + expect(page).to have_css(".js-syntax-highlight") expect(page).to have_content("require 'fileutils'") # does not show a viewer switcher @@ -71,6 +72,7 @@ feature 'File blob', :js, feature: true do expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) # shows highlighted Markdown code + expect(page).to have_css(".js-syntax-highlight") expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") # shows an enabled copy button @@ -114,6 +116,7 @@ feature 'File blob', :js, feature: true do expect(page).to have_selector('#LC1.hll') # shows highlighted Markdown code + expect(page).to have_css(".js-syntax-highlight") expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") # shows an enabled copy button diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index d04c3248ead..d0bc032ee93 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -14,7 +14,7 @@ feature 'Editing file blob', feature: true, js: true do before do project.team << [user, role] - login_as(user) + gitlab_sign_in(user) end def edit_and_commit @@ -61,7 +61,7 @@ feature 'Editing file blob', feature: true, js: true do it 'redirects to sign in and returns' do expect(page).to have_current_path(new_user_session_path) - login_as(user) + gitlab_sign_in(user) expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))) end @@ -77,7 +77,7 @@ feature 'Editing file blob', feature: true, js: true do it 'redirects to sign in and returns' do expect(page).to have_current_path(new_user_session_path) - login_as(user) + gitlab_sign_in(user) expect(page).to have_current_path(namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path))) end @@ -92,7 +92,7 @@ feature 'Editing file blob', feature: true, js: true do project.team << [user, :developer] project.repository.add_branch(user, protected_branch, 'master') create(:protected_branch, project: project, name: protected_branch) - login_as(user) + gitlab_sign_in(user) end context 'on some branch' do @@ -122,7 +122,7 @@ feature 'Editing file blob', feature: true, js: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)) end diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 92028c19361..d8c4d475a2c 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -22,7 +22,7 @@ feature 'Download buttons in branches page', feature: true do end background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] end diff --git a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb index c5e0a0f0517..406fa52e723 100644 --- a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb +++ b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb @@ -8,7 +8,7 @@ describe 'New Branch Ref Dropdown', :js, :feature do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit new_namespace_project_branch_path(project.namespace, project) end diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 7668ce5f8be..5c3d1b3f1ee 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -12,7 +12,7 @@ describe 'Branches', feature: true do context 'logged in as developer' do before do - login_as :user + gitlab_sign_in :user project.team << [@user, :developer] end @@ -87,7 +87,7 @@ describe 'Branches', feature: true do context 'logged in as master' do before do - login_as :user + gitlab_sign_in :user project.team << [@user, :master] end diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index 268d420c594..e5b1f95f2b9 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -6,7 +6,7 @@ feature 'project commit pipelines', js: true do background do user = create(:user) project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'when no builds triggered yet' do diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index bc7ca0ddd38..1fb68a0f5e4 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -7,7 +7,7 @@ describe 'Cherry-pick Commits' do let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') } before do - login_as :user + gitlab_sign_in :user project.team << [@user, :master] visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) end diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index f2de195eb7f..570a7ae7b16 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -5,7 +5,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do let(:project) { create(:project, :public) } before do - login_as(user) + gitlab_sign_in(user) end context 'when commit has pipelines' do diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb index 03b6d560c96..f7548a56984 100644 --- a/spec/features/projects/commit/rss_spec.rb +++ b/spec/features/projects/commit/rss_spec.rb @@ -8,7 +8,7 @@ feature 'Project Commits RSS' do before do user = create(:user) project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index ee6985ad993..4743d69fb75 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -6,7 +6,7 @@ describe "Compare", js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_compare_index_path(project.namespace, project, from: "master", to: "master") end diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb index 06abfbbc86b..a31960639fe 100644 --- a/spec/features/projects/deploy_keys_spec.rb +++ b/spec/features/projects/deploy_keys_spec.rb @@ -6,7 +6,7 @@ describe 'Project deploy keys', :js, :feature do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end describe 'removing key' do diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb index 0c51fe72ca4..a943f1e6a08 100644 --- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb +++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb @@ -7,7 +7,7 @@ feature 'Developer views empty project instructions', feature: true do background do project.team << [developer, :developer] - login_as(developer) + gitlab_sign_in(developer) end context 'without an SSH key' do diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb new file mode 100644 index 00000000000..48b7f1e0f34 --- /dev/null +++ b/spec/features/projects/diffs/diff_show_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +feature 'Diff file viewer', :js, feature: true do + let(:project) { create(:project, :public, :repository) } + + def visit_commit(sha, anchor: nil) + visit namespace_project_commit_path(project.namespace, project, sha, anchor: anchor) + + wait_for_requests + end + + context 'Ruby file' do + before do + visit_commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') + end + + it 'shows highlighted Ruby code' do + within('.diff-file[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd"]') do + expect(page).to have_css(".js-syntax-highlight") + expect(page).to have_content("def popen(cmd, path=nil)") + end + end + end + + context 'Ruby file (stored in LFS)' do + before do + project.add_master(project.creator) + + @commit_id = Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add Ruby file in LFS", + file_path: 'files/lfs/ruby.rb', + file_content: project.repository.blob_at('master', 'files/lfs/lfs_object.iso').data + ).execute[:result] + end + + context 'when LFS is enabled on the project' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) + + visit_commit(@commit_id) + end + + it 'shows an error message' do + expect(page).to have_content('This source diff could not be displayed because it is stored in LFS. You can view the blob instead.') + end + end + + context 'when LFS is disabled on the project' do + before do + visit_commit(@commit_id) + end + + it 'displays the diff' do + expect(page).to have_content('size 1575078') + end + end + end + + context 'Image file' do + before do + visit_commit('2f63565e7aac07bcdadb654e253078b727143ec4') + end + + it 'shows a rendered image' do + within('.diff-file[id="e986451b8f7397b617dbb6fffcb5539328c56921"]') do + expect(page).to have_css('img[alt="files/images/6049019_460s.jpg"]') + end + end + end + + context 'ISO file (stored in LFS)' do + context 'when LFS is enabled on the project' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) + + visit_commit('048721d90c449b244b7b4c53a9186b04330174ec') + end + + it 'shows that file was added' do + expect(page).to have_content('File added') + end + end + + context 'when LFS is disabled on the project' do + before do + visit_commit('048721d90c449b244b7b4c53a9186b04330174ec') + end + + it 'displays the diff' do + expect(page).to have_content('size 1575078') + end + end + end + + context 'ZIP file' do + before do + visit_commit('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') + end + + it 'shows that file was added' do + expect(page).to have_content('File added') + end + end + + context 'binary file that appears to be text in the first 1024 bytes' do + before do + visit_commit('7b1cf4336b528e0f3d1d140ee50cafdbc703597c') + end + + it 'shows the diff is collapsed' do + expect(page).to have_content('This diff is collapsed. Click to expand it.') + end + + context 'expanding the diff' do + before do + # We can't use `click_link` because the "link" doesn't have an `href`. + find('a.click-to-expand').click + + wait_for_requests + end + + it 'shows there is no preview' do + expect(page).to have_content('No preview for this file type') + end + end + end +end diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb index a263781c43c..ca202b95a44 100644 --- a/spec/features/projects/edit_spec.rb +++ b/spec/features/projects/edit_spec.rb @@ -6,7 +6,7 @@ feature 'Project edit', feature: true, js: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit edit_namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb index ee925e811e1..b48dcf6c774 100644 --- a/spec/features/projects/environments/environment_metrics_spec.rb +++ b/spec/features/projects/environments/environment_metrics_spec.rb @@ -15,7 +15,7 @@ feature 'Environment > Metrics', :feature do create(:deployment, environment: environment, deployable: build) stub_all_prometheus_requests(environment.slug) - login_as(user) + gitlab_sign_in(user) visit_environment(environment) end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 18b608c863e..7d565555f1f 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -6,7 +6,7 @@ feature 'Environment', :feature do given(:role) { :developer } background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 613b1edba36..83883dba0ba 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -7,7 +7,7 @@ feature 'Environments page', :feature, :js do background do project.team << [user, role] - login_as(user) + gitlab_sign_in(user) end given!(:environment) { } diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index c49648f54bd..db2790a4bce 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -9,7 +9,7 @@ describe 'Edit Project Settings', feature: true do describe 'project features visibility selectors', js: true do before do project.team << [member, :master] - login_as(member) + gitlab_sign_in(member) end tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" } @@ -68,9 +68,12 @@ describe 'Edit Project Settings', feature: true do end describe 'project features visibility pages' do + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let(:job) { create(:ci_build, pipeline: pipeline) } + let(:tools) do { - builds: namespace_project_pipelines_path(project.namespace, project), + builds: namespace_project_job_path(project.namespace, project, job), issues: namespace_project_issues_path(project.namespace, project), wiki: namespace_project_wiki_path(project.namespace, project, :home), snippets: namespace_project_snippets_path(project.namespace, project), @@ -80,7 +83,7 @@ describe 'Edit Project Settings', feature: true do context 'normal user' do before do - login_as(member) + gitlab_sign_in(member) end it 'renders 200 if tool is enabled' do @@ -127,7 +130,7 @@ describe 'Edit Project Settings', feature: true do context 'admin user' do before do non_member.update_attribute(:admin, true) - login_as(non_member) + gitlab_sign_in(non_member) end it 'renders 404 if feature is disabled' do @@ -153,7 +156,7 @@ describe 'Edit Project Settings', feature: true do describe 'repository visibility', js: true do before do project.team << [member, :master] - login_as(member) + gitlab_sign_in(member) visit edit_namespace_project_path(project.namespace, project) end @@ -239,7 +242,7 @@ describe 'Edit Project Settings', feature: true do before do project.team << [member, :guest] - login_as(member) + gitlab_sign_in(member) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb index 30a1eedbb48..2a82c3ac179 100644 --- a/spec/features/projects/files/browse_files_spec.rb +++ b/spec/features/projects/files/browse_files_spec.rb @@ -6,7 +6,7 @@ feature 'user browses project', feature: true, js: true do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit namespace_project_tree_path(project.namespace, project, project.default_branch) end diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb index 69744ac3948..2a1cc01fe68 100644 --- a/spec/features/projects/files/creating_a_file_spec.rb +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -6,7 +6,7 @@ feature 'User wants to create a file', feature: true do background do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_new_blob_path(project.namespace, project, project.default_branch) end diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb index 93909e91d05..4f1b8588462 100644 --- a/spec/features/projects/files/dockerfile_dropdown_spec.rb +++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb @@ -7,7 +7,7 @@ feature 'User wants to add a Dockerfile file', feature: true do project = create(:project) project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'Dockerfile') end diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index d7c29a7e074..60182bfebe9 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -22,7 +22,7 @@ feature 'Download buttons in files tree', feature: true do end background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] end diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb index 012befa7990..6e361ac4312 100644 --- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb +++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb @@ -5,7 +5,7 @@ feature 'User uses soft wrap whilst editing file', feature: true, js: true do user = create(:user) project = create(:project) project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'test_file-name') editor = find('.file-editor.code') editor.click diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb index 7a3afafec29..e97ff5fded7 100644 --- a/spec/features/projects/files/editing_a_file_spec.rb +++ b/spec/features/projects/files/editing_a_file_spec.rb @@ -17,7 +17,7 @@ feature 'User wants to edit a file', feature: true do background do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, '.gitignore')) end diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb index 5c8105de4cb..83a837fba44 100644 --- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb +++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb @@ -6,7 +6,7 @@ feature 'User views files page', feature: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref) end diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb index ee42bcaec4b..6a914820ac9 100644 --- a/spec/features/projects/files/find_file_keyboard_spec.rb +++ b/spec/features/projects/files/find_file_keyboard_spec.rb @@ -6,7 +6,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref) diff --git a/spec/features/projects/files/find_files_spec.rb b/spec/features/projects/files/find_files_spec.rb index 716b7591b95..166ec5c921b 100644 --- a/spec/features/projects/files/find_files_spec.rb +++ b/spec/features/projects/files/find_files_spec.rb @@ -5,7 +5,7 @@ feature 'Find files button in the tree header', feature: true do given(:project) { create(:project) } background do - login_as(user) + gitlab_sign_in(user) project.team << [user, :developer] end diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb index e9f49453121..7f02ec6b73d 100644 --- a/spec/features/projects/files/gitignore_dropdown_spec.rb +++ b/spec/features/projects/files/gitignore_dropdown_spec.rb @@ -5,7 +5,7 @@ feature 'User wants to add a .gitignore file', feature: true do user = create(:user) project = create(:project) project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore') end diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index 031b89d0499..f4b17c2518c 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -5,7 +5,7 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do user = create(:user) project = create(:project) project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml') end diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index 8d410cc3f2e..7daf016dd22 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -7,7 +7,7 @@ feature 'project owner creates a license file', feature: true, js: true do project.repository.delete_file(project_master, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') project.team << [project_master, :master] - login_as(project_master) + gitlab_sign_in(project_master) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 8e197bccabf..eab19d52030 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -5,7 +5,7 @@ feature 'project owner sees a link to create a license file in empty project', f let(:project) { create(:empty_project) } background do project.team << [project_master, :master] - login_as(project_master) + gitlab_sign_in(project_master) end scenario 'project master creates a license file from a template' do diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb index 9fcf12e6cb9..028a0919640 100644 --- a/spec/features/projects/files/template_type_dropdown_spec.rb +++ b/spec/features/projects/files/template_type_dropdown_spec.rb @@ -6,7 +6,7 @@ feature 'Template type dropdown selector', js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'editing a non-matching file' do diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb index de10eec0557..4ccd123f46e 100644 --- a/spec/features/projects/files/undo_template_spec.rb +++ b/spec/features/projects/files/undo_template_spec.rb @@ -6,7 +6,7 @@ feature 'Template Undo Button', js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'editing a matching file and applying a template' do diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb index 67bc9142356..aa4ed217a34 100644 --- a/spec/features/projects/gfm_autocomplete_load_spec.rb +++ b/spec/features/projects/gfm_autocomplete_load_spec.rb @@ -4,7 +4,7 @@ describe 'GFM autocomplete loading', feature: true, js: true do let(:project) { create(:project) } before do - login_as :admin + gitlab_sign_in :admin visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb index 1b680a56492..778f5d61ae3 100644 --- a/spec/features/projects/group_links_spec.rb +++ b/spec/features/projects/group_links_spec.rb @@ -9,7 +9,7 @@ feature 'Project group links', :feature, :js do background do project.add_master(master) - login_as(master) + gitlab_sign_in(master) end context 'setting an expiration date for a group link' do diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb index b91c3eff478..e1f7f06c113 100644 --- a/spec/features/projects/guest_navigation_menu_spec.rb +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -7,7 +7,7 @@ describe 'Guest navigation menu' do before do project.team << [guest, :guest] - login_as(guest) + gitlab_sign_in(guest) end it 'shows allowed tabs only' do diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 40caf89dd54..b5c64777934 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -33,7 +33,7 @@ feature 'Import/Export - project export integration test', feature: true, js: tr context 'admin user' do before do - login_as(user) + gitlab_sign_in(user) end scenario 'exports a project successfully' do diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 583f479ec18..a111aa87c52 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -19,7 +19,7 @@ feature 'Import/Export - project import integration test', feature: true, js: tr let!(:namespace) { create(:namespace, name: "asd", owner: user) } before do - login_as(user) + gitlab_sign_in(user) end scenario 'user imports an exported project successfully' do @@ -77,7 +77,7 @@ feature 'Import/Export - project import integration test', feature: true, js: tr context 'when limited to the default user namespace' do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) end scenario 'passes correct namespace ID in the URL' do diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb index cb399ea55df..b0a68f0d61f 100644 --- a/spec/features/projects/import_export/namespace_export_file_spec.rb +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -16,7 +16,7 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true context 'admin user' do before do - login_as(:admin) + gitlab_sign_in(:admin) end context 'moving the namespace' do diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz Binary files differindex 4efd5a26a82..e03e7b88174 100644 --- a/spec/features/projects/import_export/test_project_export.tar.gz +++ b/spec/features/projects/import_export/test_project_export.tar.gz diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 3076c863dcb..26a09985312 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -6,7 +6,7 @@ feature 'issuable templates', feature: true, js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user end context 'user creates an issue using templates' do @@ -124,11 +124,11 @@ feature 'issuable templates', feature: true, js: true do let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) } background do - logout + gitlab_sign_out project.team << [fork_user, :developer] fork_project.team << [fork_user, :master] create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) - login_as fork_user + gitlab_sign_in fork_user project.repository.create_file( fork_user, '.gitlab/merge_request_templates/feature-proposal.md', diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb index 3137af074ca..b2db07a75ef 100644 --- a/spec/features/projects/issues/list_spec.rb +++ b/spec/features/projects/issues/list_spec.rb @@ -7,7 +7,7 @@ feature 'Issues List' do background do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) end scenario 'user does not see create new list button' do diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb index f6852192aef..38733d39932 100644 --- a/spec/features/projects/issues/rss_spec.rb +++ b/spec/features/projects/issues/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Project Issues RSS' do before do user = create(:user) project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 727ae7081b0..070cdbf1cef 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -8,8 +8,8 @@ feature 'Jobs', :feature do let(:namespace) { project.namespace } let(:pipeline) { create(:ci_pipeline, project: project) } - let(:build) { create(:ci_build, :trace, pipeline: pipeline) } - let(:build2) { create(:ci_build) } + let(:job) { create(:ci_build, :trace, pipeline: pipeline) } + let(:job2) { create(:ci_build) } let(:artifacts_file) do fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') @@ -17,11 +17,11 @@ feature 'Jobs', :feature do before do project.team << [user, user_access_level] - login_as(user) + gitlab_sign_in(user) end describe "GET /:project/jobs" do - let!(:build) { create(:ci_build, pipeline: pipeline) } + let!(:job) { create(:ci_build, pipeline: pipeline) } context "Pending scope" do before do @@ -31,30 +31,30 @@ feature 'Jobs', :feature do it "shows Pending tab jobs" do expect(page).to have_link 'Cancel running' expect(page).to have_selector('.nav-links li.active', text: 'Pending') - expect(page).to have_content build.short_sha - expect(page).to have_content build.ref - expect(page).to have_content build.name + expect(page).to have_content job.short_sha + expect(page).to have_content job.ref + expect(page).to have_content job.name end end context "Running scope" do before do - build.run! + job.run! visit namespace_project_jobs_path(project.namespace, project, scope: :running) end it "shows Running tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Running') expect(page).to have_link 'Cancel running' - expect(page).to have_content build.short_sha - expect(page).to have_content build.ref - expect(page).to have_content build.name + expect(page).to have_content job.short_sha + expect(page).to have_content job.ref + expect(page).to have_content job.name end end context "Finished scope" do before do - build.run! + job.run! visit namespace_project_jobs_path(project.namespace, project, scope: :finished) end @@ -73,9 +73,9 @@ feature 'Jobs', :feature do it "shows All tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'All') - expect(page).to have_content build.short_sha - expect(page).to have_content build.ref - expect(page).to have_content build.name + expect(page).to have_content job.short_sha + expect(page).to have_content job.ref + expect(page).to have_content job.name expect(page).not_to have_link 'Cancel running' end end @@ -97,7 +97,7 @@ feature 'Jobs', :feature do describe "POST /:project/jobs/:id/cancel_all" do before do - build.run! + job.run! visit namespace_project_jobs_path(project.namespace, project) click_link "Cancel running" end @@ -105,19 +105,19 @@ feature 'Jobs', :feature do it 'shows all necessary content' do expect(page).to have_selector('.nav-links li.active', text: 'All') expect(page).to have_content 'canceled' - expect(page).to have_content build.short_sha - expect(page).to have_content build.ref - expect(page).to have_content build.name + expect(page).to have_content job.short_sha + expect(page).to have_content job.ref + expect(page).to have_content job.name expect(page).not_to have_link 'Cancel running' end end describe "GET /:project/jobs/:id" do context "Job from project" do - let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let(:job) { create(:ci_build, :success, pipeline: pipeline) } before do - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) end it 'shows status name', :js do @@ -131,33 +131,33 @@ feature 'Jobs', :feature do expect(page).to have_content pipeline.git_author_name end - it 'shows active build' do + it 'shows active job' do expect(page).to have_selector('.build-job.active') end end context 'when job is not running', :js do - let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let(:job) { create(:ci_build, :success, pipeline: pipeline) } before do - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) end it 'shows retry button' do expect(page).to have_link('Retry') end - context 'if build passed' do + context 'if job passed' do it 'does not show New issue button' do expect(page).not_to have_link('New issue') end end - context 'if build failed' do - let(:build) { create(:ci_build, :failed, pipeline: pipeline) } + context 'if job failed' do + let(:job) { create(:ci_build, :failed, pipeline: pipeline) } before do - visit namespace_project_job_path(namespace, project, build) + visit namespace_project_job_path(namespace, project, job) end it 'shows New issue button' do @@ -165,9 +165,9 @@ feature 'Jobs', :feature do end it 'links to issues/new with the title and description filled in' do - button_title = "Build Failed ##{build.id}" - build_path = namespace_project_job_path(namespace, project, build) - options = { issue: { title: button_title, description: build_path } } + button_title = "Build Failed ##{job.id}" + job_path = namespace_project_job_path(namespace, project, job) + options = { issue: { title: button_title, description: job_path } } href = new_namespace_project_issue_path(namespace, project, options) @@ -180,7 +180,7 @@ feature 'Jobs', :feature do context "Job from other project" do before do - visit namespace_project_job_path(project.namespace, project, build2) + visit namespace_project_job_path(project.namespace, project, job2) end it { expect(page.status_code).to eq(404) } @@ -188,8 +188,8 @@ feature 'Jobs', :feature do context "Download artifacts" do before do - build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_job_path(project.namespace, project, build) + job.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_job_path(project.namespace, project, job) end it 'has button to download artifacts' do @@ -199,10 +199,10 @@ feature 'Jobs', :feature do context 'Artifacts expire date' do before do - build.update_attributes(artifacts_file: artifacts_file, - artifacts_expire_at: expire_at) + job.update_attributes(artifacts_file: artifacts_file, + artifacts_expire_at: expire_at) - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) end context 'no expire date defined' do @@ -248,7 +248,7 @@ feature 'Jobs', :feature do context "when visiting old URL" do let(:job_url) do - namespace_project_job_path(project.namespace, project, build) + namespace_project_job_path(project.namespace, project, job) end before do @@ -262,9 +262,9 @@ feature 'Jobs', :feature do feature 'Raw trace' do before do - build.run! + job.run! - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) end it do @@ -274,16 +274,16 @@ feature 'Jobs', :feature do feature 'HTML trace', :js do before do - build.run! + job.run! - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) end context 'when job has an initial trace' do it 'loads job trace' do expect(page).to have_content 'BUILD TRACE' - build.trace.write do |stream| + job.trace.write do |stream| stream.append(' and more trace', 11) end @@ -295,12 +295,12 @@ feature 'Jobs', :feature do feature 'Variables' do let(:trigger_request) { create(:ci_trigger_request_with_variables) } - let(:build) do + let(:job) do create :ci_build, pipeline: pipeline, trigger_request: trigger_request end before do - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) end it 'shows variable key and value after click', js: true do @@ -322,20 +322,20 @@ feature 'Jobs', :feature do context 'job is successfull and has deployment' do let(:deployment) { create(:deployment) } - let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } + let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } it 'shows a link for the job' do - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) expect(page).to have_link environment.name end end context 'job is complete and not successful' do - let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } + let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } it 'shows a link for the job' do - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) expect(page).to have_link environment.name end @@ -343,10 +343,10 @@ feature 'Jobs', :feature do context 'job creates a new deployment' do let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } - let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } + let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } it 'shows a link to latest deployment' do - visit namespace_project_job_path(project.namespace, project, build) + visit namespace_project_job_path(project.namespace, project, job) expect(page).to have_link('latest deployment') end @@ -357,8 +357,8 @@ feature 'Jobs', :feature do describe "POST /:project/jobs/:id/cancel", :js do context "Job from project" do before do - build.run! - visit namespace_project_job_path(project.namespace, project, build) + job.run! + visit namespace_project_job_path(project.namespace, project, job) find('.js-cancel-job').click() end @@ -372,8 +372,8 @@ feature 'Jobs', :feature do describe "POST /:project/jobs/:id/retry" do context "Job from project", :js do before do - build.run! - visit namespace_project_job_path(project.namespace, project, build) + job.run! + visit namespace_project_job_path(project.namespace, project, job) find('.js-cancel-job').click() find('.js-retry-button').trigger('click') end @@ -388,13 +388,13 @@ feature 'Jobs', :feature do context "Job that current user is not allowed to retry" do before do - build.run! - build.cancel! + job.run! + job.cancel! project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - logout_direct - login_with(create(:user)) - visit namespace_project_job_path(project.namespace, project, build) + gitlab_sign_out_direct + gitlab_sign_in(create(:user)) + visit namespace_project_job_path(project.namespace, project, job) end it 'does not show the Retry button' do @@ -407,15 +407,15 @@ feature 'Jobs', :feature do describe "GET /:project/jobs/:id/download" do before do - build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_job_path(project.namespace, project, build) + job.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_job_path(project.namespace, project, job) click_link 'Download' end context "Build from other project" do before do - build2.update_attributes(artifacts_file: artifacts_file) - visit download_namespace_project_job_artifacts_path(project.namespace, project, build2) + job2.update_attributes(artifacts_file: artifacts_file) + visit download_namespace_project_job_artifacts_path(project.namespace, project, job2) end it { expect(page.status_code).to eq(404) } @@ -427,23 +427,23 @@ feature 'Jobs', :feature do context 'job from project' do before do Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } - build.run! - visit namespace_project_job_path(project.namespace, project, build) + job.run! + visit namespace_project_job_path(project.namespace, project, job) find('.js-raw-link-controller').click() end it 'sends the right headers' do expect(page.status_code).to eq(200) expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(build.trace.send(:current_path)) + expect(page.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path)) end end context 'job from other project' do before do Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } - build2.run! - visit raw_namespace_project_job_path(project.namespace, project, build2) + job2.run! + visit raw_namespace_project_job_path(project.namespace, project, job2) end it 'sends the right headers' do @@ -458,16 +458,16 @@ feature 'Jobs', :feature do before do Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } - build.run! + job.run! end - context 'when build has trace in file', :js do + context 'when job has trace in file', :js do before do allow_any_instance_of(Gitlab::Ci::Trace) .to receive(:paths) .and_return([existing_file]) - visit namespace_project_job_path(namespace, project, build) + visit namespace_project_job_path(namespace, project, job) find('.js-raw-link-controller').click end @@ -485,7 +485,7 @@ feature 'Jobs', :feature do .to receive(:paths) .and_return([]) - visit namespace_project_job_path(namespace, project, build) + visit namespace_project_job_path(namespace, project, job) end it 'sends the right headers' do @@ -496,7 +496,7 @@ feature 'Jobs', :feature do context "when visiting old URL" do let(:raw_job_url) do - raw_namespace_project_job_path(project.namespace, project, build) + raw_namespace_project_job_path(project.namespace, project, job) end before do @@ -512,7 +512,7 @@ feature 'Jobs', :feature do describe "GET /:project/jobs/:id/trace.json" do context "Job from project" do before do - visit trace_namespace_project_job_path(project.namespace, project, build, format: :json) + visit trace_namespace_project_job_path(project.namespace, project, job, format: :json) end it { expect(page.status_code).to eq(200) } @@ -520,7 +520,7 @@ feature 'Jobs', :feature do context "Job from other project" do before do - visit trace_namespace_project_job_path(project.namespace, project, build2, format: :json) + visit trace_namespace_project_job_path(project.namespace, project, job2, format: :json) end it { expect(page.status_code).to eq(404) } @@ -530,7 +530,7 @@ feature 'Jobs', :feature do describe "GET /:project/jobs/:id/status" do context "Job from project" do before do - visit status_namespace_project_job_path(project.namespace, project, build) + visit status_namespace_project_job_path(project.namespace, project, job) end it { expect(page.status_code).to eq(200) } @@ -538,7 +538,7 @@ feature 'Jobs', :feature do context "Job from other project" do before do - visit status_namespace_project_job_path(project.namespace, project, build2) + visit status_namespace_project_job_path(project.namespace, project, job2) end it { expect(page.status_code).to eq(404) } diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb index e2911a37e40..2c47758f30e 100644 --- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -28,7 +28,7 @@ feature 'Issue prioritization', feature: true do issue_2.labels << label_4 issue_1.labels << label_5 - login_as user + gitlab_sign_in user visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority') # Ensure we are indicating that issues are sorted by priority @@ -67,7 +67,7 @@ feature 'Issue prioritization', feature: true do issue_4.labels << label_4 # 7 issue_6.labels << label_5 # 8 - No priority - login_as user + gitlab_sign_in user visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority') expect(page).to have_selector('.dropdown-toggle', text: 'Label priority') diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb index 3130d87fba5..584dc294f05 100644 --- a/spec/features/projects/labels/subscription_spec.rb +++ b/spec/features/projects/labels/subscription_spec.rb @@ -10,7 +10,7 @@ feature 'Labels subscription', feature: true do context 'when signed in' do before do project.team << [user, :developer] - login_as user + gitlab_sign_in user end scenario 'users can subscribe/unsubscribe to labels', js: true do diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 34fafe072a3..589bfb9fbc9 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -14,7 +14,7 @@ feature 'Prioritize labels', feature: true do before do project.team << [user, :developer] - login_as user + gitlab_sign_in user end scenario 'user can prioritize a group label', js: true do @@ -120,7 +120,7 @@ feature 'Prioritize labels', feature: true do it 'does not prioritize labels' do guest = create(:user) - login_as guest + gitlab_sign_in guest visit namespace_project_labels_path(project.namespace, project) diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb index 02198ff3e41..514453db472 100644 --- a/spec/features/projects/main/download_buttons_spec.rb +++ b/spec/features/projects/main/download_buttons_spec.rb @@ -22,7 +22,7 @@ feature 'Download buttons in project main page', feature: true do end background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] end diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb index 53966229a2a..fee8cfe2c33 100644 --- a/spec/features/projects/main/rss_spec.rb +++ b/spec/features/projects/main/rss_spec.rb @@ -8,7 +8,7 @@ feature 'Project RSS' do before do user = create(:user) project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 3d253f01484..00d2a27597b 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -9,7 +9,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t project.team << [user, :master] @group_link = create(:project_group_link, project: project, group: group) - login_as(user) + gitlab_sign_in(user) visit namespace_project_settings_members_path(project.namespace, project) end diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index b483ba4c54c..7e71dbc24c0 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -7,7 +7,7 @@ feature 'Projects > Members > Group member cannot leave group project', feature: background do group.add_developer(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb index ff9b6007806..60a5cd9ec63 100644 --- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb @@ -41,7 +41,7 @@ feature 'Projects > Members > Group member cannot request access to his group pr end def login_and_visit_project_page(user) - login_as(user) + gitlab_sign_in(user) visit namespace_project_path(project.namespace, project) end end diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb index 3385e5972ff..76fe6a00dab 100644 --- a/spec/features/projects/members/group_members_spec.rb +++ b/spec/features/projects/members/group_members_spec.rb @@ -13,7 +13,7 @@ feature 'Projects members', feature: true do background do project.team << [developer, :developer] group.add_owner(user) - login_as(user) + gitlab_sign_in(user) end context 'with a group invitee' do diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb index bdeeef57273..66da28b07fe 100644 --- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb +++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb @@ -8,7 +8,7 @@ feature 'Projects > Members > Group requester cannot request access to project', background do group.add_owner(owner) - login_as(user) + gitlab_sign_in(user) visit group_path(group) perform_enqueued_jobs { click_link 'Request Access' } visit namespace_project_path(project.namespace, project) diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb index deea34214fb..9fdd7df0ee5 100644 --- a/spec/features/projects/members/list_spec.rb +++ b/spec/features/projects/members/list_spec.rb @@ -9,7 +9,7 @@ feature 'Project members list', feature: true do let(:project) { create(:project, namespace: group) } background do - login_as(user1) + gitlab_sign_in(user1) group.add_owner(user1) end diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index 1e6f15d8258..21b48b7fdd1 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -10,7 +10,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature: background do project.team << [master, :master] - login_as(master) + gitlab_sign_in(master) end scenario 'expiration date is displayed in the members list' do diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index 143390b71cd..bd445e27243 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -8,7 +8,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do background do project.request_access(user) project.team << [master, :master] - login_as(master) + gitlab_sign_in(master) end scenario 'master can see access requests' do diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb index 9564347e733..703f5dff6b5 100644 --- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb @@ -6,7 +6,7 @@ feature 'Projects > Members > Member cannot request access to his project', feat background do project.team << [member, :developer] - login_as(member) + gitlab_sign_in(member) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 5daa932e4e6..8e1788f7f2a 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -6,7 +6,7 @@ feature 'Projects > Members > Member leaves project', feature: true do background do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb index b26d55c5d5d..70e4bb19c0f 100644 --- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -4,7 +4,7 @@ feature 'Projects > Members > Owner cannot leave project', feature: true do let(:project) { create(:project) } background do - login_as(project.owner) + gitlab_sign_in(project.owner) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb index 4ca9272b9c1..0cd7e3afeda 100644 --- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb @@ -4,7 +4,7 @@ feature 'Projects > Members > Owner cannot request access to his project', featu let(:project) { create(:project) } background do - login_as(project.owner) + gitlab_sign_in(project.owner) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index d428f6fcf22..66d98ef8b90 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -8,7 +8,7 @@ feature 'Projects > Members > Sorting', feature: true do background do create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago) - login_as(master) + gitlab_sign_in(master) end scenario 'sorts alphabetically by default' do diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index ec48a4bd726..081009f2325 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -6,7 +6,7 @@ feature 'Projects > Members > User requests access', feature: true do let(:master) { project.owner } background do - login_as(user) + gitlab_sign_in(user) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 1370ab1c521..6de8855016d 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -18,7 +18,7 @@ feature 'Merge Request button', feature: true do context 'logged in as developer' do before do - login_as(user) + gitlab_sign_in(user) project.team << [user, :developer] end @@ -52,7 +52,7 @@ feature 'Merge Request button', feature: true do context 'logged in as non-member' do before do - login_as(user) + gitlab_sign_in(user) end it 'does not show Create merge request button' do diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb index 7e8a796c55d..f2a2fd0311f 100644 --- a/spec/features/projects/merge_requests/list_spec.rb +++ b/spec/features/projects/merge_requests/list_spec.rb @@ -7,7 +7,7 @@ feature 'Merge Requests List' do background do project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) end scenario 'user does not see create new list button' do diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb index b4fc0edbde8..a02e4118784 100644 --- a/spec/features/projects/milestones/milestone_spec.rb +++ b/spec/features/projects/milestones/milestone_spec.rb @@ -6,7 +6,7 @@ feature 'Project milestone', :feature do let(:milestone) { create(:milestone, project: project) } before do - login_as(user) + gitlab_sign_in(user) end context 'when project has enabled issues' do diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb index da3eaed707a..2350089255d 100644 --- a/spec/features/projects/milestones/milestones_sorting_spec.rb +++ b/spec/features/projects/milestones/milestones_sorting_spec.rb @@ -15,7 +15,7 @@ feature 'Milestones sorting', :feature, :js do due_date: 11.days.from_now, created_at: 1.hour.ago, title: "bbb", project: project) - login_as(user) + gitlab_sign_in(user) end scenario 'visit project milestones and sort by due_date_asc' do diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb new file mode 100644 index 00000000000..7403822c7fb --- /dev/null +++ b/spec/features/projects/milestones/new_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +feature 'Creating a new project milestone', :feature, :js do + let(:user) { create(:user) } + let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + + before do + login_as(user) + visit new_namespace_project_milestone_path(project.namespace, project) + end + + it 'description has autocomplete' do + find('#milestone_description').native.send_keys('') + fill_in 'milestone_description', with: '@' + + expect(page).to have_selector('.atwho-view') + end +end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index b1f9eb15667..37d9a97033b 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -4,7 +4,7 @@ feature "New project", feature: true do let(:user) { create(:admin) } before do - login_as(user) + gitlab_sign_in(user) end context "Visibility level selector" do diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 11793c0f303..e9a3cfb7f60 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -10,7 +10,7 @@ feature 'Pages', feature: true do project.team << [user, role] - login_as(user) + gitlab_sign_in(user) end shared_examples 'no pages deployed' do diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 317949d6b56..dfb973c37e5 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -12,7 +12,7 @@ feature 'Pipeline Schedules', :feature do before do project.add_master(user) - login_as(user) + gitlab_sign_in(user) visit_page end @@ -127,7 +127,7 @@ feature 'Pipeline Schedules', :feature do end it 'shows the pipeline schedule with default ref' do - page.within('.git-revision-dropdown-toggle') do + page.within('.js-target-branch-dropdown') do expect(first('.dropdown-toggle-text').text).to eq('master') end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 36a3ddca6ef..e182995922d 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -7,7 +7,7 @@ describe 'Pipeline', :feature, :js do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) project.team << [user, :developer] end @@ -47,7 +47,9 @@ describe 'Pipeline', :feature, :js do let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) } - before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } + before do + visit namespace_project_pipeline_path(project.namespace, project, pipeline) + end it 'shows the pipeline graph' do expect(page).to have_selector('.pipeline-visualization') @@ -164,7 +166,9 @@ describe 'Pipeline', :feature, :js do it { expect(page).not_to have_content('retried') } context 'when retrying' do - before { find('.js-retry-button').trigger('click') } + before do + find('.js-retry-button').trigger('click') + end it { expect(page).not_to have_content('Retry') } end @@ -174,7 +178,9 @@ describe 'Pipeline', :feature, :js do it { expect(page).not_to have_selector('.ci-canceled') } context 'when canceling' do - before { click_on 'Cancel running' } + before do + click_on 'Cancel running' + end it { expect(page).not_to have_content('Cancel running') } end @@ -226,7 +232,9 @@ describe 'Pipeline', :feature, :js do it { expect(page).not_to have_content('retried') } context 'when retrying' do - before { find('.js-retry-button').trigger('click') } + before do + find('.js-retry-button').trigger('click') + end it { expect(page).not_to have_content('Retry') } end @@ -236,7 +244,9 @@ describe 'Pipeline', :feature, :js do it { expect(page).not_to have_selector('.ci-canceled') } context 'when canceling' do - before { click_on 'Cancel running' } + before do + click_on 'Cancel running' + end it { expect(page).not_to have_content('Cancel running') } end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 05c2bf350f1..d36d073e022 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -7,7 +7,7 @@ describe 'Pipelines', :feature, :js do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) project.team << [user, :developer] end @@ -149,7 +149,9 @@ describe 'Pipelines', :feature, :js do create(:ci_pipeline, :invalid, project: project) end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it 'contains badge that indicates errors' do expect(page).to have_content 'yaml invalid' @@ -171,10 +173,12 @@ describe 'Pipelines', :feature, :js do commands: 'test') end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it 'has a dropdown with play button' do - expect(page).to have_selector('.dropdown-toggle.btn.btn-default .icon-play') + expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') end it 'has link to the manual action' do @@ -204,7 +208,9 @@ describe 'Pipelines', :feature, :js do stage: 'test') end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it 'is cancelable' do expect(page).to have_selector('.js-pipelines-cancel-button') @@ -215,7 +221,9 @@ describe 'Pipelines', :feature, :js do end context 'when canceling' do - before { find('.js-pipelines-cancel-button').trigger('click') } + before do + find('.js-pipelines-cancel-button').trigger('click') + end it 'indicates that pipeline was canceled' do expect(page).not_to have_selector('.js-pipelines-cancel-button') @@ -255,7 +263,9 @@ describe 'Pipelines', :feature, :js do stage: 'test') end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it 'has artifats' do expect(page).to have_selector('.build-artifacts') @@ -284,7 +294,9 @@ describe 'Pipelines', :feature, :js do stage: 'test') end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it { expect(page).not_to have_selector('.build-artifacts') } end @@ -297,7 +309,9 @@ describe 'Pipelines', :feature, :js do stage: 'test') end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it { expect(page).not_to have_selector('.build-artifacts') } end @@ -310,7 +324,9 @@ describe 'Pipelines', :feature, :js do name: 'build') end - before { visit_project_pipelines } + before do + visit_project_pipelines + end it 'should render a mini pipeline graph' do expect(page).to have_selector('.js-mini-pipeline-graph') @@ -437,7 +453,9 @@ describe 'Pipelines', :feature, :js do end context 'with gitlab-ci.yml' do - before { stub_ci_pipeline_to_return_yaml_file } + before do + stub_ci_pipeline_to_return_yaml_file + end it 'creates a new pipeline' do expect { click_on 'Create pipeline' } @@ -448,7 +466,9 @@ describe 'Pipelines', :feature, :js do end context 'without gitlab-ci.yml' do - before { click_on 'Create pipeline' } + before do + click_on 'Create pipeline' + end it { expect(page).to have_content('Missing .gitlab-ci.yml file') } end diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 11dcab4d737..baa38ff8cca 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -7,7 +7,7 @@ describe 'Edit Project Settings', feature: true do let(:project) { create(:empty_project, namespace: user.namespace, path: 'gitlab', name: 'sample') } before do - login_as(user) + gitlab_sign_in(user) end describe 'Project settings section', js: true do @@ -58,8 +58,13 @@ describe 'Edit Project Settings', feature: true do # Not using empty project because we need a repo to exist let(:project) { create(:project, namespace: user.namespace, name: 'gitlabhq') } - before(:context) { TestEnv.clean_test_path } - after(:example) { TestEnv.clean_test_path } + before(:context) do + TestEnv.clean_test_path + end + + after(:example) do + TestEnv.clean_test_path + end specify 'the project is accessible via the new path' do rename_project(project, path: 'bar') @@ -96,9 +101,17 @@ describe 'Edit Project Settings', feature: true do let!(:project) { create(:project, namespace: user.namespace, name: 'gitlabhq') } let!(:group) { create(:group) } - before(:context) { TestEnv.clean_test_path } - before(:example) { group.add_owner(user) } - after(:example) { TestEnv.clean_test_path } + before(:context) do + TestEnv.clean_test_path + end + + before(:example) do + group.add_owner(user) + end + + after(:example) do + TestEnv.clean_test_path + end specify 'the project is accessible via the new path' do transfer_project(project, group) diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb index 04414490571..016a992bdcf 100644 --- a/spec/features/projects/ref_switcher_spec.rb +++ b/spec/features/projects/ref_switcher_spec.rb @@ -6,7 +6,7 @@ feature 'Ref switcher', feature: true, js: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_tree_path(project.namespace, project, 'master') end diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb index c96d87e5708..2ea50e8f672 100644 --- a/spec/features/projects/services/jira_service_spec.rb +++ b/spec/features/projects/services/jira_service_spec.rb @@ -20,7 +20,7 @@ feature 'Setup Jira service', :feature, :js do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_settings_integrations_path(project.namespace, project) end diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb index 1fe82222e59..d87985f1c92 100644 --- a/spec/features/projects/services/mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -9,7 +9,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do before do stub_mattermost_setting(enabled: mattermost_enabled) project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit edit_namespace_project_service_path(project.namespace, project, service) end diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb index c0a4a1e4bf5..50707e6a49f 100644 --- a/spec/features/projects/services/slack_service_spec.rb +++ b/spec/features/projects/services/slack_service_spec.rb @@ -9,7 +9,7 @@ feature 'Projects > Slack service > Setup events', feature: true do service.fields service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, pipeline_channel: 6, wiki_page_channel: 7) project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end scenario 'user can filter events by channel' do diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb index f53b820c460..3fae38c1799 100644 --- a/spec/features/projects/services/slack_slash_command_spec.rb +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -7,7 +7,7 @@ feature 'Slack slash commands', feature: true do background do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit edit_namespace_project_service_path(project.namespace, project, service) end diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb index fbaea14a2be..a59374b37ea 100644 --- a/spec/features/projects/settings/integration_settings_spec.rb +++ b/spec/features/projects/settings/integration_settings_spec.rb @@ -7,7 +7,7 @@ feature 'Integration settings', feature: true do let(:integrations_path) { namespace_project_settings_integrations_path(project.namespace, project) } background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] end diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb index 321af416c91..f2af14ceab2 100644 --- a/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -8,7 +8,7 @@ feature 'Project settings > Merge Requests', feature: true, js: true do background do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'when Merge Request and Pipelines are initially enabled' do diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 035c57eaa47..c33fbd49d21 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -8,7 +8,7 @@ feature "Pipelines settings", feature: true do let(:role) { :developer } background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] visit namespace_project_pipelines_settings_path(project.namespace, project) end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 4cc38c5286e..2956ef73746 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -7,7 +7,7 @@ feature 'Repository settings', feature: true do background do project.team << [user, role] - login_as(user) + gitlab_sign_in(user) end context 'for developer' do diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index fac4506bdf6..18c71dee41b 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -6,7 +6,7 @@ feature 'Visibility settings', feature: true, js: true do context 'as owner' do before do - login_as(user) + gitlab_sign_in(user) visit edit_namespace_project_path(project.namespace, project) end @@ -32,7 +32,7 @@ feature 'Visibility settings', feature: true, js: true do before do project.team << [master_user, :master] - login_as(master_user) + gitlab_sign_in(master_user) visit edit_namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb index 54aa9c66a08..cec79277c33 100644 --- a/spec/features/projects/shortcuts_spec.rb +++ b/spec/features/projects/shortcuts_spec.rb @@ -7,7 +7,7 @@ feature 'Project shortcuts', feature: true do describe 'On a project', js: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 5ac1ca45c74..c75d6dbc307 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -17,7 +17,7 @@ feature 'Create Snippet', :js, feature: true do context 'when a user is authenticated' do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_snippets_path(project.namespace, project) diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb index b844e60e5d5..9e73ba4123b 100644 --- a/spec/features/projects/snippets/show_spec.rb +++ b/spec/features/projects/snippets/show_spec.rb @@ -7,7 +7,7 @@ feature 'Project snippet', :js, feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'Ruby file' do diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb index 18689c17fe9..80dbffaffc7 100644 --- a/spec/features/projects/snippets_spec.rb +++ b/spec/features/projects/snippets_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Project snippets', feature: true do +describe 'Project snippets', :js, feature: true do context 'when the project has snippets' do let(:project) { create(:empty_project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } @@ -26,5 +26,19 @@ describe 'Project snippets', feature: true do expect(page).to have_content(snippets[1].title) end end + + context 'when submitting a note' do + before do + gitlab_sign_in :admin + visit namespace_project_snippet_path(project.namespace, project, snippets[0]) + end + + it 'should have autocomplete' do + find('#note_note').native.send_keys('') + fill_in 'note[note]', with: '@' + + expect(page).to have_selector('.atwho-view') + end + end end end diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb index e88907b8016..63eb97d5a92 100644 --- a/spec/features/projects/sub_group_issuables_spec.rb +++ b/spec/features/projects/sub_group_issuables_spec.rb @@ -8,7 +8,7 @@ describe 'Subgroup Issuables', :feature, :js, :nested_groups do before do project.add_master(user) - login_as user + gitlab_sign_in user end it 'shows the full subgroup title when issues index page is empty' do diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index dd93d25c2c6..ca00a51aa3c 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -23,7 +23,7 @@ feature 'Download buttons in tags page', feature: true do end background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] end diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb index 9bf59c4139c..135584e5bf8 100644 --- a/spec/features/projects/tree/rss_spec.rb +++ b/spec/features/projects/tree/rss_spec.rb @@ -8,7 +8,7 @@ feature 'Project Tree RSS' do before do user = create(:user) project.team << [user, :developer] - login_as(user) + gitlab_sign_in(user) visit path end diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb index aeb7e0b7c33..f375e1215db 100644 --- a/spec/features/projects/user_create_dir_spec.rb +++ b/spec/features/projects/user_create_dir_spec.rb @@ -6,7 +6,7 @@ feature 'New directory creation', feature: true, js: true do given(:project) { create(:project) } background do - login_as(user) + gitlab_sign_in(user) project.team << [user, role] visit namespace_project_tree_path(project.namespace, project, 'master') open_new_directory_modal diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb index 640f1376548..f6a640b90b4 100644 --- a/spec/features/projects/view_on_env_spec.rb +++ b/spec/features/projects/view_on_env_spec.rb @@ -50,7 +50,7 @@ describe 'View on environment', js: true do let(:merge_request) { create(:merge_request, :simple, source_project: project, source_branch: branch_name) } before do - login_as(user) + gitlab_sign_in(user) visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) @@ -66,7 +66,7 @@ describe 'View on environment', js: true do context 'when visiting a comparison for the branch' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_compare_path(project.namespace, project, from: 'master', to: branch_name) @@ -80,7 +80,7 @@ describe 'View on environment', js: true do context 'when visiting a comparison for the commit' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_compare_path(project.namespace, project, from: 'master', to: sha) @@ -94,7 +94,7 @@ describe 'View on environment', js: true do context 'when visiting a blob on the branch' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_blob_path(project.namespace, project, File.join(branch_name, file_path)) @@ -108,7 +108,7 @@ describe 'View on environment', js: true do context 'when visiting a blob on the commit' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_blob_path(project.namespace, project, File.join(sha, file_path)) @@ -122,7 +122,7 @@ describe 'View on environment', js: true do context 'when visiting the commit' do before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_commit_path(project.namespace, project, sha) diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 94f6bb16730..fd6c09943e3 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -16,7 +16,7 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t project.team << [user, :master] WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute - login_as(user) + gitlab_sign_in(user) visit namespace_project_path(project.namespace, project) find('.shortcuts-wiki').trigger('click') diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb index c1f6b0cce3b..ab0ed9b8204 100644 --- a/spec/features/projects/wiki/shortcuts_spec.rb +++ b/spec/features/projects/wiki/shortcuts_spec.rb @@ -8,7 +8,7 @@ feature 'Wiki shortcuts', :feature, :js do end before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_wiki_path(project.namespace, project, wiki_page) end diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index 8912d575878..a477dcf7ee9 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -5,7 +5,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do background do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) visit namespace_project_path(project.namespace, project) find('.shortcuts-wiki').trigger('click') @@ -133,6 +133,22 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do expect(page).to have_content('My awesome wiki!') end end + + scenario 'content has autocomplete', :js do + click_link 'New page' + + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'test-autocomplete' + click_button 'Create page' + end + + page.within '.wiki-form' do + find('#wiki_content').native.send_keys('') + fill_in :wiki_content, with: '@' + end + + expect(page).to have_selector('.atwho-view') + end end end diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb index 95826e7e5be..7d31122af35 100644 --- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb @@ -13,7 +13,7 @@ describe 'Projects > Wiki > User views Git access wiki page', :feature do end before do - login_as(user) + gitlab_sign_in(user) end scenario 'Visit Wiki Page Current Commit' do diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index 86cf520ea80..64a30438681 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -5,11 +5,10 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do background do project.team << [user, :master] - login_as(user) - - visit namespace_project_path(project.namespace, project) WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute - click_link 'Wiki' + gitlab_sign_in(user) + + visit namespace_project_wikis_path(project.namespace, project) end context 'in the user namespace' do @@ -42,6 +41,15 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do expect(page).to have_content('Content can\'t be blank') expect(find('textarea#wiki_content').value).to eq '' end + + scenario 'content has autocomplete', :js do + click_link 'Edit' + + find('#wiki_content').native.send_keys('') + fill_in :wiki_content, with: '@' + + expect(page).to have_selector('.atwho-view') + end end end diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb index c17e06612de..8a88ab247f3 100644 --- a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb @@ -15,7 +15,7 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do background do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) WikiPages::UpdateService.new( project, user, diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb index 20219f3cc9a..36799925167 100644 --- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb @@ -5,7 +5,7 @@ describe 'Projects > Wiki > User views wiki in project page', feature: true do before do project.team << [user, :master] - login_as(user) + gitlab_sign_in(user) end context 'when repository is disabled for project' do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 060e19596ae..7e8a703db93 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -6,7 +6,7 @@ feature 'Project', feature: true do let(:path) { namespace_project_path(project.namespace, project) } before do - login_as(:admin) + gitlab_sign_in(:admin) end it 'parses Markdown' do @@ -39,7 +39,7 @@ feature 'Project', feature: true do let(:project) { create(:empty_project, namespace: user.namespace) } before do - login_with user + gitlab_sign_in user create(:forked_project_link, forked_to_project: project) visit edit_namespace_project_path(project.namespace, project) end @@ -60,7 +60,7 @@ feature 'Project', feature: true do let(:project) { create(:empty_project, namespace: user.namespace, name: 'project1') } before do - login_with(user) + gitlab_sign_in(user) project.team << [user, :master] visit edit_namespace_project_path(project.namespace, project) end @@ -79,7 +79,7 @@ feature 'Project', feature: true do let(:project) { create(:empty_project, namespace: user.namespace) } before do - login_with(user) + gitlab_sign_in(user) project.add_user(user, Gitlab::Access::MASTER) visit namespace_project_path(project.namespace, project) end @@ -98,7 +98,7 @@ feature 'Project', feature: true do context 'on issues page', js: true do before do - login_with(user) + gitlab_sign_in(user) project.add_user(user, Gitlab::Access::MASTER) project2.add_user(user, Gitlab::Access::MASTER) visit namespace_project_issue_path(project.namespace, project, issue) @@ -123,7 +123,7 @@ feature 'Project', feature: true do before do project.team << [user, :master] - login_as user + gitlab_sign_in user visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 667895bffa5..20b8e10f0f7 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -4,7 +4,9 @@ feature 'Protected Branches', feature: true, js: true do let(:user) { create(:user, :admin) } let(:project) { create(:project, :repository) } - before { login_as(user) } + before do + gitlab_sign_in(user) + end def set_protected_branch_name(branch_name) find(".js-protected-branch-select").trigger('click') diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index 66236dbc7fc..73a80692154 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -4,7 +4,9 @@ feature 'Projected Tags', feature: true, js: true do let(:user) { create(:user, :admin) } let(:project) { create(:project, :repository) } - before { login_as(user) } + before do + gitlab_sign_in(user) + end def set_protected_tag_name(tag_name) find(".js-protected-tag-select").click diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb index 39b1c4acf52..12049822753 100644 --- a/spec/features/reportable_note/commit_spec.rb +++ b/spec/features/reportable_note/commit_spec.rb @@ -8,7 +8,7 @@ describe 'Reportable note on commit', :feature, :js do before do project.add_master(user) - login_as user + gitlab_sign_in(user) end context 'a normal note' do diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb index 5f526818994..ca2a7f41496 100644 --- a/spec/features/reportable_note/issue_spec.rb +++ b/spec/features/reportable_note/issue_spec.rb @@ -8,7 +8,7 @@ describe 'Reportable note on issue', :feature, :js do before do project.add_master(user) - login_as user + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb index 6d053d26626..8e75b4af3eb 100644 --- a/spec/features/reportable_note/merge_request_spec.rb +++ b/spec/features/reportable_note/merge_request_spec.rb @@ -7,7 +7,7 @@ describe 'Reportable note on merge request', :feature, :js do before do project.add_master(user) - login_as user + gitlab_sign_in(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb index 3f1e0cf9097..a88990eada0 100644 --- a/spec/features/reportable_note/snippets_spec.rb +++ b/spec/features/reportable_note/snippets_spec.rb @@ -6,7 +6,7 @@ describe 'Reportable note on snippets', :feature, :js do before do project.add_master(user) - login_as user + gitlab_sign_in(user) end describe 'on project snippet' do diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 0e1cc9a0f73..ea18879b4bf 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -4,7 +4,10 @@ describe "Runners" do include GitlabRoutingHelper let(:user) { create(:user) } - before { login_as(user) } + + before do + gitlab_sign_in(user) + end describe "specific runners" do before do @@ -127,7 +130,9 @@ describe "Runners" do end context 'when runner has tags' do - before { runner.update_attribute(:tag_list, ['tag']) } + before do + runner.update_attribute(:tag_list, ['tag']) + end scenario 'user wants to prevent runner from running untagged job' do visit runners_path(project) diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 7834807b1f1..64469f999af 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -9,7 +9,7 @@ describe "Search", feature: true do let!(:issue2) { create(:issue, project: project, author: user) } before do - login_with(user) + gitlab_sign_in(user) project.team << [user, :reporter] visit search_path end @@ -83,7 +83,9 @@ describe "Search", feature: true do let(:project) { create(:project, :repository) } let(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'Bug here') } - before { note.update_attributes(commit_id: 12345678) } + before do + note.update_attributes(commit_id: 12345678) + end it 'finds comment' do visit namespace_project_path(project.namespace, project) diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 2a2655bbdb5..f33406a40a7 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -337,7 +337,9 @@ describe "Internal Project Access", feature: true do subject { namespace_project_jobs_path(project.namespace, project) } context "when allowed for public and internal" do - before { project.update(public_builds: true) } + before do + project.update(public_builds: true) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } @@ -351,7 +353,9 @@ describe "Internal Project Access", feature: true do end context "when disallowed for public and internal" do - before { project.update(public_builds: false) } + before do + project.update(public_builds: false) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } @@ -371,7 +375,9 @@ describe "Internal Project Access", feature: true do subject { namespace_project_job_path(project.namespace, project, build.id) } context "when allowed for public and internal" do - before { project.update(public_builds: true) } + before do + project.update(public_builds: true) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } @@ -385,7 +391,9 @@ describe "Internal Project Access", feature: true do end context "when disallowed for public and internal" do - before { project.update(public_builds: false) } + before do + project.update(public_builds: false) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 35d5163941e..16a1331b2f3 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -157,7 +157,9 @@ describe "Public Project Access", feature: true do subject { namespace_project_jobs_path(project.namespace, project) } context "when allowed for public" do - before { project.update(public_builds: true) } + before do + project.update(public_builds: true) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } @@ -171,7 +173,9 @@ describe "Public Project Access", feature: true do end context "when disallowed for public" do - before { project.update(public_builds: false) } + before do + project.update(public_builds: false) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } @@ -191,7 +195,9 @@ describe "Public Project Access", feature: true do subject { namespace_project_job_path(project.namespace, project, build.id) } context "when allowed for public" do - before { project.update(public_builds: true) } + before do + project.update(public_builds: true) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } @@ -205,7 +211,9 @@ describe "Public Project Access", feature: true do end context "when disallowed for public" do - before { project.update(public_builds: false) } + before do + project.update(public_builds: false) + end it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index d7b6dda4946..5d6d1e79af2 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -3,7 +3,9 @@ require 'spec_helper' feature 'Signup', feature: true do describe 'signup with no errors' do context "when sending confirmation email" do - before { stub_application_setting(send_user_confirmation_email: true) } + before do + stub_application_setting(send_user_confirmation_email: true) + end it 'creates the user account and sends a confirmation email' do user = build(:user) @@ -23,7 +25,9 @@ feature 'Signup', feature: true do end context "when not sending confirmation email" do - before { stub_application_setting(send_user_confirmation_email: false) } + before do + stub_application_setting(send_user_confirmation_email: false) + end it 'creates the user account and goes to dashboard' do user = build(:user) diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb index ddd31ede064..ac5c14ed427 100644 --- a/spec/features/snippets/create_snippet_spec.rb +++ b/spec/features/snippets/create_snippet_spec.rb @@ -4,7 +4,7 @@ feature 'Create Snippet', :js, feature: true do include DropzoneHelper before do - login_as :user + gitlab_sign_in :user visit new_snippet_path end diff --git a/spec/features/snippets/edit_snippet_spec.rb b/spec/features/snippets/edit_snippet_spec.rb index 89ae593db88..860e1b156d6 100644 --- a/spec/features/snippets/edit_snippet_spec.rb +++ b/spec/features/snippets/edit_snippet_spec.rb @@ -10,7 +10,7 @@ feature 'Edit Snippet', :js, feature: true do let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) } before do - login_as(user) + gitlab_sign_in(user) visit edit_snippet_path(snippet) wait_for_requests diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb index fd097fe2e74..ec75817b942 100644 --- a/spec/features/snippets/explore_spec.rb +++ b/spec/features/snippets/explore_spec.rb @@ -6,7 +6,7 @@ feature 'Explore Snippets', feature: true do let!(:private_snippet) { create(:personal_snippet, :private) } scenario 'User should see snippets that are not private' do - login_as create(:user) + gitlab_sign_in create(:user) visit explore_snippets_path expect(page).to have_content(public_snippet.title) @@ -15,7 +15,7 @@ feature 'Explore Snippets', feature: true do end scenario 'External user should see only public snippets' do - login_as create(:user, :external) + gitlab_sign_in create(:user, :external) visit explore_snippets_path expect(page).to have_content(public_snippet.title) diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb index 93382f4c359..3babb1c02cc 100644 --- a/spec/features/snippets/internal_snippet_spec.rb +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -5,7 +5,7 @@ feature 'Internal Snippets', feature: true, js: true do describe 'normal user' do before do - login_as :user + gitlab_sign_in :user end scenario 'sees internal snippets' do diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index 44b0c89fac7..6f7e6f543b2 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -14,7 +14,7 @@ describe 'Comments on personal snippets', :js, feature: true do let!(:other_note) { create(:note_on_personal_snippet) } before do - login_as user + gitlab_sign_in user visit snippet_path(snippet) end @@ -70,6 +70,22 @@ describe 'Comments on personal snippets', :js, feature: true do expect(find('div#notes')).to have_content('This is awesome!') end + + it 'should not have autocomplete' do + wait_for_requests + request_count_before = page.driver.network_traffic.count + + find('#note_note').native.send_keys('') + fill_in 'note[note]', with: '@' + + wait_for_requests + request_count_after = page.driver.network_traffic.count + + # This selector probably won't be in place even if autocomplete was enabled + # but we want to make sure + expect(page).not_to have_selector('.atwho-view') + expect(request_count_before).to eq(request_count_after) + end end context 'when editing a note' do diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb index 146cd3af848..4c21e7321f4 100644 --- a/spec/features/snippets/search_snippets_spec.rb +++ b/spec/features/snippets/search_snippets_spec.rb @@ -5,7 +5,7 @@ feature 'Search Snippets', feature: true do public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle') private_snippet = create(:personal_snippet, :private, title: 'Middle and End') - login_as private_snippet.author + gitlab_sign_in private_snippet.author visit dashboard_snippets_path page.within '.search' do @@ -41,7 +41,7 @@ feature 'Search Snippets', feature: true do CONTENT ) - login_as create(:user) + gitlab_sign_in create(:user) visit dashboard_snippets_path page.within '.search' do diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb index 191c2fb9a22..b971c6aab53 100644 --- a/spec/features/snippets/user_snippets_spec.rb +++ b/spec/features/snippets/user_snippets_spec.rb @@ -7,7 +7,7 @@ feature 'User Snippets', feature: true do let!(:private_snippet) { create(:personal_snippet, :private, author: author, title: "This is a private snippet") } background do - login_as author + gitlab_sign_in author visit dashboard_snippets_path end diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb index af25eebed13..52db3583dac 100644 --- a/spec/features/tags/master_creates_tag_spec.rb +++ b/spec/features/tags/master_creates_tag_spec.rb @@ -6,62 +6,80 @@ feature 'Master creates tag', feature: true do before do project.team << [user, :master] - login_with(user) - visit namespace_project_tags_path(project.namespace, project) + gitlab_sign_in(user) end - scenario 'with an invalid name displays an error' do - create_tag_in_form(tag: 'v 1.0', ref: 'master') + context 'from tag list' do + before do + visit namespace_project_tags_path(project.namespace, project) + end - expect(page).to have_content 'Tag name invalid' - end + scenario 'with an invalid name displays an error' do + create_tag_in_form(tag: 'v 1.0', ref: 'master') - scenario 'with an invalid reference displays an error' do - create_tag_in_form(tag: 'v2.0', ref: 'foo') + expect(page).to have_content 'Tag name invalid' + end - expect(page).to have_content 'Target foo is invalid' - end + scenario 'with an invalid reference displays an error' do + create_tag_in_form(tag: 'v2.0', ref: 'foo') - scenario 'that already exists displays an error' do - create_tag_in_form(tag: 'v1.1.0', ref: 'master') + expect(page).to have_content 'Target foo is invalid' + end - expect(page).to have_content 'Tag v1.1.0 already exists' - end + scenario 'that already exists displays an error' do + create_tag_in_form(tag: 'v1.1.0', ref: 'master') + + expect(page).to have_content 'Tag v1.1.0 already exists' + end - scenario 'with multiline message displays the message in a <pre> block' do - create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world") + scenario 'with multiline message displays the message in a <pre> block' do + create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world") - expect(current_path).to eq( - namespace_project_tag_path(project.namespace, project, 'v3.0')) - expect(page).to have_content 'v3.0' - page.within 'pre.wrap' do - expect(page).to have_content "Awesome tag message\n\n- hello\n- world" + expect(current_path).to eq( + namespace_project_tag_path(project.namespace, project, 'v3.0')) + expect(page).to have_content 'v3.0' + page.within 'pre.wrap' do + expect(page).to have_content "Awesome tag message\n\n- hello\n- world" + end end - end - scenario 'with multiline release notes parses the release note as Markdown' do - create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world") + scenario 'with multiline release notes parses the release note as Markdown' do + create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world") - expect(current_path).to eq( - namespace_project_tag_path(project.namespace, project, 'v4.0')) - expect(page).to have_content 'v4.0' - page.within '.description' do - expect(page).to have_content 'Awesome release notes' - expect(page).to have_selector('ul li', count: 2) + expect(current_path).to eq( + namespace_project_tag_path(project.namespace, project, 'v4.0')) + expect(page).to have_content 'v4.0' + page.within '.description' do + expect(page).to have_content 'Awesome release notes' + expect(page).to have_selector('ul li', count: 2) + end + end + + scenario 'opens dropdown for ref', js: true do + click_link 'New tag' + ref_row = find('.form-group:nth-of-type(2) .col-sm-10') + page.within ref_row do + ref_input = find('[name="ref"]', visible: false) + expect(ref_input.value).to eq 'master' + expect(find('.dropdown-toggle-text')).to have_content 'master' + + find('.js-branch-select').trigger('click') + + expect(find('.dropdown-menu')).to have_content 'empty-branch' + end end end - scenario 'opens dropdown for ref', js: true do - click_link 'New tag' - ref_row = find('.form-group:nth-of-type(2) .col-sm-10') - page.within ref_row do - ref_input = find('[name="ref"]', visible: false) - expect(ref_input.value).to eq 'master' - expect(find('.dropdown-toggle-text')).to have_content 'master' + context 'from new tag page' do + before do + visit new_namespace_project_tag_path(project.namespace, project) + end - find('.js-branch-select').trigger('click') + it 'description has autocomplete', :js do + find('#release_description').native.send_keys('') + fill_in 'release_description', with: '@' - expect(find('.dropdown-menu')).to have_content 'empty-branch' + expect(page).to have_selector('.atwho-view') end end diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index ccfafe6db7d..58f33e954f9 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -6,7 +6,7 @@ feature 'Master deletes tag', feature: true do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit namespace_project_tags_path(project.namespace, project) end diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb index 6b5b3122f72..18c8c4c511c 100644 --- a/spec/features/tags/master_updates_tag_spec.rb +++ b/spec/features/tags/master_updates_tag_spec.rb @@ -6,7 +6,7 @@ feature 'Master updates tag', feature: true do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) visit namespace_project_tags_path(project.namespace, project) end @@ -24,6 +24,17 @@ feature 'Master updates tag', feature: true do expect(page).to have_content 'v1.1.0' expect(page).to have_content 'Awesome release notes' end + + scenario 'description has autocomplete', :js do + page.within(first('.content-list .controls')) do + click_link 'Edit release notes' + end + + find('#release_description').native.send_keys('') + fill_in 'release_description', with: '@' + + expect(page).to have_selector('.atwho-view') + end end context 'from a specific tag page' do diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb index 922ac15a2eb..3c21fa06694 100644 --- a/spec/features/tags/master_views_tags_spec.rb +++ b/spec/features/tags/master_views_tags_spec.rb @@ -5,7 +5,7 @@ feature 'Master views tags', feature: true do before do project.team << [user, :master] - login_with(user) + gitlab_sign_in(user) end context 'when project has no tags' do diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 563e65d3cc5..51b1b8e2328 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -144,7 +144,9 @@ feature 'Task Lists', feature: true do describe 'nested tasks', js: true do let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) } - before { visit_issue(project, issue) } + before do + visit_issue(project, issue) + end it 'renders' do expect(page).to have_selector('ul.task-list', count: 2) diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/todos/target_state_spec.rb index 32fa88a2b21..99b70b3d3a1 100644 --- a/spec/features/todos/target_state_spec.rb +++ b/spec/features/todos/target_state_spec.rb @@ -6,7 +6,7 @@ feature 'Todo target states', feature: true do let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } before do - login_as user + gitlab_sign_in user end scenario 'on a closed issue todo has closed label' do diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb index bbfa4e08379..032fb479076 100644 --- a/spec/features/todos/todos_filtering_spec.rb +++ b/spec/features/todos/todos_filtering_spec.rb @@ -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] - login_as(user_1) + gitlab_sign_in(user_1) visit dashboard_todos_path end diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/todos/todos_sorting_spec.rb index 4d5bd476301..498bbac6d14 100644 --- a/spec/features/todos/todos_sorting_spec.rb +++ b/spec/features/todos/todos_sorting_spec.rb @@ -8,7 +8,9 @@ describe "Dashboard > User sorts todos", feature: true do let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) } - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end context 'sort options' do let(:issue_1) { create(:issue, title: 'issue_1', project: project) } @@ -30,7 +32,7 @@ describe "Dashboard > User sorts todos", feature: true do issue_2.labels << label_3 issue_1.labels << label_2 - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path end @@ -81,7 +83,7 @@ describe "Dashboard > User sorts todos", feature: true do create(:todo, user: user, project: project, target: issue_2) create(:todo, user: user, project: project, target: merge_request_1) - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index feb2fe8a7d1..41b32bdedc3 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -9,7 +9,7 @@ describe 'Dashboard Todos', feature: true do describe 'GET /dashboard/todos' do context 'User does not have todos' do before do - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path end it 'shows "All done" message' do @@ -20,7 +20,7 @@ describe 'Dashboard Todos', feature: true do context 'User has a todo', js: true do before do create(:todo, :mentioned, user: user, project: project, target: issue, author: author) - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path end @@ -101,7 +101,7 @@ describe 'Dashboard Todos', feature: true do context 'User created todos for themself' do before do - login_as(user) + gitlab_sign_in(user) end context 'issue assigned todo' do @@ -179,7 +179,7 @@ describe 'Dashboard Todos', feature: true do context 'User has done todos', js: true do before do create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author) - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path(state: :done) end @@ -217,7 +217,7 @@ describe 'Dashboard Todos', feature: true do 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) - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path end @@ -233,7 +233,7 @@ describe 'Dashboard Todos', feature: true do # Create just enough records to cause us to paginate create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author) - login_as(user) + gitlab_sign_in(user) end it 'is paginated' do @@ -321,7 +321,7 @@ describe 'Dashboard Todos', feature: true 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) - login_as(user) + gitlab_sign_in(user) visit dashboard_todos_path end @@ -337,7 +337,7 @@ describe 'Dashboard Todos', feature: true do let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } before do - login_as user + gitlab_sign_in user visit dashboard_todos_path end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index c1ae6db00c6..bc3c9917a42 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -5,13 +5,15 @@ feature 'Triggers', feature: true, js: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest_user) { create(:user) } - before { login_as(user) } before do + gitlab_sign_in(user) + @project = create(:empty_project) @project.team << [user, :master] @project.team << [user2, :master] @project.team << [guest_user, :guest] + visit namespace_project_settings_ci_cd_path(@project.namespace, @project) end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index 2fed8067042..f3662cb184f 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do - before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) } + before do + allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) + end def manage_two_factor_authentication click_on 'Manage two-factor authentication' @@ -23,12 +25,14 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do let(:user) { create(:user) } before do - login_as(user) + gitlab_sign_in(user) user.update_attribute(:otp_required_for_login, true) end describe 'when 2FA via OTP is disabled' do - before { user.update_attribute(:otp_required_for_login, false) } + before do + user.update_attribute(:otp_required_for_login, false) + end it 'does not allow registering a new device' do visit profile_account_path @@ -89,10 +93,10 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do manage_two_factor_authentication u2f_device = register_u2f_device expect(page).to have_content('Your U2F device was registered') - logout + gitlab_sign_out # Second user - user = login_as(:user) + user = gitlab_sign_in(:user) user.update_attribute(:otp_required_for_login, true) visit profile_account_path manage_two_factor_authentication @@ -143,18 +147,18 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do before do # Register and logout - login_as(user) + gitlab_sign_in(user) user.update_attribute(:otp_required_for_login, true) visit profile_account_path manage_two_factor_authentication @u2f_device = register_u2f_device - logout + gitlab_sign_out end describe "when 2FA via OTP is disabled" do it "allows logging in with the U2F device" do user.update_attribute(:otp_required_for_login, false) - login_with(user) + gitlab_sign_in(user) @u2f_device.respond_to_u2f_authentication @@ -166,7 +170,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do describe "when 2FA via OTP is enabled" do it "allows logging in with the U2F device" do user.update_attribute(:otp_required_for_login, true) - login_with(user) + gitlab_sign_in(user) @u2f_device.respond_to_u2f_authentication @@ -176,7 +180,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do end it 'persists remember_me value via hidden field' do - login_with(user, remember: true) + gitlab_sign_in(user, remember: true) @u2f_device.respond_to_u2f_authentication expect(page).to have_content('We heard back from your U2F device') @@ -191,15 +195,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do describe "but not the current user" do it "does not allow logging in with that particular device" do # Register current user with the different U2F device - current_user = login_as(:user) + current_user = gitlab_sign_in(:user) current_user.update_attribute(:otp_required_for_login, true) visit profile_account_path manage_two_factor_authentication register_u2f_device(name: 'My other device') - logout + gitlab_sign_out # Try authenticating user with the old U2F device - login_as(current_user) + gitlab_sign_in(current_user) @u2f_device.respond_to_u2f_authentication expect(page).to have_content('We heard back from your U2F device') expect(page).to have_content('Authentication via U2F device failed') @@ -209,15 +213,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do describe "and also the current user" do it "allows logging in with that particular device" do # Register current user with the same U2F device - current_user = login_as(:user) + current_user = gitlab_sign_in(:user) current_user.update_attribute(:otp_required_for_login, true) visit profile_account_path manage_two_factor_authentication register_u2f_device(@u2f_device) - logout + gitlab_sign_out # Try authenticating user with the same U2F device - login_as(current_user) + gitlab_sign_in(current_user) @u2f_device.respond_to_u2f_authentication expect(page).to have_content('We heard back from your U2F device') @@ -229,7 +233,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do describe "when a given U2F device has not been registered" do it "does not allow logging in with that particular device" do unregistered_device = FakeU2fDevice.new(page, 'My device') - login_as(user) + gitlab_sign_in(user) unregistered_device.respond_to_u2f_authentication expect(page).to have_content('We heard back from your U2F device') @@ -240,7 +244,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do describe "when more than one device has been registered by the same user" do it "allows logging in with either device" do # Register first device - user = login_as(:user) + user = gitlab_sign_in(:user) user.update_attribute(:otp_required_for_login, true) visit profile_two_factor_auth_path expect(page).to have_content("Your U2F device needs to be set up.") @@ -250,17 +254,17 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do visit profile_two_factor_auth_path expect(page).to have_content("Your U2F device needs to be set up.") second_device = register_u2f_device(name: 'My other device') - logout + gitlab_sign_out # Authenticate as both devices [first_device, second_device].each do |device| - login_as(user) + gitlab_sign_in(user) device.respond_to_u2f_authentication expect(page).to have_content('We heard back from your U2F device') expect(page).to have_css('.sign-out-link', visible: false) - logout + gitlab_sign_out end end end @@ -269,7 +273,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do let(:user) { create(:user) } before do - user = login_as(:user) + user = gitlab_sign_in(:user) user.update_attribute(:otp_required_for_login, true) visit profile_account_path manage_two_factor_authentication @@ -296,15 +300,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do before do # Register and logout - login_as(user) + gitlab_sign_in(user) user.update_attribute(:otp_required_for_login, true) visit profile_account_path end describe 'when no u2f device is registered' do before do - logout - login_with(user) + gitlab_sign_out + gitlab_sign_in(user) end it 'shows the fallback otp code UI' do @@ -316,8 +320,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do before do manage_two_factor_authentication @u2f_device = register_u2f_device - logout - login_with(user) + gitlab_sign_out + gitlab_sign_in(user) end it 'provides a button that shows the fallback otp code UI' do diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index 8509551ce4a..352f8ba70ac 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -56,7 +56,9 @@ describe 'Unsubscribe links', feature: true do end context 'when logged in' do - before { login_as(recipient) } + before do + sign_in(recipient) + end it 'unsubscribes from the issue when visiting the link from the email body' do visit body_link diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index d9d6f2e2382..797b7b3d50d 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -5,7 +5,7 @@ feature 'User uploads avatar to group', feature: true do user = create(:user) group = create(:group) group.add_owner(user) - login_as(user) + gitlab_sign_in(user) visit edit_group_path(group) attach_file( diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index eb8dbd76aab..a3f8027f4da 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' feature 'User uploads avatar to profile', feature: true do scenario 'they see their new avatar' do user = create(:user) - login_as(user) + gitlab_sign_in(user) visit profile_path attach_file( diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 9332d3b88d2..77a1012762d 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -8,7 +8,7 @@ feature 'User uploads file to note', feature: true do let(:issue) { create(:issue, project: project, author: user) } before do - login_as(user) + gitlab_sign_in(user) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb index b84f834ff1e..7538a6e4a04 100644 --- a/spec/features/user_callout_spec.rb +++ b/spec/features/user_callout_spec.rb @@ -6,7 +6,7 @@ describe 'User Callouts', js: true do let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') } before do - login_as(user) + gitlab_sign_in(user) project.team << [user, :master] end diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb index c2842255b86..1bd7e038939 100644 --- a/spec/features/user_can_display_performance_bar_spec.rb +++ b/spec/features/user_can_display_performance_bar_spec.rb @@ -57,7 +57,7 @@ describe 'User can display performacne bar', :js do context 'when user is logged-in' do before do - login_as :user + gitlab_sign_in(create(:user)) visit root_path end diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb index 67ce4b44464..377b1a0148f 100644 --- a/spec/features/users/projects_spec.rb +++ b/spec/features/users/projects_spec.rb @@ -8,7 +8,7 @@ describe 'Projects tab on a user profile', :feature, :js do before do allow(Project).to receive(:default_per_page).and_return(1) - login_as(user) + gitlab_sign_in(user) visit user_path(user) diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb index dbd5f66b55e..797b317a9bb 100644 --- a/spec/features/users/rss_spec.rb +++ b/spec/features/users/rss_spec.rb @@ -5,7 +5,7 @@ feature 'User RSS' do context 'when signed in' do before do - login_as(create(:user)) + gitlab_sign_in(create(:user)) visit path end diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb index 2e388115633..74c5cbd7887 100644 --- a/spec/features/users/snippets_spec.rb +++ b/spec/features/users/snippets_spec.rb @@ -24,7 +24,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do let!(:other_snippet) { create(:snippet, :public) } it 'contains only internal and public snippets of a user when a user is logged in' do - login_as(:user) + gitlab_sign_in(:user) visit user_path(user) page.within('.user-profile-nav') { click_link 'Snippets' } wait_for_requests diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index fbe078bd136..84af13d3e49 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -24,7 +24,7 @@ feature 'Users', feature: true, js: true do user.reload expect(user.reset_password_token).not_to be_nil - login_with(user) + gitlab_sign_in(user) expect(current_path).to eq root_path user.reload @@ -45,7 +45,9 @@ feature 'Users', feature: true, js: true do end describe 'redirect alias routes' do - before { user } + before do + expect(user).to be_persisted + end scenario '/u/user1 redirects to user page' do visit '/u/user1' diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index d0c982919db..85085bf305a 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -6,7 +6,7 @@ describe 'Project variables', js: true do let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } before do - login_as(user) + gitlab_sign_in(user) project.team << [user, :master] project.variables << variable diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index 5b3591550c1..9e70cccc3c4 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -38,28 +38,79 @@ describe GroupsFinder do end end - context 'subgroups' do + context 'subgroups', :nested_groups do let!(:parent_group) { create(:group, :public) } let!(:public_subgroup) { create(:group, :public, parent: parent_group) } let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) } let!(:private_subgroup) { create(:group, :private, parent: parent_group) } context 'without a user' do - it 'only returns public subgroups' do - expect(described_class.new(nil, parent: parent_group).execute).to contain_exactly(public_subgroup) + it 'only returns parent and public subgroups' do + expect(described_class.new(nil).execute).to contain_exactly(parent_group, public_subgroup) end end context 'with a user' do - it 'returns public and internal subgroups' do - expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup) + subject { described_class.new(user).execute } + + it 'returns parent, public, and internal subgroups' do + is_expected.to contain_exactly(parent_group, public_subgroup, internal_subgroup) end context 'being member' do - it 'returns public subgroups, internal subgroups, and private subgroups user is member of' do + it 'returns parent, public subgroups, internal subgroups, and private subgroups user is member of' do private_subgroup.add_guest(user) - expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup, private_subgroup) + is_expected.to contain_exactly(parent_group, public_subgroup, internal_subgroup, private_subgroup) + end + end + + context 'parent group private' do + before do + parent_group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + end + + context 'being member of parent group' do + it 'returns all subgroups' do + parent_group.add_guest(user) + + is_expected.to contain_exactly(parent_group, public_subgroup, internal_subgroup, private_subgroup) + end + end + + context 'authorized to private project' do + context 'project one level deep' do + let!(:subproject) { create(:empty_project, :private, namespace: private_subgroup) } + before do + subproject.add_guest(user) + end + + it 'includes the subgroup of the project' do + is_expected.to include(private_subgroup) + end + + it 'does not include private subgroups deeper down' do + subsubgroup = create(:group, :private, parent: private_subgroup) + + is_expected.not_to include(subsubgroup) + end + end + + context 'project two levels deep' do + let!(:private_subsubgroup) { create(:group, :private, parent: private_subgroup) } + let!(:subsubproject) { create(:empty_project, :private, namespace: private_subsubgroup) } + before do + subsubproject.add_guest(user) + end + + it 'returns all the ancestor groups' do + is_expected.to include(private_subsubgroup, private_subgroup, parent_group) + end + + it 'returns the groups for a given parent' do + expect(described_class.new(user, parent: parent_group).execute).to include(private_subgroup) + end + end end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 96151689359..8ace1fb5751 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -7,9 +7,9 @@ describe IssuesFinder do set(:project2) { create(:empty_project) } set(:milestone) { create(:milestone, project: project1) } set(:label) { create(:label, project: project2) } - set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab') } + set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) } set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') } - set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki') } + set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) } describe '#execute' do set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') } @@ -148,7 +148,9 @@ describe IssuesFinder do let(:params) { { label_name: [label.title, label2.title].join(',') } } let(:label2) { create(:label, project: project2) } - before { create(:label_link, label: label2, target: issue2) } + before do + create(:label_link, label: label2, target: issue2) + end it 'returns the unique issues with any of those labels' do expect(issues).to contain_exactly(issue2) @@ -213,6 +215,24 @@ describe IssuesFinder do end end + context 'filtering by created_at' do + context 'through created_after' do + let(:params) { { created_after: issue3.created_at } } + + it 'returns issues created on or after the given date' do + expect(issues).to contain_exactly(issue3) + end + end + + context 'through created_before' do + let(:params) { { created_before: issue1.created_at + 1.second } } + + it 'returns issues created on or before the given date' do + expect(issues).to contain_exactly(issue1) + end + end + end + context 'when the user is unauthorized' do let(:search_user) { nil } diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 58b7cd5e098..5eb26de6c92 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -46,5 +46,47 @@ describe MergeRequestsFinder do expect(merge_requests).to contain_exactly(merge_request1) end + + context 'with created_after and created_before params' do + let(:project4) { create(:empty_project, forked_from_project: project1) } + + let!(:new_merge_request) do + create(:merge_request, + :simple, + author: user, + created_at: 1.week.from_now, + source_project: project4, + target_project: project1) + end + + let!(:old_merge_request) do + create(:merge_request, + :simple, + author: user, + created_at: 1.week.ago, + source_project: project4, + target_project: project4) + end + + before do + project4.add_master(user) + end + + it 'filters by created_after' do + params = { project_id: project1.id, created_after: new_merge_request.created_at } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(new_merge_request) + end + + it 'filters by created_before' do + params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(old_merge_request) + end + end end end diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb index fd92664ca24..3f22b3a253d 100644 --- a/spec/finders/personal_access_tokens_finder_spec.rb +++ b/spec/finders/personal_access_tokens_finder_spec.rb @@ -25,49 +25,65 @@ describe PersonalAccessTokensFinder do end describe 'without impersonation' do - before { params[:impersonation] = false } + before do + params[:impersonation] = false + end it { is_expected.to contain_exactly(active_personal_access_token, revoked_personal_access_token, expired_personal_access_token) } describe 'with active state' do - before { params[:state] = 'active' } + before do + params[:state] = 'active' + end it { is_expected.to contain_exactly(active_personal_access_token) } end describe 'with inactive state' do - before { params[:state] = 'inactive' } + before do + params[:state] = 'inactive' + end it { is_expected.to contain_exactly(revoked_personal_access_token, expired_personal_access_token) } end end describe 'with impersonation' do - before { params[:impersonation] = true } + before do + params[:impersonation] = true + end it { is_expected.to contain_exactly(active_impersonation_token, revoked_impersonation_token, expired_impersonation_token) } describe 'with active state' do - before { params[:state] = 'active' } + before do + params[:state] = 'active' + end it { is_expected.to contain_exactly(active_impersonation_token) } end describe 'with inactive state' do - before { params[:state] = 'inactive' } + before do + params[:state] = 'inactive' + end it { is_expected.to contain_exactly(revoked_impersonation_token, expired_impersonation_token) } end end describe 'with active state' do - before { params[:state] = 'active' } + before do + params[:state] = 'active' + end it { is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token) } end describe 'with inactive state' do - before { params[:state] = 'inactive' } + before do + params[:state] = 'inactive' + end it do is_expected.to contain_exactly(expired_personal_access_token, revoked_personal_access_token, @@ -81,7 +97,9 @@ describe PersonalAccessTokensFinder do it { is_expected.to eq(active_personal_access_token) } describe 'with impersonation' do - before { params[:impersonation] = true } + before do + params[:impersonation] = true + end it { is_expected.to be_nil } end @@ -93,7 +111,9 @@ describe PersonalAccessTokensFinder do it { is_expected.to eq(active_personal_access_token) } describe 'with impersonation' do - before { params[:impersonation] = true } + before do + params[:impersonation] = true + end it { is_expected.to be_nil } end @@ -109,7 +129,9 @@ describe PersonalAccessTokensFinder do let!(:other_user_expired_impersonation_token) { create(:personal_access_token, :expired, :impersonation, user: user2) } let!(:other_user_revoked_impersonation_token) { create(:personal_access_token, :revoked, :impersonation, user: user2) } - before { params[:user] = user } + before do + params[:user] = user + end it do is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token, @@ -118,49 +140,65 @@ describe PersonalAccessTokensFinder do end describe 'without impersonation' do - before { params[:impersonation] = false } + before do + params[:impersonation] = false + end it { is_expected.to contain_exactly(active_personal_access_token, revoked_personal_access_token, expired_personal_access_token) } describe 'with active state' do - before { params[:state] = 'active' } + before do + params[:state] = 'active' + end it { is_expected.to contain_exactly(active_personal_access_token) } end describe 'with inactive state' do - before { params[:state] = 'inactive' } + before do + params[:state] = 'inactive' + end it { is_expected.to contain_exactly(revoked_personal_access_token, expired_personal_access_token) } end end describe 'with impersonation' do - before { params[:impersonation] = true } + before do + params[:impersonation] = true + end it { is_expected.to contain_exactly(active_impersonation_token, revoked_impersonation_token, expired_impersonation_token) } describe 'with active state' do - before { params[:state] = 'active' } + before do + params[:state] = 'active' + end it { is_expected.to contain_exactly(active_impersonation_token) } end describe 'with inactive state' do - before { params[:state] = 'inactive' } + before do + params[:state] = 'inactive' + end it { is_expected.to contain_exactly(revoked_impersonation_token, expired_impersonation_token) } end end describe 'with active state' do - before { params[:state] = 'active' } + before do + params[:state] = 'active' + end it { is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token) } end describe 'with inactive state' do - before { params[:state] = 'inactive' } + before do + params[:state] = 'inactive' + end it do is_expected.to contain_exactly(expired_personal_access_token, revoked_personal_access_token, @@ -174,7 +212,9 @@ describe PersonalAccessTokensFinder do it { is_expected.to eq(active_personal_access_token) } describe 'with impersonation' do - before { params[:impersonation] = true } + before do + params[:impersonation] = true + end it { is_expected.to be_nil } end @@ -186,7 +226,9 @@ describe PersonalAccessTokensFinder do it { is_expected.to eq(active_personal_access_token) } describe 'with impersonation' do - before { params[:impersonation] = true } + before do + params[:impersonation] = true + end it { is_expected.to be_nil } end diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index e0e17af681a..304b0fb67fb 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -32,7 +32,9 @@ describe PersonalProjectsFinder do end context 'external' do - before { current_user.update_attributes(external: true) } + before do + current_user.update_attributes(external: true) + end it { is_expected.to eq([private_project, public_project]) } end diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index f2aeda241c1..2b19cda35b0 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -170,7 +170,7 @@ describe PipelinesFinder do context 'when order_by and sort are specified' do context 'when order_by user_id' do let(:params) { { order_by: 'user_id', sort: 'asc' } } - let!(:pipelines) { create_list(:ci_pipeline, 2, project: project, user: create(:user)) } + let!(:pipelines) { Array.new(2) { create(:ci_pipeline, project: project, user: create(:user)) } } it 'sorts as user_id: :asc' do is_expected.to match_array(pipelines) diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index f7e7e733cf7..8be447418b0 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -6,7 +6,9 @@ describe TodosFinder do let(:project) { create(:empty_project) } let(:finder) { described_class } - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end describe '#sort' do context 'by date' do diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 49df91b236f..cc7f889b927 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -257,4 +257,24 @@ describe ApplicationHelper do it { expect(helper.active_when(true)).to eq('active') } it { expect(helper.active_when(false)).to eq(nil) } end + + describe '#support_url' do + context 'when alternate support url is specified' do + let(:alternate_url) { 'http://company.example.com/getting-help' } + + before do + allow(current_application_settings).to receive(:help_page_support_url) { alternate_url } + end + + it 'returns the alternate support url' do + expect(helper.support_url).to eq(alternate_url) + end + end + + context 'when alternate support url is not specified' do + it 'builds the support url from the promo_url' do + expect(helper.support_url).to eq(helper.promo_url + '/getting-help/') + end + end + end end diff --git a/spec/helpers/blame_helper_spec.rb b/spec/helpers/blame_helper_spec.rb new file mode 100644 index 00000000000..b4368516d83 --- /dev/null +++ b/spec/helpers/blame_helper_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe BlameHelper do + describe '#get_age_map_start_date' do + let(:dates) do + [Time.zone.local(2014, 3, 17, 0, 0, 0), + Time.zone.local(2011, 11, 2, 0, 0, 0), + Time.zone.local(2015, 7, 9, 0, 0, 0), + Time.zone.local(2013, 2, 24, 0, 0, 0), + Time.zone.local(2010, 9, 22, 0, 0, 0)] + end + let(:blame_groups) do + [ + { commit: double(committed_date: dates[0]) }, + { commit: double(committed_date: dates[1]) }, + { commit: double(committed_date: dates[2]) } + ] + end + + it 'returns the earliest date from a blame group' do + project = double(created_at: dates[3]) + + duration = helper.age_map_duration(blame_groups, project) + + expect(duration[:started_days_ago]).to eq((duration[:now] - dates[1]).to_i / 1.day) + end + + it 'returns the earliest date from a project' do + project = double(created_at: dates[4]) + + duration = helper.age_map_duration(blame_groups, project) + + expect(duration[:started_days_ago]).to eq((duration[:now] - dates[4]).to_i / 1.day) + end + end + + describe '#age_map_class' do + let(:dates) do + [Time.zone.local(2014, 3, 17, 0, 0, 0)] + end + let(:blame_groups) do + [ + { commit: double(committed_date: dates[0]) } + ] + end + let(:duration) do + project = double(created_at: dates[0]) + helper.age_map_duration(blame_groups, project) + end + + it 'returns blame-commit-age-9 when oldest' do + expect(helper.age_map_class(dates[0], duration)).to eq 'blame-commit-age-9' + end + + it 'returns blame-commit-age-0 class when newest' do + expect(helper.age_map_class(duration[:now], duration)).to eq 'blame-commit-age-0' + end + end +end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index a74615e07f9..0d909e6e140 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -8,7 +8,7 @@ describe DiffHelper do let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.raw_diffs } let(:diff) { diffs.first } - let(:diff_refs) { [commit.parent, commit] } + let(:diff_refs) { commit.diff_refs } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } describe 'diff_view' do @@ -148,12 +148,21 @@ describe DiffHelper do it 'puts comments on added lines' do left = Gitlab::Diff::Line.new('\\nonewline', 'old-nonewline', 3, 3, 3) - right = Gitlab::Diff::Line.new('new line', 'add', 3, 3, 3) + right = Gitlab::Diff::Line.new('new line', 'new', 3, 3, 3) result = helper.parallel_diff_discussions(left, right, diff_file) expect(result).to eq([nil, 'comment']) end + + it 'puts comments on unchanged lines' do + left = Gitlab::Diff::Line.new('unchanged line', nil, 3, 3, 3) + right = Gitlab::Diff::Line.new('unchanged line', nil, 3, 3, 3) + + result = helper.parallel_diff_discussions(left, right, diff_file) + + expect(result).to eq(['comment', nil]) + end end describe "#diff_match_line" do @@ -207,4 +216,41 @@ describe DiffHelper do expect(output).not_to have_css 'td:nth-child(3)' end end + + context 'viewer related' do + let(:viewer) { diff_file.simple_viewer } + + before do + assign(:project, project) + end + + describe '#diff_render_error_reason' do + context 'for error :too_large' do + before do + expect(viewer).to receive(:render_error).and_return(:too_large) + end + + it 'returns an error message' do + expect(helper.diff_render_error_reason(viewer)).to eq('it is too large') + end + end + + context 'for error :server_side_but_stored_externally' do + before do + expect(viewer).to receive(:render_error).and_return(:server_side_but_stored_externally) + expect(diff_file).to receive(:external_storage).and_return(:lfs) + end + + it 'returns an error message' do + expect(helper.diff_render_error_reason(viewer)).to eq('it is stored in LFS') + end + end + end + + describe '#diff_render_error_options' do + it 'includes a "view the blob" link' do + expect(helper.diff_render_error_options(viewer)).to include(/view the blob/) + end + end + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index a695621b87a..9a4086725d2 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -250,7 +250,9 @@ describe ProjectsHelper do end context "when project is private" do - before { project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + before do + project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end it "shows only allowed options" do helper.instance_variable_set(:@project, project) @@ -300,4 +302,37 @@ describe ProjectsHelper do expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private') end end + + describe '#get_project_nav_tabs' do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + before do + allow(helper).to receive(:can?) { true } + end + + subject do + helper.send(:get_project_nav_tabs, project, user) + end + + context 'when builds feature is enabled' do + before do + allow(project).to receive(:builds_enabled?).and_return(true) + end + + it "does include pipelines tab" do + is_expected.to include(:pipelines) + end + end + + context 'when builds feature is disabled' do + before do + allow(project).to receive(:builds_enabled?).and_return(false) + end + + it "do not include pipelines tab" do + is_expected.not_to include(:pipelines) + end + end + end end diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/8_metrics_spec.rb index 570754621f3..a507d7f7f2b 100644 --- a/spec/initializers/8_metrics_spec.rb +++ b/spec/initializers/8_metrics_spec.rb @@ -7,6 +7,7 @@ describe 'instrument_classes', lib: true do before do allow(config).to receive(:instrument_method) allow(config).to receive(:instrument_methods) + allow(config).to receive(:instrument_instance_method) allow(config).to receive(:instrument_instance_methods) end diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js index a27dc48b3fd..93dc60d59fe 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js @@ -1,15 +1,6 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; (() => { - // TODO: remove this hack! - // PhantomJS causes spyOn to panic because replaceState isn't "writable" - let phantomjs; - try { - phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; - } catch (err) { - phantomjs = false; - } - describe('Linked Tabs', () => { preloadFixtures('static/linked_tabs.html.raw'); @@ -19,9 +10,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('when is initialized', () => { beforeEach(() => { - if (!phantomjs) { - spyOn(window.history, 'replaceState').and.callFake(function () {}); - } + spyOn(window.history, 'replaceState').and.callFake(function () {}); }); it('should activate the tab correspondent to the given action', () => { @@ -47,7 +36,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('on click', () => { it('should change the url according to the clicked tab', () => { - const historySpy = !phantomjs && spyOn(history, 'replaceState').and.callFake(() => {}); + const historySpy = spyOn(history, 'replaceState').and.callFake(() => {}); const linkedTabs = new LinkedTabs({ action: 'show', diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js index 44a4386b250..ace95000468 100644 --- a/spec/javascripts/commits_spec.js +++ b/spec/javascripts/commits_spec.js @@ -5,15 +5,6 @@ import '~/pager'; import '~/commits'; (() => { - // TODO: remove this hack! - // PhantomJS causes spyOn to panic because replaceState isn't "writable" - let phantomjs; - try { - phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; - } catch (err) { - phantomjs = false; - } - describe('Commits List', () => { beforeEach(() => { setFixtures(` @@ -61,9 +52,7 @@ import '~/commits'; CommitsList.init(25); CommitsList.searchField.val(''); - if (!phantomjs) { - spyOn(history, 'replaceState').and.stub(); - } + spyOn(history, 'replaceState').and.stub(); ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => { req.success({ data: '<li>Result</li>', diff --git a/spec/javascripts/groups/mock_data.js b/spec/javascripts/groups/mock_data.js index 1c0ec7a97d0..b3f5d791b89 100644 --- a/spec/javascripts/groups/mock_data.js +++ b/spec/javascripts/groups/mock_data.js @@ -6,6 +6,7 @@ const group1 = { visibility: 'public', avatar_url: null, web_url: 'http://localhost:3000/groups/level1', + group_path: '/level1', full_name: 'level1', full_path: 'level1', parent_id: null, @@ -28,6 +29,7 @@ const group14 = { visibility: 'public', avatar_url: null, web_url: 'http://localhost:3000/groups/level1/level2/level3/level4', + group_path: '/level1/level2/level3/level4', full_name: 'level1 / level2 / level3 / level4', full_path: 'level1/level2/level3/level4', parent_id: 1127, @@ -49,6 +51,7 @@ const group2 = { visibility: 'public', avatar_url: null, web_url: 'http://localhost:3000/groups/devops', + group_path: '/devops', full_name: 'devops', full_path: 'devops', parent_id: null, @@ -70,6 +73,7 @@ const group21 = { visibility: 'public', avatar_url: null, web_url: 'http://localhost:3000/groups/devops/chef', + group_path: '/devops/chef', full_name: 'devops / chef', full_path: 'devops/chef', parent_id: 1119, diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 59c006aa0af..2ccc4f16192 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -126,7 +126,7 @@ describe('Issuable output', () => { describe('updateIssuable', () => { it('fetches new data after update', (done) => { - spyOn(vm.service, 'getData'); + spyOn(vm.service, 'getData').and.callThrough(); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ json() { diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 7b910282cc8..9916d2c1e21 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -12,15 +12,6 @@ import '~/notes'; import 'vendor/jquery.scrollTo'; (function () { - // TODO: remove this hack! - // PhantomJS causes spyOn to panic because replaceState isn't "writable" - var phantomjs; - try { - phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; - } catch (err) { - phantomjs = false; - } - describe('MergeRequestTabs', function () { var stubLocation = {}; var setLocation = function (stubs) { @@ -37,11 +28,9 @@ import 'vendor/jquery.scrollTo'; this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation }); setLocation(); - if (!phantomjs) { - this.spies = { - history: spyOn(window.history, 'replaceState').and.callFake(function () {}) - }; - } + this.spies = { + history: spyOn(window.history, 'replaceState').and.callFake(function () {}) + }; }); afterEach(function () { @@ -208,11 +197,9 @@ import 'vendor/jquery.scrollTo'; pathname: '/foo/bar/merge_requests/1' }); newState = this.subject('commits'); - if (!phantomjs) { - expect(this.spies.history).toHaveBeenCalledWith({ - url: newState - }, document.title, newState); - } + expect(this.spies.history).toHaveBeenCalledWith({ + url: newState + }, document.title, newState); }); it('treats "show" like "notes"', function () { diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index bfd8b8648a6..b272f43a166 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -126,6 +126,7 @@ import '~/notes'; const deferred = $.Deferred(); spyOn($, 'ajax').and.returnValue(deferred.promise()); spyOn(this.notes, 'revertNoteEditForm'); + spyOn(this.notes, 'setupNewNote'); $('.js-comment-button').click(); deferred.resolve(noteEntity); @@ -136,6 +137,46 @@ import '~/notes'; this.notes.updateNote(updatedNote, $targetNote); expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote); + expect(this.notes.setupNewNote).toHaveBeenCalled(); + }); + }); + + describe('updateNoteTargetSelector', () => { + const hash = 'note_foo'; + let $note; + + beforeEach(() => { + $note = $(`<div id="${hash}"></div>`); + spyOn($note, 'filter').and.callThrough(); + spyOn($note, 'toggleClass').and.callThrough(); + }); + + it('sets target when hash matches', () => { + spyOn(gl.utils, 'getLocationHash'); + gl.utils.getLocationHash.and.returnValue(hash); + + Notes.updateNoteTargetSelector($note); + + expect($note.filter).toHaveBeenCalledWith(`#${hash}`); + expect($note.toggleClass).toHaveBeenCalledWith('target', true); + }); + + it('unsets target when hash does not match', () => { + spyOn(gl.utils, 'getLocationHash'); + gl.utils.getLocationHash.and.returnValue('note_doesnotexist'); + + Notes.updateNoteTargetSelector($note); + + expect($note.toggleClass).toHaveBeenCalledWith('target', false); + }); + + it('unsets target when there is not a hash fragment anymore', () => { + spyOn(gl.utils, 'getLocationHash'); + gl.utils.getLocationHash.and.returnValue(null); + + Notes.updateNoteTargetSelector($note); + + expect($note.toggleClass).toHaveBeenCalledWith('target', null); }); }); @@ -189,9 +230,13 @@ import '~/notes'; Notes.isUpdatedNote.and.returnValue(true); const $note = $('<div>'); $notesList.find.and.returnValue($note); + const $newNote = $(note.html); + Notes.animateUpdateNote.and.returnValue($newNote); + Notes.prototype.renderNote.call(notes, note, null, $notesList); expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note); + expect(notes.setupNewNote).toHaveBeenCalledWith($newNote); }); describe('while editing', () => { @@ -378,6 +423,23 @@ import '~/notes'; }); }); + describe('putEditFormInPlace', () => { + it('should call gl.GLForm with GFM parameter passed through', () => { + spyOn(gl, 'GLForm'); + + const $el = jasmine.createSpyObj('$form', ['find', 'closest']); + $el.find.and.returnValue($('<div>')); + $el.closest.and.returnValue($('<div>')); + + Notes.prototype.putEditFormInPlace.call({ + getEditFormSelector: () => '', + enableGFM: true + }, $el); + + expect(gl.GLForm).toHaveBeenCalledWith(jasmine.any(Object), true); + }); + }); + describe('postComment & updateComment', () => { const sampleComment = 'foo'; const updatedComment = 'bar'; @@ -533,46 +595,46 @@ import '~/notes'; }); }); - describe('hasSlashCommands', () => { + describe('hasQuickActions', () => { beforeEach(() => { this.notes = new Notes('', []); }); - it('should return true when comment begins with a slash command', () => { + it('should return true when comment begins with a quick action', () => { const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; - const hasSlashCommands = this.notes.hasSlashCommands(sampleComment); + const hasQuickActions = this.notes.hasQuickActions(sampleComment); - expect(hasSlashCommands).toBeTruthy(); + expect(hasQuickActions).toBeTruthy(); }); - it('should return false when comment does NOT begin with a slash command', () => { + it('should return false when comment does NOT begin with a quick action', () => { const sampleComment = 'Hey, /unassign Merging this'; - const hasSlashCommands = this.notes.hasSlashCommands(sampleComment); + const hasQuickActions = this.notes.hasQuickActions(sampleComment); - expect(hasSlashCommands).toBeFalsy(); + expect(hasQuickActions).toBeFalsy(); }); - it('should return false when comment does NOT have any slash commands', () => { + it('should return false when comment does NOT have any quick actions', () => { const sampleComment = 'Looking good, Awesome!'; - const hasSlashCommands = this.notes.hasSlashCommands(sampleComment); + const hasQuickActions = this.notes.hasQuickActions(sampleComment); - expect(hasSlashCommands).toBeFalsy(); + expect(hasQuickActions).toBeFalsy(); }); }); - describe('stripSlashCommands', () => { - it('should strip slash commands from the comment which begins with a slash command', () => { + describe('stripQuickActions', () => { + it('should strip quick actions from the comment which begins with a quick action', () => { this.notes = new Notes(); const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; - const stripedComment = this.notes.stripSlashCommands(sampleComment); + const stripedComment = this.notes.stripQuickActions(sampleComment); expect(stripedComment).toBe(''); }); - it('should strip slash commands from the comment but leaves plain comment if it is present', () => { + it('should strip quick actions from the comment but leaves plain comment if it is present', () => { this.notes = new Notes(); const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this'; - const stripedComment = this.notes.stripSlashCommands(sampleComment); + const stripedComment = this.notes.stripQuickActions(sampleComment); expect(stripedComment).toBe('Merging this'); }); @@ -580,14 +642,14 @@ import '~/notes'; it('should NOT strip string that has slashes within', () => { this.notes = new Notes(); const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1'; - const stripedComment = this.notes.stripSlashCommands(sampleComment); + const stripedComment = this.notes.stripQuickActions(sampleComment); expect(stripedComment).toBe(sampleComment); }); }); - describe('getSlashCommandDescription', () => { - const availableSlashCommands = [ + describe('getQuickActionDescription', () => { + const availableQuickActions = [ { name: 'close', description: 'Close this issue', params: [] }, { name: 'title', description: 'Change title', params: [{}] }, { name: 'estimate', description: 'Set time estimate', params: [{}] } @@ -597,19 +659,19 @@ import '~/notes'; this.notes = new Notes(); }); - it('should return executing slash command description when note has single slash command', () => { + it('should return executing quick action description when note has single quick action', () => { const sampleComment = '/close'; - expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying command to close this issue'); + expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying command to close this issue'); }); - it('should return generic multiple slash command description when note has multiple slash commands', () => { + it('should return generic multiple quick action description when note has multiple quick actions', () => { const sampleComment = '/close\n/title [Duplicate] Issue foobar'; - expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying multiple commands'); + expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying multiple commands'); }); - it('should return generic slash command description when available slash commands list is not populated', () => { + it('should return generic quick action description when available quick actions list is not populated', () => { const sampleComment = '/close\n/title [Duplicate] Issue foobar'; - expect(this.notes.getSlashCommandDescription(sampleComment)).toBe('Applying command'); + expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command'); }); }); diff --git a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js index 845b371d90c..56c57d94798 100644 --- a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js +++ b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js @@ -95,7 +95,7 @@ describe('Interval Pattern Input Component', function () { describe('User Actions', function () { beforeEach(function () { - // For an unknown reason, Phantom.js doesn't trigger click events + // For an unknown reason, some browsers do not propagate click events // on radio buttons in a way Vue can register. So, we have to mount // to a fixture. setFixtures('<div id="my-mount"></div>'); diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index 594a9856d2c..3c4b20a5f06 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -19,7 +19,7 @@ describe('Pipeline Url Component', () => { }, }).$mount(); - expect(component.$el.tagName).toEqual('TD'); + expect(component.$el.getAttribute('class')).toContain('table-section'); }); it('should render a link the provided path and id', () => { @@ -94,7 +94,7 @@ describe('Pipeline Url Component', () => { }, }).$mount(); - expect(component.$el.querySelector('.js-pipeline-url-lastest').textContent).toContain('latest'); + expect(component.$el.querySelector('.js-pipeline-url-latest').textContent).toContain('latest'); expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain('yaml invalid'); expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck'); }); diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js index c89dacbcd93..8a58b77f1e3 100644 --- a/spec/javascripts/pipelines/pipelines_actions_spec.js +++ b/spec/javascripts/pipelines/pipelines_actions_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import pipelinesActionsComp from '~/pipelines/components/pipelines_actions'; +import pipelinesActionsComp from '~/pipelines/components/pipelines_actions.vue'; describe('Pipelines Actions dropdown', () => { let component; diff --git a/spec/javascripts/pipelines/pipelines_artifacts_spec.js b/spec/javascripts/pipelines/pipelines_artifacts_spec.js index 9724b63d957..acb67d0ec21 100644 --- a/spec/javascripts/pipelines/pipelines_artifacts_spec.js +++ b/spec/javascripts/pipelines/pipelines_artifacts_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import artifactsComp from '~/pipelines/components/pipelines_artifacts'; +import artifactsComp from '~/pipelines/components/pipelines_artifacts.vue'; describe('Pipelines Artifacts dropdown', () => { let component; diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index 3a56156358b..c30abb2edb0 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import pipelinesComp from '~/pipelines/pipelines'; +import pipelinesComp from '~/pipelines/components/pipelines.vue'; import Store from '~/pipelines/stores/pipelines_store'; describe('Pipelines', () => { diff --git a/spec/javascripts/pipelines/time_ago_spec.js b/spec/javascripts/pipelines/time_ago_spec.js index 24581e8c672..42b34c82f89 100644 --- a/spec/javascripts/pipelines/time_ago_spec.js +++ b/spec/javascripts/pipelines/time_ago_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import timeAgo from '~/pipelines/components/time_ago'; +import timeAgo from '~/pipelines/components/time_ago.vue'; describe('Timeago component', () => { let TimeAgo; diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js index 81ac589f4e6..c08a73851be 100644 --- a/spec/javascripts/pipelines_spec.js +++ b/spec/javascripts/pipelines_spec.js @@ -1,10 +1,5 @@ import Pipelines from '~/pipelines'; -// Fix for phantomJS -if (!Element.prototype.matches && Element.prototype.webkitMatchesSelector) { - Element.prototype.matches = Element.prototype.webkitMatchesSelector; -} - describe('Pipelines', () => { preloadFixtures('static/pipeline_graph.html.raw'); diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index 3dba2e817ff..cc336180ff7 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -1,4 +1,3 @@ -/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, max-len */ /* global Project */ import 'select2/select2'; @@ -7,47 +6,52 @@ import '~/api'; import '~/project_select'; import '~/project'; -(function() { - describe('Project Title', function() { - preloadFixtures('issues/open-issue.html.raw'); - loadJSONFixtures('projects.json'); +describe('Project Title', () => { + preloadFixtures('issues/open-issue.html.raw'); + loadJSONFixtures('projects.json'); - beforeEach(function() { - loadFixtures('issues/open-issue.html.raw'); + beforeEach(() => { + loadFixtures('issues/open-issue.html.raw'); - window.gon = {}; - window.gon.api_version = 'v3'; + window.gon = {}; + window.gon.api_version = 'v3'; - return this.project = new Project(); - }); + // eslint-disable-next-line no-new + new Project(); + }); - describe('project list', function() { - var fakeAjaxResponse = function fakeAjaxResponse(req) { - var d; - expect(req.url).toBe('/api/v3/projects.json?simple=true'); - expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20, membership: true }); - d = $.Deferred(); - d.resolve(this.projects_data); - return d.promise(); - }; - - beforeEach((function(_this) { - return function() { - _this.projects_data = getJSONFixture('projects.json'); - return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this)); - }; - })(this)); - it('toggles dropdown', function() { - var menu = $('.js-dropdown-menu-projects'); - $('.js-projects-dropdown-toggle').click(); - expect(menu).toHaveClass('open'); - menu.find('.dropdown-menu-close-icon').click(); - expect(menu).not.toHaveClass('open'); + describe('project list', () => { + let reqUrl; + let reqData; + + beforeEach(() => { + const fakeResponseData = getJSONFixture('projects.json'); + spyOn(jQuery, 'ajax').and.callFake((req) => { + const def = $.Deferred(); + reqUrl = req.url; + reqData = req.data; + def.resolve(fakeResponseData); + return def.promise(); }); }); - afterEach(() => { - window.gon = {}; + it('toggles dropdown', () => { + const $menu = $('.js-dropdown-menu-projects'); + $('.js-projects-dropdown-toggle').click(); + expect($menu).toHaveClass('open'); + expect(reqUrl).toBe('/api/v3/projects.json?simple=true'); + expect(reqData).toEqual({ + search: '', + order_by: 'last_activity_at', + per_page: 20, + membership: true, + }); + $menu.find('.dropdown-menu-close-icon').click(); + expect($menu).not.toHaveClass('open'); }); }); -}).call(window); + + afterEach(() => { + window.gon = {}; + }); +}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 2c34402576b..729db02e06c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -16,6 +16,14 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = 'http://test.host'; window.gon = window.gon || {}; +// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row +// because it appears to lock up the thread that communicates to Karma's socket +// This async beforeEach gets called on every spec and releases the JS thread long +// enough for the socket to continue to communicate. +// The downside is that it creates a minor performance penalty in the time it takes +// to run our unit tests. +beforeEach(done => done()); // eslint-disable-line jasmine/no-global-setup + // render all of our tests const testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(function (path) { diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js index f6e0c3dfb74..6a44c54cdee 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js @@ -1,20 +1,31 @@ import Vue from 'vue'; -import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links'; +import MRWidgetRelatedLinks from '~/vue_merge_request_widget/components/mr_widget_related_links'; -const createComponent = (data) => { - const Component = Vue.extend(relatedLinksComponent); +describe('MRWidgetRelatedLinks', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(MRWidgetRelatedLinks); + vm = new Component({ + el: document.createElement('div'), + propsData: { + isMerged: false, + relatedLinks: {}, + }, + }); + }); - return new Component({ - el: document.createElement('div'), - propsData: data, + afterEach(() => { + vm.$destroy(); }); -}; -describe('MRWidgetRelatedLinks', () => { describe('props', () => { it('should have props', () => { - const { relatedLinks } = relatedLinksComponent.props; + const { isMerged, relatedLinks } = MRWidgetRelatedLinks.props; + expect(isMerged).toBeDefined(); + expect(isMerged.type).toBe(Boolean); + expect(isMerged.required).toBeTruthy(); expect(relatedLinks).toBeDefined(); expect(relatedLinks.type instanceof Object).toBeTruthy(); expect(relatedLinks.required).toBeTruthy(); @@ -22,16 +33,38 @@ describe('MRWidgetRelatedLinks', () => { }); describe('computed', () => { + describe('closingText', () => { + const dummyIssueLabel = 'dummy label'; + + beforeEach(() => { + spyOn(vm, 'issueLabel').and.returnValue(dummyIssueLabel); + }); + + it('outputs text for closing issues', () => { + vm.isMerged = false; + + const text = vm.closingText; + + expect(text).toBe(`Closes ${dummyIssueLabel}`); + }); + + it('outputs text for closed issues', () => { + vm.isMerged = true; + + const text = vm.closingText; + + expect(text).toBe(`Closed ${dummyIssueLabel}`); + }); + }); + describe('hasLinks', () => { it('should return correct value when we have links reference', () => { - const data = { - relatedLinks: { - closing: '/foo', - mentioned: '/foo', - assignToMe: '/foo', - }, + vm.relatedLinks = { + closing: '/foo', + mentioned: '/foo', + assignToMe: '/foo', }; - const vm = createComponent(data); + expect(vm.hasLinks).toBeTruthy(); vm.relatedLinks.closing = null; @@ -44,95 +77,160 @@ describe('MRWidgetRelatedLinks', () => { expect(vm.hasLinks).toBeFalsy(); }); }); - }); - describe('methods', () => { - const data = { - relatedLinks: { - closing: '<a href="#">#23</a> and <a>#42</a>', - mentioned: '<a href="#">#7</a>', - }, - }; - const vm = createComponent(data); + describe('mentionedText', () => { + it('outputs text for one mentioned issue before merging', () => { + vm.isMerged = false; + spyOn(vm, 'hasMultipleIssues').and.returnValue(false); - describe('hasMultipleIssues', () => { - it('should return true if the given text has multiple issues', () => { - expect(vm.hasMultipleIssues(data.relatedLinks.closing)).toBeTruthy(); + const text = vm.mentionedText; + + expect(text).toBe('is mentioned but will not be closed'); }); - it('should return false if the given text has one issue', () => { - expect(vm.hasMultipleIssues(data.relatedLinks.mentioned)).toBeFalsy(); + it('outputs text for one mentioned issue after merging', () => { + vm.isMerged = true; + spyOn(vm, 'hasMultipleIssues').and.returnValue(false); + + const text = vm.mentionedText; + + expect(text).toBe('is mentioned but was not closed'); + }); + + it('outputs text for multiple mentioned issue before merging', () => { + vm.isMerged = false; + spyOn(vm, 'hasMultipleIssues').and.returnValue(true); + + const text = vm.mentionedText; + + expect(text).toBe('are mentioned but will not be closed'); + }); + + it('outputs text for multiple mentioned issue after merging', () => { + vm.isMerged = true; + spyOn(vm, 'hasMultipleIssues').and.returnValue(true); + + const text = vm.mentionedText; + + expect(text).toBe('are mentioned but were not closed'); }); }); + }); - describe('issueLabel', () => { + describe('methods', () => { + const relatedLinks = { + oneIssue: '<a href="#">#7</a>', + twoIssues: '<a href="#">#23</a> and <a>#42</a>', + threeIssues: '<a href="#">#1</a>, <a>#2</a>, and <a>#3</a>', + }; + + beforeEach(() => { + vm.relatedLinks = relatedLinks; + }); + + describe('hasMultipleIssues', () => { it('should return true if the given text has multiple issues', () => { - expect(vm.issueLabel('closing')).toEqual('issues'); + expect(vm.hasMultipleIssues(relatedLinks.twoIssues)).toBeTruthy(); + expect(vm.hasMultipleIssues(relatedLinks.threeIssues)).toBeTruthy(); }); it('should return false if the given text has one issue', () => { - expect(vm.issueLabel('mentioned')).toEqual('issue'); + expect(vm.hasMultipleIssues(relatedLinks.oneIssue)).toBeFalsy(); }); }); - describe('verbLabel', () => { + describe('issueLabel', () => { it('should return true if the given text has multiple issues', () => { - expect(vm.verbLabel('closing')).toEqual('are'); + expect(vm.issueLabel('twoIssues')).toEqual('issues'); + expect(vm.issueLabel('threeIssues')).toEqual('issues'); }); it('should return false if the given text has one issue', () => { - expect(vm.verbLabel('mentioned')).toEqual('is'); + expect(vm.issueLabel('oneIssue')).toEqual('issue'); }); }); }); describe('template', () => { - it('should have only have closing issues text', () => { - const vm = createComponent({ - relatedLinks: { - closing: '<a href="#">#23</a> and <a>#42</a>', - }, - }); - const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); - - expect(content).toContain('Closes issues #23 and #42'); - expect(content).not.toContain('mentioned'); - }); + it('should have only have closing issues text', (done) => { + vm.relatedLinks = { + closing: '<a href="#">#23</a> and <a>#42</a>', + }; - it('should have only have mentioned issues text', () => { - const vm = createComponent({ - relatedLinks: { - mentioned: '<a href="#">#7</a>', - }, - }); + Vue.nextTick() + .then(() => { + const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); - expect(vm.$el.innerText).toContain('issue #7'); - expect(vm.$el.innerText).toContain('is mentioned but will not be closed.'); - expect(vm.$el.innerText).not.toContain('Closes'); + expect(content).toContain('Closes issues #23 and #42'); + expect(content).not.toContain('mentioned'); + }) + .then(done) + .catch(done.fail); }); - it('should have closing and mentioned issues at the same time', () => { - const vm = createComponent({ - relatedLinks: { - closing: '<a href="#">#7</a>', - mentioned: '<a href="#">#23</a> and <a>#42</a>', - }, - }); - const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); + it('should have only have mentioned issues text', (done) => { + vm.relatedLinks = { + mentioned: '<a href="#">#7</a>', + }; + + Vue.nextTick() + .then(() => { + expect(vm.$el.innerText).toContain('issue #7'); + expect(vm.$el.innerText).toContain('is mentioned but will not be closed'); + expect(vm.$el.innerText).not.toContain('Closes'); + }) + .then(done) + .catch(done.fail); + }); - expect(content).toContain('Closes issue #7.'); - expect(content).toContain('issues #23 and #42'); - expect(content).toContain('are mentioned but will not be closed.'); + it('should have closing and mentioned issues at the same time', (done) => { + vm.relatedLinks = { + closing: '<a href="#">#7</a>', + mentioned: '<a href="#">#23</a> and <a>#42</a>', + }; + + Vue.nextTick() + .then(() => { + const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); + + expect(content).toContain('Closes issue #7.'); + expect(content).toContain('issues #23 and #42'); + expect(content).toContain('are mentioned but will not be closed'); + }) + .then(done) + .catch(done.fail); }); - it('should have assing issues link', () => { - const vm = createComponent({ - relatedLinks: { - assignToMe: '<a href="#">Assign yourself to these issues</a>', - }, - }); + it('should have assing issues link', (done) => { + vm.relatedLinks = { + assignToMe: '<a href="#">Assign yourself to these issues</a>', + }; + + Vue.nextTick() + .then(() => { + expect(vm.$el.innerText).toContain('Assign yourself to these issues'); + }) + .then(done) + .catch(done.fail); + }); - expect(vm.$el.innerText).toContain('Assign yourself to these issues'); + it('should use different wording after merging', (done) => { + vm.isMerged = true; + vm.relatedLinks = { + closing: '<a href="#">#7</a>', + mentioned: '<a href="#">#23</a> and <a>#42</a>', + }; + + Vue.nextTick() + .then(() => { + const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); + + expect(content).toContain('Closed issue #7.'); + expect(content).toContain('issues #23 and #42'); + expect(content).toContain('are mentioned but were not closed'); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 3a0c50b750f..425dff89439 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -48,12 +48,13 @@ describe('mrWidgetOptions', () => { }); describe('shouldRenderMergeHelp', () => { - it('should return false for the initial merged state', () => { + it('should return false after merging', () => { + vm.mr.isMerged = true; expect(vm.shouldRenderMergeHelp).toBeFalsy(); }); - it('should return true for a state which requires help widget', () => { - vm.mr.state = 'conflicts'; + it('should return true before merging', () => { + vm.mr.isMerged = false; expect(vm.shouldRenderMergeHelp).toBeTruthy(); }); }); diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js index 56dd0198ae2..71285866302 100644 --- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js @@ -18,5 +18,17 @@ describe('MergeRequestStore', () => { store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress }); expect(store.hasSHAChanged).toBe(false); }); + + it('sets isMerged to true for merged state', () => { + store.setData({ ...mockData, state: 'merged' }); + + expect(store.isMerged).toBe(true); + }); + + it('sets isMerged to false for readyToMerge state', () => { + store.setData({ ...mockData, state: 'readyToMerge' }); + + expect(store.isMerged).toBe(false); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js index 540245fe71e..1c3188cdda2 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/javascripts/vue_shared/components/commit_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import commitComp from '~/vue_shared/components/commit'; +import commitComp from '~/vue_shared/components/commit.vue'; describe('Commit component', () => { let props; diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index e28639f12f3..b4553acb341 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -87,7 +87,7 @@ describe('Header CI Component', () => { vm.actions[0].isLoading = true; Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toEqual(''); + expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); done(); }); }); diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js index 67419cfcbea..9475ee28a03 100644 --- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js +++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import tableRowComp from '~/vue_shared/components/pipelines_table_row'; +import tableRowComp from '~/vue_shared/components/pipelines_table_row.vue'; describe('Pipelines Table Row', () => { const jsonFixtureName = 'pipelines/pipelines.json'; @@ -34,7 +34,7 @@ describe('Pipelines Table Row', () => { it('should render a table row', () => { component = buildComponent(pipeline); - expect(component.$el).toEqual('TR'); + expect(component.$el.getAttribute('class')).toContain('gl-responsive-table-row'); }); describe('status column', () => { @@ -44,13 +44,13 @@ describe('Pipelines Table Row', () => { it('should render a pipeline link', () => { expect( - component.$el.querySelector('td.commit-link a').getAttribute('href'), + component.$el.querySelector('.table-section.commit-link a').getAttribute('href'), ).toEqual(pipeline.path); }); it('should render status text', () => { expect( - component.$el.querySelector('td.commit-link a').textContent, + component.$el.querySelector('.table-section.commit-link a').textContent, ).toContain(pipeline.details.status.text); }); }); @@ -62,24 +62,24 @@ describe('Pipelines Table Row', () => { it('should render a pipeline link', () => { expect( - component.$el.querySelector('td:nth-child(2) a').getAttribute('href'), + component.$el.querySelector('.table-section:nth-child(2) a').getAttribute('href'), ).toEqual(pipeline.path); }); it('should render pipeline ID', () => { expect( - component.$el.querySelector('td:nth-child(2) a > span').textContent, + component.$el.querySelector('.table-section:nth-child(2) a > span').textContent, ).toEqual(`#${pipeline.id}`); }); describe('when a user is provided', () => { it('should render user information', () => { expect( - component.$el.querySelector('td:nth-child(2) a:nth-child(3)').getAttribute('href'), + component.$el.querySelector('.table-section:nth-child(2) a:nth-child(3)').getAttribute('href'), ).toEqual(pipeline.user.path); expect( - component.$el.querySelector('td:nth-child(2) img').getAttribute('data-original-title'), + component.$el.querySelector('.table-section:nth-child(2) img').getAttribute('data-original-title'), ).toEqual(pipeline.user.name); }); }); @@ -142,7 +142,7 @@ describe('Pipelines Table Row', () => { it('should render an icon for each stage', () => { expect( - component.$el.querySelectorAll('td:nth-child(4) .js-builds-dropdown-button').length, + component.$el.querySelectorAll('.table-section:nth-child(4) .js-builds-dropdown-button').length, ).toEqual(pipeline.details.stages.length); }); }); @@ -154,7 +154,7 @@ describe('Pipelines Table Row', () => { it('should render the provided actions', () => { expect( - component.$el.querySelectorAll('td:nth-child(6) ul li').length, + component.$el.querySelectorAll('.table-section:nth-child(6) ul li').length, ).toEqual(pipeline.details.manual_actions.length); }); }); diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_spec.js index 6cc178b8f1d..4c35d702004 100644 --- a/spec/javascripts/vue_shared/components/pipelines_table_spec.js +++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import pipelinesTableComp from '~/vue_shared/components/pipelines_table'; +import pipelinesTableComp from '~/vue_shared/components/pipelines_table.vue'; import '~/lib/utils/datetime_utility'; describe('Pipelines Table', () => { @@ -32,16 +32,14 @@ describe('Pipelines Table', () => { }); it('should render a table', () => { - expect(component.$el).toEqual('TABLE'); + expect(component.$el.getAttribute('class')).toContain('ci-table'); }); it('should render table head with correct columns', () => { - expect(component.$el.querySelector('th.js-pipeline-status').textContent).toEqual('Status'); - expect(component.$el.querySelector('th.js-pipeline-info').textContent).toEqual('Pipeline'); - expect(component.$el.querySelector('th.js-pipeline-commit').textContent).toEqual('Commit'); - expect(component.$el.querySelector('th.js-pipeline-stages').textContent).toEqual('Stages'); - expect(component.$el.querySelector('th.js-pipeline-date').textContent).toEqual(''); - expect(component.$el.querySelector('th.js-pipeline-actions').textContent).toEqual(''); + expect(component.$el.querySelector('.table-section.js-pipeline-status').textContent.trim()).toEqual('Status'); + expect(component.$el.querySelector('.table-section.js-pipeline-info').textContent.trim()).toEqual('Pipeline'); + expect(component.$el.querySelector('.table-section.js-pipeline-commit').textContent.trim()).toEqual('Commit'); + expect(component.$el.querySelector('.table-section.js-pipeline-stages').textContent.trim()).toEqual('Stages'); }); }); @@ -53,7 +51,7 @@ describe('Pipelines Table', () => { service: {}, }, }).$mount(); - expect(component.$el.querySelectorAll('tbody tr').length).toEqual(0); + expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(0); }); }); @@ -67,7 +65,7 @@ describe('Pipelines Table', () => { }, }).$mount(); - expect(component.$el.querySelectorAll('tbody tr').length).toEqual(1); + expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(1); }); }); }); diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js index bf28019ef24..f3b4adc0b70 100644 --- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js +++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js @@ -22,7 +22,7 @@ describe('Time ago with tooltip component', () => { }).$mount(); expect(vm.$el.tagName).toEqual('TIME'); - expect(vm.$el.classList.contains('js-timeago')).toEqual(true); + expect(vm.$el.classList.contains('js-vue-timeago')).toEqual(true); expect( vm.$el.getAttribute('data-original-title'), ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z')); @@ -44,17 +44,6 @@ describe('Time ago with tooltip component', () => { expect(vm.$el.getAttribute('data-placement')).toEqual('bottom'); }); - it('should render short format class', () => { - vm = new TimeagoTooltip({ - propsData: { - time: '2017-05-08T14:57:39.781Z', - shortFormat: true, - }, - }).$mount(); - - expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true); - }); - it('should render provided html class', () => { vm = new TimeagoTooltip({ propsData: { diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index fbf7a461fa5..76cefe112fb 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -82,7 +82,9 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do context 'with RequestStore enabled' do let(:reference_filter) { HTML::Pipeline.new([described_class]) } - before { allow(RequestStore).to receive(:active?).and_return(true) } + before do + allow(RequestStore).to receive(:active?).and_return(true) + end it 'queries the collection on the first call' do expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb index 7c4a0f32c7b..97504aebed5 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -39,7 +39,9 @@ describe Banzai::Filter::RedactorFilter, lib: true do end context 'valid projects' do - before { allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(true) } + before do + allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(true) + end it 'allows permitted Project references' do user = create(:user) @@ -54,7 +56,9 @@ describe Banzai::Filter::RedactorFilter, lib: true do end context 'invalid projects' do - before { allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(false) } + before do + allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(false) + end it 'removes unpermitted references' do user = create(:user) diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index f4f42bfc3ed..76fab93821a 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -114,7 +114,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do expect(hash).to eq({ link => user }) end - it 'returns an empty Hash when entry does not exist in the database' do + it 'returns an empty Hash when entry does not exist in the database', :request_store do link = double(:link) expect(link).to receive(:has_attribute?). diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index 412ffa77c36..583ce63a8ab 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -10,7 +10,9 @@ describe Banzai::ReferenceParser::CommitParser, lib: true do describe '#nodes_visible_to_user' do context 'when the link has a data-issue attribute' do - before { link['data-commit'] = 123 } + before do + link['data-commit'] = 123 + end it_behaves_like "referenced feature visibility", "repository" end diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb index 96e55b0997a..8c0f5d7df97 100644 --- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb @@ -10,7 +10,9 @@ describe Banzai::ReferenceParser::CommitRangeParser, lib: true do describe '#nodes_visible_to_user' do context 'when the link has a data-issue attribute' do - before { link['data-commit-range'] = '123..456' } + before do + link['data-commit-range'] = '123..456' + end it_behaves_like "referenced feature visibility", "repository" end diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb index 0af36776a54..d212bbac619 100644 --- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb @@ -10,7 +10,9 @@ describe Banzai::ReferenceParser::ExternalIssueParser, lib: true do describe '#nodes_visible_to_user' do context 'when the link has a data-issue attribute' do - before { link['data-external-issue'] = 123 } + before do + link['data-external-issue'] = 123 + end levels = [ProjectFeature::DISABLED, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb index 8c540d35ddd..ddd699f3c25 100644 --- a/spec/lib/banzai/reference_parser/label_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb @@ -11,7 +11,9 @@ describe Banzai::ReferenceParser::LabelParser, lib: true do describe '#nodes_visible_to_user' do context 'when the link has a data-issue attribute' do - before { link['data-label'] = label.id.to_s } + before do + link['data-label'] = label.id.to_s + end it_behaves_like "referenced feature visibility", "issues", "merge_requests" end diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb index 2d4d589ae34..72d4f3bc18e 100644 --- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb @@ -11,7 +11,9 @@ describe Banzai::ReferenceParser::MilestoneParser, lib: true do describe '#nodes_visible_to_user' do context 'when the link has a data-issue attribute' do - before { link['data-milestone'] = milestone.id.to_s } + before do + link['data-milestone'] = milestone.id.to_s + end it_behaves_like "referenced feature visibility", "issues", "merge_requests" end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 2ca0773ad1d..af0e7855a9b 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -596,62 +596,117 @@ module Ci end describe "Image and service handling" do - it "returns image and service when defined" do - config = YAML.dump({ - image: "ruby:2.1", - services: ["mysql"], - before_script: ["pwd"], - rspec: { script: "rspec" } - }) + context "when extended docker configuration is used" do + it "returns image and service when defined" do + config = YAML.dump({ image: { name: "ruby:2.1" }, + services: ["mysql", { name: "docker:dind", alias: "docker" }], + before_script: ["pwd"], + rspec: { script: "rspec" } }) - config_processor = GitlabCiYamlProcessor.new(config, path) + config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) - expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ - stage: "test", - stage_idx: 1, - name: "rspec", - commands: "pwd\nrspec", - coverage_regex: nil, - tag_list: [], - options: { - image: "ruby:2.1", - services: ["mysql"] - }, - allow_failure: false, - when: "on_success", - environment: nil, - yaml_variables: [] - }) + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + stage: "test", + stage_idx: 1, + name: "rspec", + commands: "pwd\nrspec", + coverage_regex: nil, + tag_list: [], + options: { + image: { name: "ruby:2.1" }, + services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }] + }, + allow_failure: false, + when: "on_success", + environment: nil, + yaml_variables: [] + }) + end + + it "returns image and service when overridden for job" do + config = YAML.dump({ image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: { image: { name: "ruby:2.5" }, + services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + stage: "test", + stage_idx: 1, + name: "rspec", + commands: "pwd\nrspec", + coverage_regex: nil, + tag_list: [], + options: { + image: { name: "ruby:2.5" }, + services: [{ name: "postgresql", alias: "db-pg" }, { name: "docker:dind" }] + }, + allow_failure: false, + when: "on_success", + environment: nil, + yaml_variables: [] + }) + end end - it "returns image and service when overridden for job" do - config = YAML.dump({ - image: "ruby:2.1", - services: ["mysql"], - before_script: ["pwd"], - rspec: { image: "ruby:2.5", services: ["postgresql"], script: "rspec" } - }) + context "when etended docker configuration is not used" do + it "returns image and service when defined" do + config = YAML.dump({ image: "ruby:2.1", + services: ["mysql", "docker:dind"], + before_script: ["pwd"], + rspec: { script: "rspec" } }) - config_processor = GitlabCiYamlProcessor.new(config, path) + config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) - expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ - stage: "test", - stage_idx: 1, - name: "rspec", - commands: "pwd\nrspec", - coverage_regex: nil, - tag_list: [], - options: { - image: "ruby:2.5", - services: ["postgresql"] - }, - allow_failure: false, - when: "on_success", - environment: nil, - yaml_variables: [] - }) + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + stage: "test", + stage_idx: 1, + name: "rspec", + commands: "pwd\nrspec", + coverage_regex: nil, + tag_list: [], + options: { + image: { name: "ruby:2.1" }, + services: [{ name: "mysql" }, { name: "docker:dind" }] + }, + allow_failure: false, + when: "on_success", + environment: nil, + yaml_variables: [] + }) + end + + it "returns image and service when overridden for job" do + config = YAML.dump({ image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + stage: "test", + stage_idx: 1, + name: "rspec", + commands: "pwd\nrspec", + coverage_regex: nil, + tag_list: [], + options: { + image: { name: "ruby:2.5" }, + services: [{ name: "postgresql" }, { name: "docker:dind" }] + }, + allow_failure: false, + when: "on_success", + environment: nil, + yaml_variables: [] + }) + end end end @@ -884,8 +939,8 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: "ruby:2.1", - services: ["mysql"], + image: { name: "ruby:2.1" }, + services: [{ name: "mysql" }], artifacts: { name: "custom_name", paths: ["logs/", "binaries/"], @@ -1261,7 +1316,7 @@ EOT config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a hash or a string") end it "returns errors if job name is blank" do @@ -1282,35 +1337,35 @@ EOT config = YAML.dump({ rspec: { script: "test", image: ["test"] } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string") end it "returns errors if services parameter is not an array" do config = YAML.dump({ services: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be a array") end it "returns errors if services parameter is not an array of strings" do config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string") end it "returns errors if job services parameter is not an array" do config = YAML.dump({ rspec: { script: "test", services: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be a array") end it "returns errors if job services parameter is not an array of strings" do config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string") end it "returns error if job configuration is invalid" do @@ -1324,7 +1379,7 @@ EOT config = YAML.dump({ extra: { script: 'rspec', services: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be a array") end it "returns errors if there are no jobs defined" do diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 33ab005667a..2b26a318583 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -77,7 +77,10 @@ describe ExtractsPath, lib: true do context 'without a path' do let(:params) { { ref: 'v1.0.0.atom' } } - before { assign_ref_vars } + + before do + assign_ref_vars + end it 'sets the un-suffixed version as @ref' do expect(@ref).to eq('v1.0.0') diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb index 94dcddcc30c..fc72df575be 100644 --- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb +++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb @@ -40,7 +40,9 @@ describe Gitlab::Auth::UniqueIpsLimiter, :redis, lib: true do end context 'allow 2 unique ips' do - before { current_application_settings.update!(unique_ips_limit_per_user: 2) } + before do + current_application_settings.update!(unique_ips_limit_per_user: 2) + end it 'blocks user trying to login from third ip' do change_ip('ip1') diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/build/status_spec.rb index 3c5414701a7..6abf4ca46a9 100644 --- a/spec/lib/gitlab/badge/build/status_spec.rb +++ b/spec/lib/gitlab/badge/build/status_spec.rb @@ -29,7 +29,9 @@ describe Gitlab::Badge::Build::Status do let!(:build) { create_build(project, sha, branch) } context 'build success' do - before { build.success! } + before do + build.success! + end describe '#status' do it 'is successful' do @@ -39,7 +41,9 @@ describe Gitlab::Badge::Build::Status do end context 'build failed' do - before { build.drop! } + before do + build.drop! + end describe '#status' do it 'failed' do diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index c0c309d8179..643e590438a 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -20,7 +20,9 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ).exec end - before { project.add_developer(user) } + before do + project.add_developer(user) + end context 'without failed checks' do it "doesn't raise an error" do @@ -50,7 +52,9 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let!(:protected_tag) { create(:protected_tag, project: project, name: 'v*') } context 'as master' do - before { project.add_master(user) } + before do + project.add_master(user) + end context 'deletion' do let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb index 382385dfd6b..773a52cdfbc 100644 --- a/spec/lib/gitlab/ci/build/image_spec.rb +++ b/spec/lib/gitlab/ci/build/image_spec.rb @@ -10,12 +10,28 @@ describe Gitlab::Ci::Build::Image do let(:image_name) { 'ruby:2.1' } let(:job) { create(:ci_build, options: { image: image_name } ) } - it 'fabricates an object of the proper class' do - is_expected.to be_kind_of(described_class) + context 'when image is defined as string' do + it 'fabricates an object of the proper class' do + is_expected.to be_kind_of(described_class) + end + + it 'populates fabricated object with the proper name attribute' do + expect(subject.name).to eq(image_name) + end end - it 'populates fabricated object with the proper name attribute' do - expect(subject.name).to eq(image_name) + context 'when image is defined as hash' do + let(:entrypoint) { '/bin/sh' } + let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) } + + it 'fabricates an object of the proper class' do + is_expected.to be_kind_of(described_class) + end + + it 'populates fabricated object with the proper attributes' do + expect(subject.name).to eq(image_name) + expect(subject.entrypoint).to eq(entrypoint) + end end context 'when image name is empty' do @@ -41,10 +57,39 @@ describe Gitlab::Ci::Build::Image do let(:service_image_name) { 'postgres' } let(:job) { create(:ci_build, options: { services: [service_image_name] }) } - it 'fabricates an non-empty array of objects' do - is_expected.to be_kind_of(Array) - is_expected.not_to be_empty - expect(subject.first.name).to eq(service_image_name) + context 'when service is defined as string' do + it 'fabricates an non-empty array of objects' do + is_expected.to be_kind_of(Array) + is_expected.not_to be_empty + end + + it 'populates fabricated objects with the proper name attributes' do + expect(subject.first).to be_kind_of(described_class) + expect(subject.first.name).to eq(service_image_name) + end + end + + context 'when service is defined as hash' do + let(:service_entrypoint) { '/bin/sh' } + let(:service_alias) { 'db' } + let(:service_command) { 'sleep 30' } + let(:job) do + create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint, + alias: service_alias, command: service_command }] }) + end + + it 'fabricates an non-empty array of objects' do + is_expected.to be_kind_of(Array) + is_expected.not_to be_empty + expect(subject.first).to be_kind_of(described_class) + end + + it 'populates fabricated objects with the proper attributes' do + expect(subject.first.name).to eq(service_image_name) + expect(subject.first.entrypoint).to eq(service_entrypoint) + expect(subject.first.alias).to eq(service_alias) + expect(subject.first.command).to eq(service_command) + end end context 'when service image name is empty' do diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 2ed120f356a..878b1d6b862 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -4,7 +4,9 @@ describe Gitlab::Ci::Config::Entry::Cache do let(:entry) { described_class.new(config) } describe 'validations' do - before { entry.compose! } + before do + entry.compose! + end context 'when entry config value is correct' do let(:config) do diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index c330e609337..3c0007f4d57 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -3,7 +3,9 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Environment do let(:entry) { described_class.new(config) } - before { entry.compose! } + before do + entry.compose! + end context 'when configuration is a string' do let(:config) { 'production' } diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 23270ad5053..293f112b2b0 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -33,7 +33,9 @@ describe Gitlab::Ci::Config::Entry::Global do end describe '#compose!' do - before { global.compose! } + before do + global.compose! + end it 'creates nodes hash' do expect(global.descendants).to be_an Array @@ -79,7 +81,9 @@ describe Gitlab::Ci::Config::Entry::Global do end context 'when composed' do - before { global.compose! } + before do + global.compose! + end describe '#errors' do it 'has no errors' do @@ -95,13 +99,13 @@ describe Gitlab::Ci::Config::Entry::Global do describe '#image_value' do it 'returns valid image' do - expect(global.image_value).to eq 'ruby:2.2' + expect(global.image_value).to eq(name: 'ruby:2.2') end end describe '#services_value' do it 'returns array of services' do - expect(global.services_value).to eq ['postgres:9.1', 'mysql:5.5'] + expect(global.services_value).to eq [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }] end end @@ -150,8 +154,8 @@ describe Gitlab::Ci::Config::Entry::Global do script: %w[rspec ls], before_script: %w(ls pwd), commands: "ls\npwd\nrspec\nls", - image: 'ruby:2.2', - services: ['postgres:9.1', 'mysql:5.5'], + image: { name: 'ruby:2.2' }, + services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'] }, variables: { 'VAR' => 'value' }, @@ -161,8 +165,8 @@ describe Gitlab::Ci::Config::Entry::Global do before_script: [], script: %w[spinach], commands: 'spinach', - image: 'ruby:2.2', - services: ['postgres:9.1', 'mysql:5.5'], + image: { name: 'ruby:2.2' }, + services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'] }, variables: {}, @@ -175,7 +179,9 @@ describe Gitlab::Ci::Config::Entry::Global do end context 'when most of entires not defined' do - before { global.compose! } + before do + global.compose! + end let(:hash) do { cache: { key: 'a' }, rspec: { script: %w[ls] } } @@ -218,7 +224,9 @@ describe Gitlab::Ci::Config::Entry::Global do # details. # context 'when entires specified but not defined' do - before { global.compose! } + before do + global.compose! + end let(:hash) do { variables: nil, rspec: { script: 'rspec' } } @@ -233,7 +241,9 @@ describe Gitlab::Ci::Config::Entry::Global do end context 'when configuration is not valid' do - before { global.compose! } + before do + global.compose! + end context 'when before script is not an array' do let(:hash) do @@ -297,7 +307,9 @@ describe Gitlab::Ci::Config::Entry::Global do end describe '#[]' do - before { global.compose! } + before do + global.compose! + end let(:hash) do { cache: { key: 'a' }, rspec: { script: 'ls' } } diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index 3c99cb0a1ee..bca22e39500 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -3,43 +3,104 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Image do let(:entry) { described_class.new(config) } - describe 'validation' do - context 'when entry config value is correct' do - let(:config) { 'ruby:2.2' } + context 'when configuration is a string' do + let(:config) { 'ruby:2.2' } - describe '#value' do - it 'returns image string' do - expect(entry.value).to eq 'ruby:2.2' - end + describe '#value' do + it 'returns image hash' do + expect(entry.value).to eq({ name: 'ruby:2.2' }) end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#image' do + it "returns image's name" do + expect(entry.name).to eq 'ruby:2.2' + end + end - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end + describe '#entrypoint' do + it "returns image's entrypoint" do + expect(entry.entrypoint).to be_nil end + end + end - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end + context 'when configuration is a hash' do + let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } } + + describe '#value' do + it 'returns image hash' do + expect(entry.value).to eq(config) end end - context 'when entry value is not correct' do - let(:config) { ['ruby:2.2'] } + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'image config should be a string' - end + describe '#image' do + it "returns image's name" do + expect(entry.name).to eq 'ruby:2.2' end + end + + describe '#entrypoint' do + it "returns image's entrypoint" do + expect(entry.entrypoint).to eq '/bin/sh' + end + end + end + + context 'when entry value is not correct' do + let(:config) { ['ruby:2.2'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'image config should be a hash or a string' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + + context 'when unexpected key is specified' do + let(:config) { { name: 'ruby:2.2', non_existing: 'test' } } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'image config contains unknown keys: non_existing' + end + end - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid end end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 9249bb9c172..92cba689f47 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -18,7 +18,9 @@ describe Gitlab::Ci::Config::Entry::Job do end describe 'validations' do - before { entry.compose! } + before do + entry.compose! + end context 'when entry config value is correct' do let(:config) { { script: 'rspec' } } @@ -97,14 +99,16 @@ describe Gitlab::Ci::Config::Entry::Job do let(:deps) { double('deps', '[]' => unspecified) } context 'when job config overrides global config' do - before { entry.compose!(deps) } + before do + entry.compose!(deps) + end let(:config) do { script: 'rspec', image: 'some_image', cache: { key: 'test' } } end it 'overrides global config' do - expect(entry[:image].value).to eq 'some_image' + expect(entry[:image].value).to eq(name: 'some_image') expect(entry[:cache].value).to eq(key: 'test') end end @@ -125,10 +129,14 @@ describe Gitlab::Ci::Config::Entry::Job do end context 'when composed' do - before { entry.compose! } + before do + entry.compose! + end describe '#value' do - before { entry.compose! } + before do + entry.compose! + end context 'when entry is correct' do let(:config) do diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 7d104372ac6..c0a2b6517e3 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -4,7 +4,9 @@ describe Gitlab::Ci::Config::Entry::Jobs do let(:entry) { described_class.new(config) } describe 'validations' do - before { entry.compose! } + before do + entry.compose! + end context 'when entry config value is correct' do let(:config) { { rspec: { script: 'rspec' } } } @@ -48,7 +50,9 @@ describe Gitlab::Ci::Config::Entry::Jobs do end context 'when valid job entries composed' do - before { entry.compose! } + before do + entry.compose! + end let(:config) do { rspec: { script: 'rspec' }, diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb new file mode 100644 index 00000000000..7202fe525e4 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -0,0 +1,119 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Service do + let(:entry) { described_class.new(config) } + + before do + entry.compose! + end + + context 'when configuration is a string' do + let(:config) { 'postgresql:9.5' } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to include(name: 'postgresql:9.5') + end + end + + describe '#image' do + it "returns service's image name" do + expect(entry.name).to eq 'postgresql:9.5' + end + end + + describe '#alias' do + it "returns service's alias" do + expect(entry.alias).to be_nil + end + end + + describe '#command' do + it "returns service's command" do + expect(entry.command).to be_nil + end + end + end + + context 'when configuration is a hash' do + let(:config) do + { name: 'postgresql:9.5', alias: 'db', command: 'cmd', entrypoint: '/bin/sh' } + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to eq config + end + end + + describe '#image' do + it "returns service's image name" do + expect(entry.name).to eq 'postgresql:9.5' + end + end + + describe '#alias' do + it "returns service's alias" do + expect(entry.alias).to eq 'db' + end + end + + describe '#command' do + it "returns service's command" do + expect(entry.command).to eq 'cmd' + end + end + + describe '#entrypoint' do + it "returns service's entrypoint" do + expect(entry.entrypoint).to eq '/bin/sh' + end + end + end + + context 'when entry value is not correct' do + let(:config) { ['postgresql:9.5'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'service config should be a hash or a string' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + + context 'when unexpected key is specified' do + let(:config) { { name: 'postgresql:9.5', non_existing: 'test' } } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'service config contains unknown keys: non_existing' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb index 66fad3b6b16..7c4319aee63 100644 --- a/spec/lib/gitlab/ci/config/entry/services_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb @@ -3,37 +3,32 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Services do let(:entry) { described_class.new(config) } - describe 'validations' do - context 'when entry config value is correct' do - let(:config) { ['postgres:9.1', 'mysql:5.5'] } + before do + entry.compose! + end - describe '#value' do - it 'returns array of services as is' do - expect(entry.value).to eq config - end - end + context 'when configuration is valid' do + let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old' }] } - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid end end - context 'when entry value is not correct' do - let(:config) { 'ls' } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'services config should be an array of strings' - end + describe '#value' do + it 'returns valid array' do + expect(entry.value).to eq([{ name: 'postgresql:9.5' }, { name: 'postgresql:9.1', alias: 'postgres_old' }]) end + end + end + + context 'when configuration is invalid' do + let(:config) { 'postgresql:9.5' } - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end + describe '#valid?' do + it 'is invalid' do + expect(entry).not_to be_valid end end end diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 8ad9b7cdf07..114d2490490 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -47,7 +47,9 @@ describe Gitlab::Ci::Status::Build::Cancelable do describe '#has_action?' do context 'when user is allowed to update build' do - before { build.project.team << [user, :developer] } + before do + build.project.team << [user, :developer] + end it { is_expected.to have_action } end diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb index 72bd7c4eb93..03d1f46b517 100644 --- a/spec/lib/gitlab/ci/status/build/common_spec.rb +++ b/spec/lib/gitlab/ci/status/build/common_spec.rb @@ -17,13 +17,17 @@ describe Gitlab::Ci::Status::Build::Common do describe '#has_details?' do context 'when user has access to read build' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it { is_expected.to have_details } end context 'when user does not have access to read build' do - before { project.update(public_builds: false) } + before do + project.update(public_builds: false) + end it { is_expected.not_to have_details } end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 3f30b2c38f2..c8a97016f20 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -6,7 +6,9 @@ describe Gitlab::Ci::Status::Build::Factory do let(:status) { factory.fabricate! } let(:factory) { described_class.new(build, user) } - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end context 'when build is successful' do let(:build) { create(:ci_build, :success) } diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 0e15a5f3c6b..32b2e62e4e0 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -28,7 +28,9 @@ describe Gitlab::Ci::Status::Build::Play do end context 'when user can not push to the branch' do - before { build.project.add_developer(user) } + before do + build.project.add_developer(user) + end it { is_expected.not_to have_action } end diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index 2db0f8d29bd..099d873fc01 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -47,7 +47,9 @@ describe Gitlab::Ci::Status::Build::Retryable do describe '#has_action?' do context 'when user is allowed to update build' do - before { build.project.team << [user, :developer] } + before do + build.project.team << [user, :developer] + end it { is_expected.to have_action } end diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 8d021c35a69..23902f26b1a 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -19,7 +19,9 @@ describe Gitlab::Ci::Status::Build::Stop do describe '#has_action?' do context 'when user is allowed to update build' do - before { build.project.team << [user, :developer] } + before do + build.project.team << [user, :developer] + end it { is_expected.to have_action } end diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb index 5a97d98b55f..b38fbee2486 100644 --- a/spec/lib/gitlab/ci/status/external/common_spec.rb +++ b/spec/lib/gitlab/ci/status/external/common_spec.rb @@ -4,9 +4,10 @@ describe Gitlab::Ci::Status::External::Common do let(:user) { create(:user) } let(:project) { external_status.project } let(:external_target_url) { 'http://example.gitlab.com/status' } + let(:external_description) { 'my description' } let(:external_status) do - create(:generic_commit_status, target_url: external_target_url) + create(:generic_commit_status, target_url: external_target_url, description: external_description) end subject do @@ -15,13 +16,21 @@ describe Gitlab::Ci::Status::External::Common do .extend(described_class) end + describe '#label' do + it 'returns description' do + expect(subject.label).to eq external_description + end + end + describe '#has_action?' do it { is_expected.not_to have_action } end describe '#has_details?' do context 'when user has access to read commit status' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it { is_expected.to have_details } end diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index d665674bf70..f5fd31e8d03 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -17,7 +17,9 @@ describe Gitlab::Ci::Status::Pipeline::Common do describe '#has_details?' do context 'when user has access to read pipeline' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it { is_expected.to have_details } end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index fda39d78610..a566f24f6a6 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -32,6 +32,37 @@ describe Gitlab::CurrentSettings do expect(current_application_settings).to be_a(ApplicationSetting) end + + context 'with migrations pending' do + before do + expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true) + end + + it 'returns an in-memory ApplicationSetting object' do + settings = current_application_settings + + expect(settings).to be_a(OpenStruct) + expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled) + expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled) + end + + it 'uses the existing database settings and falls back to defaults' do + db_settings = create(:application_setting, + home_page_url: 'http://mydomain.com', + signup_enabled: false) + settings = current_application_settings + app_defaults = ApplicationSetting.last + + expect(settings).to be_a(OpenStruct) + expect(settings.home_page_url).to eq(db_settings.home_page_url) + expect(settings.signup_enabled?).to be_falsey + expect(settings.signup_enabled).to be_falsey + + # Check that unspecified values use the defaults + settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key } + settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) } + end + end end context 'with DB unavailable' do diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 3fdafd867da..30aa463faf8 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -7,7 +7,42 @@ describe Gitlab::Database::MigrationHelpers, lib: true do ) end - before { allow(model).to receive(:puts) } + before do + allow(model).to receive(:puts) + end + + describe '#add_timestamps_with_timezone' do + before do + allow(model).to receive(:transaction_open?).and_return(false) + end + + context 'using PostgreSQL' do + before do + allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + allow(model).to receive(:disable_statement_timeout) + end + + it 'adds "created_at" and "updated_at" fields with the "datetime_with_timezone" data type' do + expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, { null: false }) + expect(model).to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, { null: false }) + + model.add_timestamps_with_timezone(:foo) + end + end + + context 'using MySQL' do + before do + allow(Gitlab::Database).to receive(:postgresql?).and_return(false) + end + + it 'adds "created_at" and "updated_at" fields with "datetime_with_timezone" data type' do + expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, { null: false }) + expect(model).to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, { null: false }) + + model.add_timestamps_with_timezone(:foo) + end + end + end describe '#add_concurrent_index' do context 'outside a transaction' do diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 9b1d66a1b1c..428b6edb7d6 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -53,14 +53,18 @@ describe Gitlab::Database, lib: true do describe '.nulls_last_order' do context 'when using PostgreSQL' do - before { expect(described_class).to receive(:postgresql?).and_return(true) } + before do + expect(described_class).to receive(:postgresql?).and_return(true) + end it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'} it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'} end context 'when using MySQL' do - before { expect(described_class).to receive(:postgresql?).and_return(false) } + before do + expect(described_class).to receive(:postgresql?).and_return(false) + end it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column IS NULL, column ASC'} it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC'} @@ -69,14 +73,18 @@ describe Gitlab::Database, lib: true do describe '.nulls_first_order' do context 'when using PostgreSQL' do - before { expect(described_class).to receive(:postgresql?).and_return(true) } + before do + expect(described_class).to receive(:postgresql?).and_return(true) + end it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC NULLS FIRST'} it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'} end context 'when using MySQL' do - before { expect(described_class).to receive(:postgresql?).and_return(false) } + before do + expect(described_class).to receive(:postgresql?).and_return(false) + end it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC'} it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column IS NULL, column DESC'} @@ -121,6 +129,55 @@ describe Gitlab::Database, lib: true do end end + describe '.bulk_insert' do + before do + allow(described_class).to receive(:connection).and_return(connection) + allow(connection).to receive(:quote_column_name, &:itself) + allow(connection).to receive(:quote, &:itself) + allow(connection).to receive(:execute) + end + + let(:connection) { double(:connection) } + + let(:rows) do + [ + { a: 1, b: 2, c: 3 }, + { c: 6, a: 4, b: 5 } + ] + end + + it 'does nothing with empty rows' do + expect(connection).not_to receive(:execute) + + described_class.bulk_insert('test', []) + end + + it 'uses the ordering from the first row' do + expect(connection).to receive(:execute) do |sql| + expect(sql).to include('(1, 2, 3)') + expect(sql).to include('(4, 5, 6)') + end + + described_class.bulk_insert('test', rows) + end + + it 'quotes column names' do + expect(connection).to receive(:quote_column_name).with(:a) + expect(connection).to receive(:quote_column_name).with(:b) + expect(connection).to receive(:quote_column_name).with(:c) + + described_class.bulk_insert('test', rows) + end + + it 'quotes values' do + 1.upto(6) do |i| + expect(connection).to receive(:quote).with(i) + end + + described_class.bulk_insert('test', rows) + end + end + describe '.create_connection_pool' do it 'creates a new connection pool with specific pool size' do pool = described_class.create_connection_pool(5) diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index a9953bb0d01..f289131cc3a 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -92,4 +92,305 @@ describe Gitlab::Diff::File, lib: true do expect(diff_file.diffable?).to be_falsey end end + + describe '#content_changed?' do + context 'when created' do + let(:commit) { project.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') } + + it 'returns false' do + expect(diff_file.content_changed?).to be_falsey + end + end + + context 'when deleted' do + let(:commit) { project.commit('d59c60028b053793cecfb4022de34602e1a9218e') } + let(:diff_file) { commit.diffs.diff_file_with_old_path('files/js/commit.js.coffee') } + + it 'returns false' do + expect(diff_file.content_changed?).to be_falsey + end + end + + context 'when renamed' do + let(:commit) { project.commit('6907208d755b60ebeacb2e9dfea74c92c3449a1f') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/js/commit.coffee') } + + before do + allow(diff_file.new_blob).to receive(:id).and_return(diff_file.old_blob.id) + end + + it 'returns false' do + expect(diff_file.content_changed?).to be_falsey + end + end + + context 'when content changed' do + context 'when binary' do + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') } + + it 'returns true' do + expect(diff_file.content_changed?).to be_truthy + end + end + + context 'when not binary' do + let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + it 'returns true' do + expect(diff_file.content_changed?).to be_truthy + end + end + end + end + + describe '#simple_viewer' do + context 'when the file is not diffable' do + before do + allow(diff_file).to receive(:diffable?).and_return(false) + end + + it 'returns a Not Diffable viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::NotDiffable) + end + end + + context 'when the content changed' do + context 'when the file represented by the diff file is binary' do + before do + allow(diff_file).to receive(:raw_binary?).and_return(true) + end + + it 'returns a No Preview viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::NoPreview) + end + end + + context 'when the diff file old and new blob types are different' do + before do + allow(diff_file).to receive(:different_type?).and_return(true) + end + + it 'returns a No Preview viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::NoPreview) + end + end + + context 'when the file represented by the diff file is text-based' do + it 'returns a text viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Text) + end + end + end + + context 'when created' do + let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + before do + allow(diff_file).to receive(:content_changed?).and_return(nil) + end + + context 'when the file represented by the diff file is binary' do + before do + allow(diff_file).to receive(:raw_binary?).and_return(true) + end + + it 'returns an Added viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Added) + end + end + + context 'when the diff file old and new blob types are different' do + before do + allow(diff_file).to receive(:different_type?).and_return(true) + end + + it 'returns an Added viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Added) + end + end + + context 'when the file represented by the diff file is text-based' do + it 'returns a text viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Text) + end + end + end + + context 'when deleted' do + let(:commit) { project.commit('d59c60028b053793cecfb4022de34602e1a9218e') } + let(:diff_file) { commit.diffs.diff_file_with_old_path('files/js/commit.js.coffee') } + + before do + allow(diff_file).to receive(:content_changed?).and_return(nil) + end + + context 'when the file represented by the diff file is binary' do + before do + allow(diff_file).to receive(:raw_binary?).and_return(true) + end + + it 'returns a Deleted viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Deleted) + end + end + + context 'when the diff file old and new blob types are different' do + before do + allow(diff_file).to receive(:different_type?).and_return(true) + end + + it 'returns a Deleted viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Deleted) + end + end + + context 'when the file represented by the diff file is text-based' do + it 'returns a text viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Text) + end + end + end + + context 'when renamed' do + let(:commit) { project.commit('6907208d755b60ebeacb2e9dfea74c92c3449a1f') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/js/commit.coffee') } + + before do + allow(diff_file).to receive(:content_changed?).and_return(nil) + end + + it 'returns a Renamed viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::Renamed) + end + end + + context 'when mode changed' do + before do + allow(diff_file).to receive(:content_changed?).and_return(nil) + allow(diff_file).to receive(:mode_changed?).and_return(true) + end + + it 'returns a Mode Changed viewer' do + expect(diff_file.simple_viewer).to be_a(DiffViewer::ModeChanged) + end + end + end + + describe '#rich_viewer' do + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') } + + context 'when the diff file has a matching viewer' do + context 'when the diff file content did not change' do + before do + allow(diff_file).to receive(:content_changed?).and_return(false) + end + + it 'returns nil' do + expect(diff_file.rich_viewer).to be_nil + end + end + + context 'when the diff file is not diffable' do + before do + allow(diff_file).to receive(:diffable?).and_return(false) + end + + it 'returns nil' do + expect(diff_file.rich_viewer).to be_nil + end + end + + context 'when the diff file old and new blob types are different' do + before do + allow(diff_file).to receive(:different_type?).and_return(true) + end + + it 'returns nil' do + expect(diff_file.rich_viewer).to be_nil + end + end + + context 'when the diff file has an external storage error' do + before do + allow(diff_file).to receive(:external_storage_error?).and_return(true) + end + + it 'returns nil' do + expect(diff_file.rich_viewer).to be_nil + end + end + + context 'when everything is right' do + it 'returns the viewer' do + expect(diff_file.rich_viewer).to be_a(DiffViewer::Image) + end + end + end + + context 'when the diff file does not have a matching viewer' do + let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + it 'returns nil' do + expect(diff_file.rich_viewer).to be_nil + end + end + end + + describe '#rendered_as_text?' do + context 'when the simple viewer is text-based' do + let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + context 'when ignoring errors' do + context 'when the viewer has render errors' do + before do + diff_file.diff.too_large! + end + + it 'returns true' do + expect(diff_file.rendered_as_text?).to be_truthy + end + end + + context "when the viewer doesn't have render errors" do + it 'returns true' do + expect(diff_file.rendered_as_text?).to be_truthy + end + end + end + + context 'when not ignoring errors' do + context 'when the viewer has render errors' do + before do + diff_file.diff.too_large! + end + + it 'returns false' do + expect(diff_file.rendered_as_text?(ignore_errors: false)).to be_falsey + end + end + + context "when the viewer doesn't have render errors" do + it 'returns true' do + expect(diff_file.rendered_as_text?(ignore_errors: false)).to be_truthy + end + end + end + end + + context 'when the simple viewer is binary' do + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') } + + it 'returns false' do + expect(diff_file.rendered_as_text?).to be_falsey + end + end + end end diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 3f79eaf7afb..cd0309e248d 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -91,7 +91,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do end end - context 'when the note contains slash commands' do + context 'when the note contains quick actions' do let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") } context 'and current user cannot update noteable' do diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb index 3c6ef7c7ccb..4acf4f047f1 100644 --- a/spec/lib/gitlab/etag_caching/middleware_spec.rb +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -15,13 +15,13 @@ describe Gitlab::EtagCaching::Middleware do end it 'does not add ETag header' do - _, headers, _ = middleware.call(build_env(path, if_none_match)) + _, headers, _ = middleware.call(build_request(path, if_none_match)) expect(headers['ETag']).to be_nil end it 'passes status code from app' do - status, _, _ = middleware.call(build_env(path, if_none_match)) + status, _, _ = middleware.call(build_request(path, if_none_match)) expect(status).to eq app_status_code end @@ -39,7 +39,7 @@ describe Gitlab::EtagCaching::Middleware do expect_any_instance_of(Gitlab::EtagCaching::Store) .to receive(:touch).and_return('123') - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end context 'when If-None-Match header was specified' do @@ -51,7 +51,7 @@ describe Gitlab::EtagCaching::Middleware do expect(Gitlab::Metrics).to receive(:add_event) .with(:etag_caching_key_not_found, endpoint: 'issue_notes') - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end end end @@ -65,7 +65,7 @@ describe Gitlab::EtagCaching::Middleware do end it 'returns this value as header' do - _, headers, _ = middleware.call(build_env(path, if_none_match)) + _, headers, _ = middleware.call(build_request(path, if_none_match)) expect(headers['ETag']).to eq 'W/"123"' end @@ -82,17 +82,17 @@ describe Gitlab::EtagCaching::Middleware do it 'does not call app' do expect(app).not_to receive(:call) - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end it 'returns status code 304' do - status, _, _ = middleware.call(build_env(path, if_none_match)) + status, _, _ = middleware.call(build_request(path, if_none_match)) expect(status).to eq 304 end it 'returns empty body' do - _, _, body = middleware.call(build_env(path, if_none_match)) + _, _, body = middleware.call(build_request(path, if_none_match)) expect(body).to be_empty end @@ -103,7 +103,7 @@ describe Gitlab::EtagCaching::Middleware do expect(Gitlab::Metrics).to receive(:add_event) .with(:etag_caching_cache_hit, endpoint: 'issue_notes') - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end context 'when polling is disabled' do @@ -113,7 +113,7 @@ describe Gitlab::EtagCaching::Middleware do end it 'returns status code 429' do - status, _, _ = middleware.call(build_env(path, if_none_match)) + status, _, _ = middleware.call(build_request(path, if_none_match)) expect(status).to eq 429 end @@ -131,7 +131,7 @@ describe Gitlab::EtagCaching::Middleware do it 'calls app' do expect(app).to receive(:call).and_return([app_status_code, {}, ['body']]) - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end it 'tracks "etag_caching_resource_changed" event' do @@ -142,7 +142,7 @@ describe Gitlab::EtagCaching::Middleware do expect(Gitlab::Metrics).to receive(:add_event) .with(:etag_caching_resource_changed, endpoint: 'issue_notes') - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end end @@ -160,7 +160,7 @@ describe Gitlab::EtagCaching::Middleware do expect(Gitlab::Metrics).to receive(:add_event) .with(:etag_caching_header_missing, endpoint: 'issue_notes') - middleware.call(build_env(path, if_none_match)) + middleware.call(build_request(path, if_none_match)) end end @@ -192,10 +192,7 @@ describe Gitlab::EtagCaching::Middleware do .to receive(:get).and_return(value) end - def build_env(path, if_none_match) - { - 'PATH_INFO' => path, - 'HTTP_IF_NONE_MATCH' => if_none_match - } + def build_request(path, if_none_match) + { 'PATH_INFO' => path, 'HTTP_IF_NONE_MATCH' => if_none_match } end end diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb index 2bb40827fcf..f69cb502ca6 100644 --- a/spec/lib/gitlab/etag_caching/router_spec.rb +++ b/spec/lib/gitlab/etag_caching/router_spec.rb @@ -2,115 +2,91 @@ require 'spec_helper' describe Gitlab::EtagCaching::Router do it 'matches issue notes endpoint' do - request = build_request( + result = described_class.match( '/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'issue_notes' end it 'matches issue title endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/issues/123/realtime_changes' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'issue_title' end it 'matches project pipelines endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/pipelines.json' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'project_pipelines' end it 'matches commit pipelines endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'commit_pipelines' end it 'matches new merge request pipelines endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/merge_requests/new.json' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'new_merge_request_pipelines' end it 'matches merge request pipelines endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/merge_requests/234/pipelines.json' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'merge_request_pipelines' end it 'matches build endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/builds/234.json' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'project_build' end it 'does not match blob with confusing name' do - request = build_request( + result = described_class.match( '/my-group/my-project/blob/master/pipelines.json' ) - result = described_class.match(request) - expect(result).to be_blank end it 'matches the environments path' do - request = build_request( + result = described_class.match( '/my-group/my-project/environments.json' ) - result = described_class.match(request) expect(result).to be_present - expect(result.name).to eq 'environments' end it 'matches pipeline#show endpoint' do - request = build_request( + result = described_class.match( '/my-group/my-project/pipelines/2.json' ) - result = described_class.match(request) - expect(result).to be_present expect(result.name).to eq 'project_pipeline' end - - def build_request(path) - double(path_info: path) - end end diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb new file mode 100644 index 00000000000..b793176d84a --- /dev/null +++ b/spec/lib/gitlab/fake_application_settings_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::FakeApplicationSettings do + let(:defaults) { { signin_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } } + + subject { described_class.new(defaults) } + + it 'wraps OpenStruct variables properly' do + expect(subject.signin_enabled).to be_falsey + expect(subject.signup_enabled).to be_truthy + expect(subject.foobar).to eq('asdf') + end + + it 'defines predicate methods' do + expect(subject.signin_enabled?).to be_falsey + expect(subject.signup_enabled?).to be_truthy + end + + it 'predicate method changes when value is updated' do + subject.signin_enabled = true + + expect(subject.signin_enabled?).to be_truthy + end + + it 'does not define a predicate method' do + expect(subject.foobar?).to be_nil + end + + it 'does not override an existing predicate method' do + expect(subject.test?).to eq(123) + end +end diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 5d416c9eec3..eaec699ad90 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -6,7 +6,9 @@ describe Gitlab::Gfm::ReferenceRewriter do let(:new_project) { create(:empty_project, name: 'new-project') } let(:user) { create(:user) } - before { old_project.team << [user, :reporter] } + before do + old_project.team << [user, :reporter] + end describe '#rewrite' do subject do diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index da213f617cc..78d741f0110 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -90,7 +90,7 @@ EOT let(:diff) { described_class.new(@rugged_diff) } it 'initializes the diff' do - expect(diff.to_hash).to eq(@raw_diff_hash.merge(too_large: nil)) + expect(diff.to_hash).to eq(@raw_diff_hash) end it 'does not prune the diff' do diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb new file mode 100644 index 00000000000..143aa2218c9 --- /dev/null +++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gitlab::Git::GitmodulesParser do + it 'should parse a .gitmodules file correctly' do + parser = described_class.new(<<-'GITMODULES'.strip_heredoc) + [submodule "vendor/libgit2"] + path = vendor/libgit2 + [submodule "vendor/libgit2"] + url = https://github.com/nodegit/libgit2.git + + # a comment + [submodule "moved"] + path = new/path + url = https://example.com/some/project + [submodule "bogus"] + url = https://example.com/another/project + GITMODULES + + modules = parser.parse + + expect(modules).to eq({ + 'vendor/libgit2' => { 'name' => 'vendor/libgit2', + 'url' => 'https://github.com/nodegit/libgit2.git' }, + 'new/path' => { 'name' => 'moved', + 'url' => 'https://example.com/some/project' } + }) + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index e1e4aa9fde9..02b3f167250 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -16,7 +16,9 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#root_ref' do context 'with gitaly disabled' do - before { allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) } + before do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) + end it 'calls #discover_default_branch' do expect(repository).to receive(:discover_default_branch) @@ -25,8 +27,13 @@ describe Gitlab::Git::Repository, seed_helper: true do end context 'with gitaly enabled' do - before { stub_gitaly } - after { Gitlab::GitalyClient.clear_stubs! } + before do + stub_gitaly + end + + after do + Gitlab::GitalyClient.clear_stubs! + end it 'gets the branch name from GitalyClient' do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) @@ -120,8 +127,13 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.not_to include("branch-from-space") } context 'with gitaly enabled' do - before { stub_gitaly } - after { Gitlab::GitalyClient.clear_stubs! } + before do + stub_gitaly + end + + after do + Gitlab::GitalyClient.clear_stubs! + end it 'gets the branch names from GitalyClient' do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) @@ -158,8 +170,13 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.not_to include("v5.0.0") } context 'with gitaly enabled' do - before { stub_gitaly } - after { Gitlab::GitalyClient.clear_stubs! } + before do + stub_gitaly + end + + after do + Gitlab::GitalyClient.clear_stubs! + end it 'gets the tag names from GitalyClient' do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) @@ -341,7 +358,7 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(submodule).to eq([ "six", { "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", - "path" => "six", + "name" => "six", "url" => "git://github.com/randx/six.git" } ]) @@ -349,14 +366,14 @@ describe Gitlab::Git::Repository, seed_helper: true do it 'should handle nested submodules correctly' do nested = submodules['nested/six'] - expect(nested['path']).to eq('nested/six') + expect(nested['name']).to eq('nested/six') expect(nested['url']).to eq('git://github.com/randx/six.git') expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') end it 'should handle deeply nested submodules correctly' do nested = submodules['deeper/nested/six'] - expect(nested['path']).to eq('deeper/nested/six') + expect(nested['name']).to eq('deeper/nested/six') expect(nested['url']).to eq('git://github.com/randx/six.git') expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') end @@ -376,7 +393,7 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(submodules.first).to eq([ "six", { "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", - "path" => "six", + "name" => "six", "url" => "git://github.com/randx/six.git" } ]) @@ -1280,8 +1297,13 @@ describe Gitlab::Git::Repository, seed_helper: true do end context 'with gitaly enabled' do - before { stub_gitaly } - after { Gitlab::GitalyClient.clear_stubs! } + before do + stub_gitaly + end + + after do + Gitlab::GitalyClient.clear_stubs! + end it 'gets the branches from GitalyClient' do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches). diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 36d1d777583..9a86cfa66e4 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do let(:pull_access_check) { access.check('git-upload-pack', '_any') } let(:push_access_check) { access.check('git-receive-pack', '_any') } - let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) } + let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) } let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:actor) { user } let(:protocol) { 'ssh' } + let(:redirected_path) { nil } let(:authentication_abilities) do [ :read_project, @@ -60,7 +61,9 @@ describe Gitlab::GitAccess, lib: true do let(:actor) { deploy_key } context 'when the DeployKey has access to the project' do - before { deploy_key.projects << project } + before do + deploy_key.projects << project + end it 'allows pull access' do expect { pull_access_check }.not_to raise_error @@ -84,7 +87,9 @@ describe Gitlab::GitAccess, lib: true do context 'when actor is a User' do context 'when the User can read the project' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'allows pull access' do expect { pull_access_check }.not_to raise_error @@ -158,8 +163,50 @@ describe Gitlab::GitAccess, lib: true do end end + describe '#check_project_moved!' do + before do + project.team << [user, :master] + end + + context 'when a redirect was not followed to find the project' do + context 'pull code' do + it { expect { pull_access_check }.not_to raise_error } + end + + context 'push code' do + it { expect { push_access_check }.not_to raise_error } + end + end + + context 'when a redirect was followed to find the project' do + let(:redirected_path) { 'some/other-path' } + + context 'pull code' do + it { expect { pull_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) } + it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) } + + context 'http protocol' do + let(:protocol) { 'http' } + it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) } + end + end + + context 'push code' do + it { expect { push_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) } + it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) } + + context 'http protocol' do + let(:protocol) { 'http' } + it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) } + end + end + end + end + describe '#check_command_disabled!' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end context 'over http' do let(:protocol) { 'http' } @@ -196,7 +243,9 @@ describe Gitlab::GitAccess, lib: true do describe '#check_download_access!' do describe 'master permissions' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end context 'pull code' do it { expect { pull_access_check }.not_to raise_error } @@ -204,7 +253,9 @@ describe Gitlab::GitAccess, lib: true do end describe 'guest permissions' do - before { project.team << [user, :guest] } + before do + project.team << [user, :guest] + end context 'pull code' do it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') } @@ -253,7 +304,9 @@ describe Gitlab::GitAccess, lib: true do context 'pull code' do context 'when project is authorized' do - before { key.projects << project } + before do + key.projects << project + end it { expect { pull_access_check }.not_to raise_error } end @@ -292,7 +345,9 @@ describe Gitlab::GitAccess, lib: true do end describe 'reporter user' do - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end context 'pull code' do it { expect { pull_access_check }.not_to raise_error } @@ -303,7 +358,9 @@ describe Gitlab::GitAccess, lib: true do let(:user) { create(:admin) } context 'when member of the project' do - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end context 'pull code' do it { expect { pull_access_check }.not_to raise_error } @@ -328,7 +385,9 @@ describe Gitlab::GitAccess, lib: true do end describe '#check_push_access!' do - before { merge_into_protected_branch } + before do + merge_into_protected_branch + end let(:unprotected_branch) { 'unprotected_branch' } let(:changes) do @@ -457,19 +516,25 @@ describe Gitlab::GitAccess, lib: true do [%w(feature exact), ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type| context do - before { create(:protected_branch, name: protected_branch_name, project: project) } + before do + create(:protected_branch, name: protected_branch_name, project: project) + end run_permission_checks(permissions_matrix) end context "when developers are allowed to push into the #{protected_branch_type} protected branch" do - before { create(:protected_branch, :developers_can_push, name: protected_branch_name, project: project) } + before do + create(:protected_branch, :developers_can_push, name: protected_branch_name, project: project) + end run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) end context "developers are allowed to merge into the #{protected_branch_type} protected branch" do - before { create(:protected_branch, :developers_can_merge, name: protected_branch_name, project: project) } + before do + create(:protected_branch, :developers_can_merge, name: protected_branch_name, project: project) + end context "when a merge request exists for the given source/target branch" do context "when the merge request is in progress" do @@ -496,13 +561,17 @@ describe Gitlab::GitAccess, lib: true do end context "when developers are allowed to push and merge into the #{protected_branch_type} protected branch" do - before { create(:protected_branch, :developers_can_merge, :developers_can_push, name: protected_branch_name, project: project) } + before do + create(:protected_branch, :developers_can_merge, :developers_can_push, name: protected_branch_name, project: project) + end run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) end context "when no one is allowed to push to the #{protected_branch_name} protected branch" do - before { create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) } + before do + create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) + end run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }, master: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }, @@ -515,7 +584,9 @@ describe Gitlab::GitAccess, lib: true do let(:authentication_abilities) { build_authentication_abilities } context 'when project is authorized' do - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') } end @@ -549,7 +620,9 @@ describe Gitlab::GitAccess, lib: true do let(:can_push) { true } context 'when project is authorized' do - before { key.projects << project } + before do + key.projects << project + end it { expect { push_access_check }.not_to raise_error } end @@ -579,7 +652,9 @@ describe Gitlab::GitAccess, lib: true do let(:can_push) { false } context 'when project is authorized' do - before { key.projects << project } + before do + key.projects << project + end it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') } end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index a1eb95750ba..797ec8cb23e 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do - let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) } + let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) } let(:project) { create(:project, :repository) } let(:user) { create(:user) } + let(:redirected_path) { nil } let(:authentication_abilities) do [ :read_project, diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notifications_spec.rb index b87dacb175b..e5c9e06a15e 100644 --- a/spec/lib/gitlab/gitaly_client/notifications_spec.rb +++ b/spec/lib/gitlab/gitaly_client/notifications_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' describe Gitlab::GitalyClient::Notifications do describe '#post_receive' do let(:project) { create(:empty_project) } - let(:repo_path) { project.repository.path_to_repo } + let(:storage_name) { project.repository_storage } + let(:relative_path) { project.path_with_namespace + '.git' } subject { described_class.new(project.repository) } it 'sends a post_receive message' do expect_any_instance_of(Gitaly::Notifications::Stub). - to receive(:post_receive).with(gitaly_request_with_repo_path(repo_path)) + to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path)) subject.post_receive end diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb index d8cd2dcbd2a..2ea44ef74b0 100644 --- a/spec/lib/gitlab/gitaly_client/ref_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe Gitlab::GitalyClient::Ref do let(:project) { create(:empty_project) } - let(:repo_path) { project.repository.path_to_repo } + let(:storage_name) { project.repository_storage } + let(:relative_path) { project.path_with_namespace + '.git' } let(:client) { described_class.new(project.repository) } before do @@ -19,7 +20,8 @@ describe Gitlab::GitalyClient::Ref do describe '#branch_names' do it 'sends a find_all_branch_names message' do expect_any_instance_of(Gitaly::Ref::Stub). - to receive(:find_all_branch_names).with(gitaly_request_with_repo_path(repo_path)). + to receive(:find_all_branch_names). + with(gitaly_request_with_path(storage_name, relative_path)). and_return([]) client.branch_names @@ -29,7 +31,8 @@ describe Gitlab::GitalyClient::Ref do describe '#tag_names' do it 'sends a find_all_tag_names message' do expect_any_instance_of(Gitaly::Ref::Stub). - to receive(:find_all_tag_names).with(gitaly_request_with_repo_path(repo_path)). + to receive(:find_all_tag_names). + with(gitaly_request_with_path(storage_name, relative_path)). and_return([]) client.tag_names @@ -39,7 +42,8 @@ describe Gitlab::GitalyClient::Ref do describe '#default_branch_name' do it 'sends a find_default_branch_name message' do expect_any_instance_of(Gitaly::Ref::Stub). - to receive(:find_default_branch_name).with(gitaly_request_with_repo_path(repo_path)). + to receive(:find_default_branch_name). + with(gitaly_request_with_path(storage_name, relative_path)). and_return(double(name: 'foo')) client.default_branch_name @@ -49,7 +53,8 @@ describe Gitlab::GitalyClient::Ref do describe '#local_branches' do it 'sends a find_local_branches message' do expect_any_instance_of(Gitaly::Ref::Stub). - to receive(:find_local_branches).with(gitaly_request_with_repo_path(repo_path)). + to receive(:find_local_branches). + with(gitaly_request_with_path(storage_name, relative_path)). and_return([]) client.local_branches diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 95ecba67532..ce7b18b784a 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -5,7 +5,9 @@ require 'spec_helper' describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do describe '.stub' do # Notice that this is referring to gRPC "stubs", not rspec stubs - before { described_class.clear_stubs! } + before do + described_class.clear_stubs! + end context 'when passed a UNIX socket address' do it 'passes the address as-is to GRPC' do @@ -41,7 +43,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do let(:real_feature_name) { "gitaly_#{feature_name}" } context 'when Gitaly is disabled' do - before { allow(described_class).to receive(:enabled?).and_return(false) } + before do + allow(described_class).to receive(:enabled?).and_return(false) + end it 'returns false' do expect(described_class.feature_enabled?(feature_name)).to be(false) @@ -66,7 +70,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do end context "when the feature flag is set to disable" do - before { Feature.get(real_feature_name).disable } + before do + Feature.get(real_feature_name).disable + end it 'returns false' do expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false) @@ -74,7 +80,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do end context "when the feature flag is set to enable" do - before { Feature.get(real_feature_name).enable } + before do + Feature.get(real_feature_name).enable + end it 'returns true' do expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true) @@ -82,7 +90,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do end context "when the feature flag is set to a percentage of time" do - before { Feature.get(real_feature_name).enable_percentage_of_time(70) } + before do + Feature.get(real_feature_name).enable_percentage_of_time(70) + end it 'bases the result on pseudo-random numbers' do expect(Random).to receive(:rand).and_return(0.3) @@ -104,7 +114,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do end context "when the feature flag is set to disable" do - before { Feature.get(real_feature_name).disable } + before do + Feature.get(real_feature_name).disable + end it 'returns false' do expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false) diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb index 5d0ed1522b3..08010c2d0e2 100644 --- a/spec/lib/gitlab/group_hierarchy_spec.rb +++ b/spec/lib/gitlab/group_hierarchy_spec.rb @@ -17,6 +17,12 @@ describe Gitlab::GroupHierarchy, :postgresql do it 'includes all of the ancestors' do expect(relation).to include(parent, child1) end + + it 'uses ancestors_base #initialize argument' do + relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors + + expect(relation).to include(parent, child1, child2) + end end describe '#base_and_descendants' do @@ -31,6 +37,12 @@ describe Gitlab::GroupHierarchy, :postgresql do it 'includes all the descendants' do expect(relation).to include(child1, child2) end + + it 'uses descendants_base #initialize argument' do + relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants + + expect(relation).to include(parent, child1, child2) + end end describe '#all_groups' do @@ -49,5 +61,17 @@ describe Gitlab::GroupHierarchy, :postgresql do it 'includes the descendants' do expect(relation).to include(child2) end + + it 'uses ancestors_base #initialize argument for ancestors' do + relation = described_class.new(Group.where(id: child1.id), Group.where(id: Group.maximum(:id).succ)).all_groups + + expect(relation).to include(parent) + end + + it 'uses descendants_base #initialize argument for descendants' do + relation = described_class.new(Group.where(id: Group.maximum(:id).succ), Group.where(id: child1.id)).all_groups + + expect(relation).to include(child2) + end end end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index c2bb9f9a166..fdc5b484ef1 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -15,7 +15,9 @@ describe Gitlab::Highlight, lib: true do Gitlab::Highlight.new(blob.path, blob.data, repository: repository) end - before { project.change_head('gitattributes') } + before do + project.change_head('gitattributes') + end describe 'basic language selection' do let(:path) { 'custom-highlighting/test.gitlab-custom' } diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb index a3dbeaa3753..0dba4132101 100644 --- a/spec/lib/gitlab/i18n_spec.rb +++ b/spec/lib/gitlab/i18n_spec.rb @@ -4,7 +4,9 @@ describe Gitlab::I18n, lib: true do let(:user) { create(:user, preferred_language: 'es') } describe '.locale=' do - after { described_class.use_default_locale } + after do + described_class.use_default_locale + end it 'sets the locale based on current user preferred language' do described_class.locale = user.preferred_language diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 412eb33b35b..a5f09f1856e 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -88,6 +88,9 @@ merge_requests: - head_pipeline merge_request_diff: - merge_request +- merge_request_diff_files +merge_request_diff_files: +- merge_request_diff pipelines: - project - user diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index e3599d6fe59..98c117b4cd8 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2821,9 +2821,11 @@ "committer_email": "dmitriy.zaporozhets@gmail.com" } ], - "utf8_st_diffs": [ + "merge_request_diff_files": [ { - "diff": "Binary files a/.DS_Store and /dev/null differ\n", + "merge_request_diff_id": 27, + "relative_order": 0, + "utf8_diff": "Binary files a/.DS_Store and /dev/null differ\n", "new_path": ".DS_Store", "old_path": ".DS_Store", "a_mode": "100644", @@ -2834,7 +2836,9 @@ "too_large": false }, { - "diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n", + "merge_request_diff_id": 27, + "relative_order": 1, + "utf8_diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n", "new_path": ".gitignore", "old_path": ".gitignore", "a_mode": "100644", @@ -2845,7 +2849,9 @@ "too_large": false }, { - "diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n", + "merge_request_diff_id": 27, + "relative_order": 2, + "utf8_diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n", "new_path": ".gitmodules", "old_path": ".gitmodules", "a_mode": "100644", @@ -2856,7 +2862,9 @@ "too_large": false }, { - "diff": "Binary files a/files/.DS_Store and /dev/null differ\n", + "merge_request_diff_id": 27, + "relative_order": 3, + "utf8_diff": "Binary files a/files/.DS_Store and /dev/null differ\n", "new_path": "files/.DS_Store", "old_path": "files/.DS_Store", "a_mode": "100644", @@ -2867,7 +2875,9 @@ "too_large": false }, { - "diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n", + "merge_request_diff_id": 27, + "relative_order": 4, + "utf8_diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n", "new_path": "files/ruby/feature.rb", "old_path": "files/ruby/feature.rb", "a_mode": "0", @@ -2878,7 +2888,9 @@ "too_large": false }, { - "diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", + "merge_request_diff_id": 27, + "relative_order": 5, + "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", "new_path": "files/ruby/popen.rb", "old_path": "files/ruby/popen.rb", "a_mode": "100644", @@ -2889,7 +2901,9 @@ "too_large": false }, { - "diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n", + "merge_request_diff_id": 27, + "relative_order": 6, + "utf8_diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n", "new_path": "files/ruby/regex.rb", "old_path": "files/ruby/regex.rb", "a_mode": "100644", @@ -2900,7 +2914,9 @@ "too_large": false }, { - "diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n", + "merge_request_diff_id": 27, + "relative_order": 7, + "utf8_diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n", "new_path": "gitlab-grack", "old_path": "gitlab-grack", "a_mode": "0", @@ -2911,7 +2927,9 @@ "too_large": false }, { - "diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n", + "merge_request_diff_id": 27, + "relative_order": 8, + "utf8_diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n", "new_path": "gitlab-shell", "old_path": "gitlab-shell", "a_mode": "0", diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 14338515892..c11b15a811b 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -86,8 +86,13 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do it 'has the correct data for merge request st_diffs' do # makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+ + # one MergeRequestDiff uses the new format, where st_diffs is expected to be nil - expect(MergeRequestDiff.where.not(st_diffs: nil).count).to eq(9) + expect(MergeRequestDiff.where.not(st_diffs: nil).count).to eq(8) + end + + it 'has the correct data for merge request diff files' do + expect(MergeRequestDiffFile.where.not(diff: nil).count).to eq(9) end it 'has the correct time for merge request st_commits' do diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 5aeb29b7fec..e52f79513f1 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -83,6 +83,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json['merge_requests'].first['merge_request_diff']['utf8_st_diffs']).not_to be_nil end + it 'has merge request diff files' do + expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_files']).not_to be_empty + end + it 'has merge requests comments' do expect(saved_project_json['merge_requests'].first['notes']).not_to be_empty end @@ -145,6 +149,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(project_tree_saver.save).to be true end + it 'does not complain about non UTF-8 characters in MR diff files' do + ActiveRecord::Base.connection.execute("UPDATE merge_request_diff_files SET diff = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'") + + expect(project_tree_saver.save).to be true + end + context 'group members' do let(:user2) { create(:user, email: 'group@member.com') } let(:member_emails) do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 50ff6ecc1e0..fadd3ad1330 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -172,6 +172,17 @@ MergeRequestDiff: - real_size - head_commit_sha - start_commit_sha +MergeRequestDiffFile: +- merge_request_diff_id +- relative_order +- new_file +- renamed_file +- deleted_file +- new_path +- old_path +- a_mode +- b_mode +- too_large Ci::Pipeline: - id - project_id diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index 91f9d06b85a..e8c599a95ee 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Gitlab::Kubernetes do + include KubernetesHelpers include described_class describe '#container_exec_url' do @@ -36,4 +37,13 @@ describe Gitlab::Kubernetes do it { expect(result.query).to match(/\Acontainer=container\+1&/) } end end + + describe '#filter_by_label' do + it 'returns matching labels' do + matching_items = [kube_pod(app: 'foo')] + items = matching_items + [kube_pod] + + expect(filter_by_label(items, app: 'foo')).to eq(matching_items) + end + end end diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb index 563c074017a..9454878b057 100644 --- a/spec/lib/gitlab/ldap/adapter_spec.rb +++ b/spec/lib/gitlab/ldap/adapter_spec.rb @@ -74,13 +74,17 @@ describe Gitlab::LDAP::Adapter, lib: true do subject { adapter.dn_matches_filter?(:dn, :filter) } context "when the search result is non-empty" do - before { allow(adapter).to receive(:ldap_search).and_return([:foo]) } + before do + allow(adapter).to receive(:ldap_search).and_return([:foo]) + end it { is_expected.to be_truthy } end context "when the search result is empty" do - before { allow(adapter).to receive(:ldap_search).and_return([]) } + before do + allow(adapter).to receive(:ldap_search).and_return([]) + end it { is_expected.to be_falsey } end @@ -91,13 +95,17 @@ describe Gitlab::LDAP::Adapter, lib: true do context "when the search is successful" do context "and the result is non-empty" do - before { allow(ldap).to receive(:search).and_return([:foo]) } + before do + allow(ldap).to receive(:search).and_return([:foo]) + end it { is_expected.to eq [:foo] } end context "and the result is empty" do - before { allow(ldap).to receive(:search).and_return([]) } + before do + allow(ldap).to receive(:search).and_return([]) + end it { is_expected.to eq [] } end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index a0eda685ca3..f0a1dd22fee 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -173,7 +173,9 @@ describe Gitlab::LDAP::User, lib: true do context 'signup' do context 'dont block on create' do - before { configure_block(false) } + before do + configure_block(false) + end it do ldap_user.save @@ -183,7 +185,9 @@ describe Gitlab::LDAP::User, lib: true do end context 'block on create' do - before { configure_block(true) } + before do + configure_block(true) + end it do ldap_user.save @@ -200,7 +204,9 @@ describe Gitlab::LDAP::User, lib: true do end context 'dont block on create' do - before { configure_block(false) } + before do + configure_block(false) + end it do ldap_user.save @@ -210,7 +216,9 @@ describe Gitlab::LDAP::User, lib: true do end context 'block on create' do - before { configure_block(true) } + before do + configure_block(true) + end it do ldap_user.save diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb index 168090d5b5c..88107536c9e 100644 --- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb +++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb @@ -6,7 +6,9 @@ describe Gitlab::Middleware::RailsQueueDuration do let(:env) { {} } let(:transaction) { double(:transaction) } - before { expect(app).to receive(:call).with(env).and_return('yay') } + before do + expect(app).to receive(:call).with(env).and_return('yay') + end describe '#call' do it 'calls the app when metrics are disabled' do @@ -15,7 +17,9 @@ describe Gitlab::Middleware::RailsQueueDuration do end context 'when metrics are enabled' do - before { allow(Gitlab::Metrics).to receive(:current_transaction).and_return(transaction) } + before do + allow(Gitlab::Metrics).to receive(:current_transaction).and_return(transaction) + end it 'calls the app when metrics are enabled but no timing header is found' do expect(middleware.call(env)).to eq('yay') diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb index 8aaeb5779d3..19ab17419fc 100644 --- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb +++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb @@ -55,7 +55,9 @@ describe Gitlab::OAuth::AuthHash, lib: true do end context 'email not provided' do - before { info_hash.delete(:email) } + before do + info_hash.delete(:email) + end it 'generates a temp email' do expect( auth_hash.email).to start_with('temp-email-for-oauth') @@ -63,7 +65,9 @@ describe Gitlab::OAuth::AuthHash, lib: true do end context 'username not provided' do - before { info_hash.delete(:nickname) } + before do + info_hash.delete(:nickname) + end it 'takes the first part of the email as username' do expect(auth_hash.username).to eql 'onur.kucuk_ABC-123' @@ -71,7 +75,9 @@ describe Gitlab::OAuth::AuthHash, lib: true do end context 'name not provided' do - before { info_hash.delete(:name) } + before do + info_hash.delete(:name) + end it 'concats first and lastname as the name' do expect(auth_hash.name).to eql name_utf8 diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 8943d1aa488..ea29cb9caf1 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -112,7 +112,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'with new allow_single_sign_on enabled syntax' do - before { stub_omniauth_config(allow_single_sign_on: ['twitter']) } + before do + stub_omniauth_config(allow_single_sign_on: ['twitter']) + end it "creates a user from Omniauth" do oauth_user.save @@ -125,7 +127,9 @@ describe Gitlab::OAuth::User, lib: true do end context "with old allow_single_sign_on enabled syntax" do - before { stub_omniauth_config(allow_single_sign_on: true) } + before do + stub_omniauth_config(allow_single_sign_on: true) + end it "creates a user from Omniauth" do oauth_user.save @@ -138,14 +142,20 @@ describe Gitlab::OAuth::User, lib: true do end context 'with new allow_single_sign_on disabled syntax' do - before { stub_omniauth_config(allow_single_sign_on: []) } + before do + stub_omniauth_config(allow_single_sign_on: []) + end + it 'throws an error' do expect{ oauth_user.save }.to raise_error StandardError end end context 'with old allow_single_sign_on disabled (Default)' do - before { stub_omniauth_config(allow_single_sign_on: false) } + before do + stub_omniauth_config(allow_single_sign_on: false) + end + it 'throws an error' do expect{ oauth_user.save }.to raise_error StandardError end @@ -153,21 +163,30 @@ describe Gitlab::OAuth::User, lib: true do end context "with auto_link_ldap_user disabled (default)" do - before { stub_omniauth_config(auto_link_ldap_user: false) } + before do + stub_omniauth_config(auto_link_ldap_user: false) + end + include_examples "to verify compliance with allow_single_sign_on" end context "with auto_link_ldap_user enabled" do - before { stub_omniauth_config(auto_link_ldap_user: true) } + before do + stub_omniauth_config(auto_link_ldap_user: true) + end context "and no LDAP provider defined" do - before { stub_ldap_config(providers: []) } + before do + stub_ldap_config(providers: []) + end include_examples "to verify compliance with allow_single_sign_on" end context "and at least one LDAP provider is defined" do - before { stub_ldap_config(providers: %w(ldapmain)) } + before do + stub_ldap_config(providers: %w(ldapmain)) + end context "and a corresponding LDAP person" do before do @@ -238,7 +257,9 @@ describe Gitlab::OAuth::User, lib: true do end context "and no corresponding LDAP person" do - before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) } + before do + allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) + end include_examples "to verify compliance with allow_single_sign_on" end @@ -248,11 +269,16 @@ describe Gitlab::OAuth::User, lib: true do describe 'blocking' do let(:provider) { 'twitter' } - before { stub_omniauth_config(allow_single_sign_on: ['twitter']) } + + before do + stub_omniauth_config(allow_single_sign_on: ['twitter']) + end context 'signup with omniauth only' do context 'dont block on create' do - before { stub_omniauth_config(block_auto_created_users: false) } + before do + stub_omniauth_config(block_auto_created_users: false) + end it do oauth_user.save @@ -262,7 +288,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'block on create' do - before { stub_omniauth_config(block_auto_created_users: true) } + before do + stub_omniauth_config(block_auto_created_users: true) + end it do oauth_user.save @@ -284,7 +312,9 @@ describe Gitlab::OAuth::User, lib: true do context "and no account for the LDAP user" do context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) + end it do oauth_user.save @@ -294,7 +324,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) + end it do oauth_user.save @@ -308,7 +340,9 @@ describe Gitlab::OAuth::User, lib: true do let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) + end it do oauth_user.save @@ -318,7 +352,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) + end it do oauth_user.save @@ -336,7 +372,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'dont block on create' do - before { stub_omniauth_config(block_auto_created_users: false) } + before do + stub_omniauth_config(block_auto_created_users: false) + end it do oauth_user.save @@ -346,7 +384,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'block on create' do - before { stub_omniauth_config(block_auto_created_users: true) } + before do + stub_omniauth_config(block_auto_created_users: true) + end it do oauth_user.save @@ -356,7 +396,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) + end it do oauth_user.save @@ -366,7 +408,9 @@ describe Gitlab::OAuth::User, lib: true do end context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) + end it do oauth_user.save diff --git a/spec/lib/gitlab/slash_commands/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb index 5b9173d3d3f..f44a562dc63 100644 --- a/spec/lib/gitlab/slash_commands/command_definition_spec.rb +++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::SlashCommands::CommandDefinition do +describe Gitlab::QuickActions::CommandDefinition do subject { described_class.new(:command) } describe "#all_names" do diff --git a/spec/lib/gitlab/slash_commands/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb index 33b49a5ddf9..a4bb3f911d7 100644 --- a/spec/lib/gitlab/slash_commands/dsl_spec.rb +++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe Gitlab::SlashCommands::Dsl do +describe Gitlab::QuickActions::Dsl do before :all do DummyClass = Struct.new(:project) do - include Gitlab::SlashCommands::Dsl # rubocop:disable RSpec/DescribedClass + include Gitlab::QuickActions::Dsl # rubocop:disable RSpec/DescribedClass desc 'A command with no args' command :no_args, :none do diff --git a/spec/lib/gitlab/slash_commands/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb index d7f77486b3e..9d32938e155 100644 --- a/spec/lib/gitlab/slash_commands/extractor_spec.rb +++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe Gitlab::SlashCommands::Extractor do +describe Gitlab::QuickActions::Extractor do let(:definitions) do Class.new do - include Gitlab::SlashCommands::Dsl + include Gitlab::QuickActions::Dsl command(:reopen, :open) { } command(:assign) { } diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb index 8b77c925705..593aa5038ad 100644 --- a/spec/lib/gitlab/redis_spec.rb +++ b/spec/lib/gitlab/redis_spec.rb @@ -108,11 +108,18 @@ describe Gitlab::Redis do end describe '.with' do - before { clear_pool } - after { clear_pool } + before do + clear_pool + end + + after do + clear_pool + end context 'when running not on sidekiq workers' do - before { allow(Sidekiq).to receive(:server?).and_return(false) } + before do + allow(Sidekiq).to receive(:server?).and_return(false) + end it 'instantiates a connection pool with size 5' do expect(ConnectionPool).to receive(:new).with(size: 5).and_call_original diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index f9025397107..efea4f429bf 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -4,24 +4,44 @@ describe ::Gitlab::RepoPath do describe '.parse' do set(:project) { create(:project) } - it 'parses a full repository path' do - expect(described_class.parse(project.repository.path)).to eq([project, false]) - end + context 'a repository storage path' do + it 'parses a full repository path' do + expect(described_class.parse(project.repository.path)).to eq([project, false, nil]) + end - it 'parses a full wiki path' do - expect(described_class.parse(project.wiki.repository.path)).to eq([project, true]) + it 'parses a full wiki path' do + expect(described_class.parse(project.wiki.repository.path)).to eq([project, true, nil]) + end end - it 'parses a relative repository path' do - expect(described_class.parse(project.full_path + '.git')).to eq([project, false]) - end + context 'a relative path' do + it 'parses a relative repository path' do + expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil]) + end - it 'parses a relative wiki path' do - expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true]) - end + it 'parses a relative wiki path' do + expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil]) + end + + it 'parses a relative path starting with /' do + expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil]) + end + + context 'of a redirected project' do + let(:redirect) { project.route.create_redirect('foo/bar') } + + it 'parses a relative repository path' do + expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar']) + end + + it 'parses a relative wiki path' do + expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki']) + end - it 'parses a relative path starting with /' do - expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false]) + it 'parses a relative path starting with /' do + expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar']) + end + end end end @@ -43,4 +63,33 @@ describe ::Gitlab::RepoPath do ) end end + + describe '.find_project' do + let(:project) { create(:empty_project) } + let(:redirect) { project.route.create_redirect('foo/bar/baz') } + + context 'when finding a project by its canonical path' do + context 'when the cases match' do + it 'returns the project and false' do + expect(described_class.find_project(project.full_path)).to eq([project, false]) + end + end + + context 'when the cases do not match' do + # This is slightly different than web behavior because on the web it is + # easy and safe to redirect someone to the correctly-cased URL. For git + # requests, we should accept wrongly-cased URLs because it is a pain to + # block people's git operations and force them to update remote URLs. + it 'returns the project and false' do + expect(described_class.find_project(project.full_path.upcase)).to eq([project, false]) + end + end + end + + context 'when finding a project via a redirect' do + it 'returns the project and true' do + expect(described_class.find_project(redirect.path)).to eq([project, true]) + end + end + end end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index b106d156b75..a4d2367b72a 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -31,11 +31,17 @@ describe Gitlab::Saml::User, lib: true do allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) end - before { stub_basic_saml_config } + before do + stub_basic_saml_config + end describe 'account exists on server' do - before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } + before do + stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) + end + let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } + context 'and should bind with SAML' do it 'adds the SAML identity to the existing user' do saml_user.save @@ -57,7 +63,10 @@ describe Gitlab::Saml::User, lib: true do end end - before { stub_saml_group_config(%w(Interns)) } + before do + stub_saml_group_config(%w(Interns)) + end + context 'are defined but the user does not belong there' do it 'does not mark the user as external' do saml_user.save @@ -80,7 +89,9 @@ describe Gitlab::Saml::User, lib: true do describe 'no account exists on server' do shared_examples 'to verify compliance with allow_single_sign_on' do context 'with allow_single_sign_on enabled' do - before { stub_omniauth_config(allow_single_sign_on: ['saml']) } + before do + stub_omniauth_config(allow_single_sign_on: ['saml']) + end it 'creates a user from SAML' do saml_user.save @@ -93,14 +104,20 @@ describe Gitlab::Saml::User, lib: true do end context 'with allow_single_sign_on default (["saml"])' do - before { stub_omniauth_config(allow_single_sign_on: ['saml']) } + before do + stub_omniauth_config(allow_single_sign_on: ['saml']) + end + it 'does not throw an error' do expect{ saml_user.save }.not_to raise_error end end context 'with allow_single_sign_on disabled' do - before { stub_omniauth_config(allow_single_sign_on: false) } + before do + stub_omniauth_config(allow_single_sign_on: false) + end + it 'throws an error' do expect{ saml_user.save }.to raise_error StandardError end @@ -128,15 +145,22 @@ describe Gitlab::Saml::User, lib: true do end context 'with auto_link_ldap_user disabled (default)' do - before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) } + before do + stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) + end + include_examples 'to verify compliance with allow_single_sign_on' end context 'with auto_link_ldap_user enabled' do - before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) } + before do + stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) + end context 'and at least one LDAP provider is defined' do - before { stub_ldap_config(providers: %w(ldapmain)) } + before do + stub_ldap_config(providers: %w(ldapmain)) + end context 'and a corresponding LDAP person' do before do @@ -239,11 +263,15 @@ describe Gitlab::Saml::User, lib: true do end describe 'blocking' do - before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } + before do + stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) + end context 'signup with SAML only' do context 'dont block on create' do - before { stub_omniauth_config(block_auto_created_users: false) } + before do + stub_omniauth_config(block_auto_created_users: false) + end it 'does not block the user' do saml_user.save @@ -253,7 +281,9 @@ describe Gitlab::Saml::User, lib: true do end context 'block on create' do - before { stub_omniauth_config(block_auto_created_users: true) } + before do + stub_omniauth_config(block_auto_created_users: true) + end it 'blocks user' do saml_user.save @@ -270,7 +300,9 @@ describe Gitlab::Saml::User, lib: true do end context 'dont block on create' do - before { stub_omniauth_config(block_auto_created_users: false) } + before do + stub_omniauth_config(block_auto_created_users: false) + end it do saml_user.save @@ -280,7 +312,9 @@ describe Gitlab::Saml::User, lib: true do end context 'block on create' do - before { stub_omniauth_config(block_auto_created_users: true) } + before do + stub_omniauth_config(block_auto_created_users: true) + end it do saml_user.save diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb index 519eb1b274f..1bc6536439e 100644 --- a/spec/lib/gitlab/serializer/pagination_spec.rb +++ b/spec/lib/gitlab/serializer/pagination_spec.rb @@ -22,7 +22,9 @@ describe Gitlab::Serializer::Pagination do let(:params) { { page: 1, per_page: 2 } } context 'when a multiple resources are present in relation' do - before { create_list(:user, 3) } + before do + create_list(:user, 3) + end it 'correctly paginates the resource' do expect(subject.count).to be 2 diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb index 13e6953147b..28d7f9858c3 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/slash_commands/command_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Command, service: true do +describe Gitlab::SlashCommands::Command, service: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } @@ -93,19 +93,19 @@ describe Gitlab::ChatCommands::Command, service: true do context 'IssueShow is triggered' do let(:params) { { text: 'issue show 123' } } - it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) } + it { is_expected.to eq(Gitlab::SlashCommands::IssueShow) } end context 'IssueCreate is triggered' do let(:params) { { text: 'issue create my title' } } - it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) } + it { is_expected.to eq(Gitlab::SlashCommands::IssueNew) } end context 'IssueSearch is triggered' do let(:params) { { text: 'issue search my query' } } - it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) } + it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) } end end end diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb index 46dbdeae37c..d919f7260db 100644 --- a/spec/lib/gitlab/chat_commands/deploy_spec.rb +++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Deploy, service: true do +describe Gitlab::SlashCommands::Deploy, service: true do describe '#execute' do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/chat_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb index 84c22328064..4de50d4a8bb 100644 --- a/spec/lib/gitlab/chat_commands/issue_new_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::IssueNew, service: true do +describe Gitlab::SlashCommands::IssueNew, service: true do describe '#execute' do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb index 551ccb79a58..06fff0afc50 100644 --- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::IssueSearch, service: true do +describe Gitlab::SlashCommands::IssueSearch, service: true do describe '#execute' do let!(:issue) { create(:issue, project: project, title: 'find me') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb index 1f20d0a44ce..1899f664ccd 100644 --- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::IssueShow, service: true do +describe Gitlab::SlashCommands::IssueShow, service: true do describe '#execute' do let(:issue) { create(:issue, project: project) } let(:project) { create(:empty_project) } diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb index ae41d75ab0c..ef3d217f7be 100644 --- a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::Access do +describe Gitlab::SlashCommands::Presenters::Access do describe '#access_denied' do subject { described_class.new.access_denied } diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb index dc2dd300072..dee3c77db27 100644 --- a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::Deploy do +describe Gitlab::SlashCommands::Presenters::Deploy do let(:build) { create(:ci_build) } describe '#present' do diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb index 17fcdbc2452..7f81ebb47db 100644 --- a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::IssueNew do +describe Gitlab::SlashCommands::Presenters::IssueNew do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb index ec6d3e34a96..7e57a0addcb 100644 --- a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb @@ -1,10 +1,12 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::IssueSearch do +describe Gitlab::SlashCommands::Presenters::IssueSearch do let(:project) { create(:empty_project) } let(:message) { subject[:text] } - before { create_list(:issue, 2, project: project) } + before do + create_list(:issue, 2, project: project) + end subject { described_class.new(project.issues).present } diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb index 3916fc704a4..2a6ed860737 100644 --- a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::IssueShow do +describe Gitlab::SlashCommands::Presenters::IssueShow do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index 329d1d74970..bf45c8d16d6 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -52,7 +52,10 @@ describe Gitlab::Template::IssueTemplate do context 'when repo is bare or empty' do let(:empty_project) { create(:empty_project) } - before { empty_project.add_user(user, Gitlab::Access::MASTER) } + + before do + empty_project.add_user(user, Gitlab::Access::MASTER) + end it "returns empty array" do templates = subject.by_category('', empty_project) @@ -77,7 +80,9 @@ describe Gitlab::Template::IssueTemplate do context "when repo is empty" do let(:empty_project) { create(:empty_project) } - before { empty_project.add_user(user, Gitlab::Access::MASTER) } + before do + empty_project.add_user(user, Gitlab::Access::MASTER) + end it "raises file not found" do issue_template = subject.new('.gitlab/issue_templates/not_existent.md', empty_project) diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index 2b0056d9bab..8479f92c8df 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -52,7 +52,10 @@ describe Gitlab::Template::MergeRequestTemplate do context 'when repo is bare or empty' do let(:empty_project) { create(:empty_project) } - before { empty_project.add_user(user, Gitlab::Access::MASTER) } + + before do + empty_project.add_user(user, Gitlab::Access::MASTER) + end it "returns empty array" do templates = subject.by_category('', empty_project) @@ -77,7 +80,9 @@ describe Gitlab::Template::MergeRequestTemplate do context "when repo is empty" do let(:empty_project) { create(:empty_project) } - before { empty_project.add_user(user, Gitlab::Access::MASTER) } + before do + empty_project.add_user(user, Gitlab::Access::MASTER) + end it "raises file not found" do issue_template = subject.new('.gitlab/merge_request_templates/not_existent.md', empty_project) diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index b1999409170..ad19998dff4 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -212,7 +212,7 @@ describe Gitlab::Workhorse, lib: true do it 'includes a Repository param' do repo_param = { Repository: { - path: repo_path, + path: '', # deprecated field; grpc automatically creates it anyway storage_name: 'default', relative_path: project.full_path + '.git' } } diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb index 18726754517..e7022bd06f8 100644 --- a/spec/lib/json_web_token/rsa_token_spec.rb +++ b/spec/lib/json_web_token/rsa_token_spec.rb @@ -15,11 +15,15 @@ describe JSONWebToken::RSAToken do let(:rsa_token) { described_class.new(nil) } let(:rsa_encoded) { rsa_token.encoded } - before { allow_any_instance_of(described_class).to receive(:key).and_return(rsa_key) } + before do + allow_any_instance_of(described_class).to receive(:key).and_return(rsa_key) + end context 'token' do context 'for valid key to be validated' do - before { rsa_token['key'] = 'value' } + before do + rsa_token['key'] = 'value' + end subject { JWT.decode(rsa_encoded, rsa_key) } diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb index 3d955e4d774..d7e7560d962 100644 --- a/spec/lib/json_web_token/token_spec.rb +++ b/spec/lib/json_web_token/token_spec.rb @@ -3,7 +3,10 @@ describe JSONWebToken::Token do context 'custom parameters' do let(:value) { 'value' } - before { token[:key] = value } + + before do + token[:key] = value + end it { expect(token[:key]).to eq(value) } it { expect(token.payload).to include(key: value) } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ec6f6c42eac..980b24370d0 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -130,8 +130,13 @@ describe Notify do end context 'with a preferred language' do - before { Gitlab::I18n.locale = :es } - after { Gitlab::I18n.use_default_locale } + before do + Gitlab::I18n.locale = :es + end + + after do + Gitlab::I18n.use_default_locale + end it 'always generates the email using the default language' do is_expected.to have_body_text('foo, bar, and baz') @@ -581,7 +586,9 @@ describe Notify do let(:project) { create(:project, :repository) } let(:commit) { project.commit } - before(:each) { allow(note).to receive(:noteable).and_return(commit) } + before do + allow(note).to receive(:noteable).and_return(commit) + end subject { described_class.note_commit_email(recipient.id, note.id) } @@ -603,7 +610,10 @@ describe Notify do describe 'on a merge request' do let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } - before(:each) { allow(note).to receive(:noteable).and_return(merge_request) } + + before do + allow(note).to receive(:noteable).and_return(merge_request) + end subject { described_class.note_merge_request_email(recipient.id, note.id) } @@ -625,7 +635,10 @@ describe Notify do describe 'on an issue' do let(:issue) { create(:issue, project: project) } let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } - before(:each) { allow(note).to receive(:noteable).and_return(issue) } + + before do + allow(note).to receive(:noteable).and_return(issue) + end subject { described_class.note_issue_email(recipient.id, note.id) } @@ -687,7 +700,9 @@ describe Notify do let(:commit) { project.commit } let(:note) { create(:discussion_note_on_commit, commit_id: commit.id, project: project, author: note_author) } - before(:each) { allow(note).to receive(:noteable).and_return(commit) } + before do + allow(note).to receive(:noteable).and_return(commit) + end subject { described_class.note_commit_email(recipient.id, note.id) } @@ -711,7 +726,10 @@ describe Notify do let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) } let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } - before(:each) { allow(note).to receive(:noteable).and_return(merge_request) } + + before do + allow(note).to receive(:noteable).and_return(merge_request) + end subject { described_class.note_merge_request_email(recipient.id, note.id) } @@ -735,7 +753,10 @@ describe Notify do let(:issue) { create(:issue, project: project) } let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) } let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } - before(:each) { allow(note).to receive(:noteable).and_return(issue) } + + before do + allow(note).to receive(:noteable).and_return(issue) + end subject { described_class.note_issue_email(recipient.id, note.id) } diff --git a/spec/migrations/README.md b/spec/migrations/README.md new file mode 100644 index 00000000000..05d4f35db72 --- /dev/null +++ b/spec/migrations/README.md @@ -0,0 +1,87 @@ +# Testing migrations + +In order to reliably test a migration, we need to test it against a database +schema that this migration has been written for. In order to achieve that we +have some _migration helpers_ and RSpec test tag, called `:migration`. + +If you want to write a test for a migration consider adding `:migration` tag to +the test signature, like `describe SomeMigrationClass, :migration`. + +## How does it work? + +Adding a `:migration` tag to a test signature injects a few before / after +hooks to the test. + +The most important change is that adding a `:migration` tag adds a `before` +hook that will revert all migrations to the point that a migration under test +is not yet migrated. + +In other words, our custom RSpec hooks will find a previous migration, and +migrate the database **down** to the previous migration version. + +With this approach you can test a migration against a database schema that this +migration has been written for. + +Use `migrate!` helper to run the migration that is under test. + +The `after` hook will migrate the database **up** and reinstitutes the latest +schema version, so that the process does not affect subsequent specs and +ensures proper isolation. + +## Available helpers + +Use `table` helper to create a temporary `ActiveRecord::Base` derived model +for a table. + +Use `migrate!` helper to run the migration that is under test. It will not only +run migration, but will also bump the schema version in the `schema_migrations` +table. It is necessary because in the `after` hook we trigger the rest of +the migrations, and we need to know where to start. + +See `spec/support/migrations_helpers.rb` for all the available helpers. + +## An example + +```ruby +require 'spec_helper' + +# Load a migration class. + +require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb') + +describe MigratePipelineStages, :migration do + + # Create test data - pipeline and CI/CD jobs. + + let(:jobs) { table(:ci_builds) } + let(:stages) { table(:ci_stages) } + let(:pipelines) { table(:ci_pipelines) } + let(:projects) { table(:projects) } + + before do + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a') + jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') + jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test') + end + + # Test the migration. + + it 'correctly migrates pipeline stages' do + expect(stages.count).to be_zero + + migrate! + + expect(stages.count).to eq 2 + expect(stages.all.pluck(:name)).to match_array %w[test build] + end +end +``` + +## Best practices + +1. Use only one test example per migration unless there is a good reason to +use more. +1. Note that this type of tests do not run within the transaction, we use +a truncation database cleanup strategy. Do not depend on transaction being +present. diff --git a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb new file mode 100644 index 00000000000..1396d12e5a9 --- /dev/null +++ b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170607121233_convert_custom_notification_settings_to_columns') + +describe ConvertCustomNotificationSettingsToColumns, :migration do + let(:settings_params) do + [ + { level: 0, events: [:new_note] }, # disabled, single event + { level: 3, events: [:new_issue, :reopen_issue, :close_issue, :reassign_issue] }, # global, multiple events + { level: 5, events: described_class::EMAIL_EVENTS }, # custom, all events + { level: 5, events: [] } # custom, no events + ] + end + + let(:notification_settings_before) do + settings_params.map do |params| + events = {} + + params[:events].each do |event| + events[event] = true + end + + user = create(:user) + create_params = { user_id: user.id, level: params[:level], events: events } + notification_setting = described_class::NotificationSetting.create(create_params) + + [notification_setting, params] + end + end + + let(:notification_settings_after) do + settings_params.map do |params| + events = {} + + params[:events].each do |event| + events[event] = true + end + + user = create(:user) + create_params = events.merge(user_id: user.id, level: params[:level]) + notification_setting = described_class::NotificationSetting.create(create_params) + + [notification_setting, params] + end + end + + describe '#up' do + it 'migrates all settings where a custom event is enabled, even if they are not currently using the custom level' do + notification_settings_before + + described_class.new.up + + notification_settings_before.each do |(notification_setting, params)| + notification_setting.reload + + expect(notification_setting.read_attribute_before_type_cast(:events)).to be_nil + expect(notification_setting.level).to eq(params[:level]) + + described_class::EMAIL_EVENTS.each do |event| + # We don't set the others to false, just let them default to nil + expected = params[:events].include?(event) || nil + + expect(notification_setting.read_attribute(event)).to eq(expected) + end + end + end + end + + describe '#down' do + it 'creates a custom events hash for all settings where at least one event is enabled' do + notification_settings_after + + described_class.new.down + + notification_settings_after.each do |(notification_setting, params)| + notification_setting.reload + + expect(notification_setting.level).to eq(params[:level]) + + if params[:events].empty? + # We don't migrate empty settings + expect(notification_setting.events).to eq({}) + else + described_class::EMAIL_EVENTS.each do |event| + expected = params[:events].include?(event) + + expect(notification_setting.events[event]).to eq(expected) + expect(notification_setting.read_attribute(event)).to be_nil + end + end + end + end + + it 'reverts the database to the state it was in before' do + notification_settings_before + + described_class.new.up + described_class.new.down + + notification_settings_before.each do |(notification_setting, params)| + notification_setting.reload + + expect(notification_setting.level).to eq(params[:level]) + + if params[:events].empty? + # We don't migrate empty settings + expect(notification_setting.events).to eq({}) + else + described_class::EMAIL_EVENTS.each do |event| + expected = params[:events].include?(event) + + expect(notification_setting.events[event]).to eq(expected) + expect(notification_setting.read_attribute(event)).to be_nil + end + end + end + end + end +end diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb index 36e82729c23..4bd8d4ac0d1 100644 --- a/spec/migrations/rename_more_reserved_project_names_spec.rb +++ b/spec/migrations/rename_more_reserved_project_names_spec.rb @@ -17,7 +17,9 @@ describe RenameMoreReservedProjectNames, truncate: true do describe '#up' do context 'when project repository exists' do - before { project.create_repository } + before do + project.create_repository + end context 'when no exception is raised' do it 'renames project with reserved names' do diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb index 4fb7ed36884..05e021c2e32 100644 --- a/spec/migrations/rename_reserved_project_names_spec.rb +++ b/spec/migrations/rename_reserved_project_names_spec.rb @@ -17,7 +17,9 @@ describe RenameReservedProjectNames, truncate: true do describe '#up' do context 'when project repository exists' do - before { project.create_repository } + before do + project.create_repository + end context 'when no exception is raised' do it 'renames project with reserved names' do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index fa229542f70..166a4474abf 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -78,7 +78,9 @@ describe ApplicationSetting, models: true do # Upgraded databases will have this sort of content context 'repository_storages is a String, not an Array' do - before { setting.__send__(:raw_write_attribute, :repository_storages, 'default') } + before do + setting.__send__(:raw_write_attribute, :repository_storages, 'default') + end it { expect(setting.repository_storages_before_type_cast).to eq('default') } it { expect(setting.repository_storages).to eq(['default']) } diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 219db365a91..333f4139a96 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -21,22 +21,29 @@ describe BroadcastMessage, models: true do end describe '.current' do - it "returns last message if time match" do + it 'returns message if time match' do message = create(:broadcast_message) - expect(BroadcastMessage.current).to eq message + expect(BroadcastMessage.current).to include(message) end - it "returns nil if time not come" do + it 'returns multiple messages if time match' do + message1 = create(:broadcast_message) + message2 = create(:broadcast_message) + + expect(BroadcastMessage.current).to contain_exactly(message1, message2) + end + + it 'returns empty list if time not come' do create(:broadcast_message, :future) - expect(BroadcastMessage.current).to be_nil + expect(BroadcastMessage.current).to be_empty end - it "returns nil if time has passed" do + it 'returns empty list if time has passed' do create(:broadcast_message, :expired) - expect(BroadcastMessage.current).to be_nil + expect(BroadcastMessage.current).to be_empty end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b0716e04d3d..3816422fec6 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -21,6 +21,18 @@ describe Ci::Build, :models do it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:trace) } + describe '.manual_actions' do + let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) } + let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) } + let!(:manual_action) { create(:ci_build, :manual, pipeline: pipeline) } + + subject { described_class.manual_actions } + + it { is_expected.to include(manual_action) } + it { is_expected.to include(manual_but_succeeded) } + it { is_expected.not_to include(manual_but_created) } + end + describe '#actionize' do context 'when build is a created' do before do @@ -95,12 +107,18 @@ describe Ci::Build, :models do it { is_expected.to be_truthy } context 'is expired' do - before { build.update(artifacts_expire_at: Time.now - 7.days) } + before do + build.update(artifacts_expire_at: Time.now - 7.days) + end + it { is_expected.to be_falsy } end context 'is not expired' do - before { build.update(artifacts_expire_at: Time.now + 7.days) } + before do + build.update(artifacts_expire_at: Time.now + 7.days) + end + it { is_expected.to be_truthy } end end @@ -110,13 +128,17 @@ describe Ci::Build, :models do subject { build.artifacts_expired? } context 'is expired' do - before { build.update(artifacts_expire_at: Time.now - 7.days) } + before do + build.update(artifacts_expire_at: Time.now - 7.days) + end it { is_expected.to be_truthy } end context 'is not expired' do - before { build.update(artifacts_expire_at: Time.now + 7.days) } + before do + build.update(artifacts_expire_at: Time.now + 7.days) + end it { is_expected.to be_falsey } end @@ -141,7 +163,9 @@ describe Ci::Build, :models do context 'when artifacts_expire_at is specified' do let(:expire_at) { Time.now + 7.days } - before { build.artifacts_expire_at = expire_at } + before do + build.artifacts_expire_at = expire_at + end it { is_expected.to be_within(5).of(expire_at - Time.now) } end @@ -926,6 +950,10 @@ describe Ci::Build, :models do context 'when other build is retried' do let!(:retried_build) { Ci::Build.retry(other_build, user) } + before do + retried_build.success + end + it 'returns a retried build' do is_expected.to contain_exactly(retried_build) end @@ -1071,7 +1099,9 @@ describe Ci::Build, :models do describe '#has_expiring_artifacts?' do context 'when artifacts have expiration date set' do - before { build.update(artifacts_expire_at: 1.day.from_now) } + before do + build.update(artifacts_expire_at: 1.day.from_now) + end it 'has expiring artifacts' do expect(build).to have_expiring_artifacts @@ -1079,7 +1109,9 @@ describe Ci::Build, :models do end context 'when artifacts do not have expiration date set' do - before { build.update(artifacts_expire_at: nil) } + before do + build.update(artifacts_expire_at: nil) + end it 'does not have expiring artifacts' do expect(build).not_to have_expiring_artifacts diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb index 48116c7e701..d43c33d3807 100644 --- a/spec/models/ci/legacy_stage_spec.rb +++ b/spec/models/ci/legacy_stage_spec.rb @@ -55,6 +55,17 @@ describe Ci::LegacyStage, :models do expect(stage.groups.map(&:name)) .to eq %w[aaaaa rspec spinach] end + + context 'when a name is nil on legacy pipelines' do + before do + pipeline.builds.first.update_attribute(:name, nil) + end + + it 'returns an array of three groups' do + expect(stage.groups.map(&:name)) + .to eq ['', 'aaaaa', 'rspec', 'spinach'] + end + end end describe '#statuses_count' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b50c7700bd3..e86cbe8498a 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1156,7 +1156,9 @@ describe Ci::Pipeline, models: true do end context 'when pipeline is not stuck' do - before { create(:ci_runner, :shared, :online) } + before do + create(:ci_runner, :shared, :online) + end it 'is not stuck' do expect(pipeline).not_to be_stuck diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index ba247dcc5cf..6056d78da4e 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -206,19 +206,25 @@ eos it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy } context 'commit has no description' do - before { allow(commit).to receive(:description?).and_return(false) } + before do + allow(commit).to receive(:description?).and_return(false) + end it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy } end context "another_commit's description does not revert commit" do - before { allow(commit).to receive(:description).and_return("Foo Bar") } + before do + allow(commit).to receive(:description).and_return("Foo Bar") + end it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy } end context "another_commit's description reverts commit" do - before { allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") } + before do + allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") + end it { expect(commit.reverts_commit?(another_commit, user)).to be_truthy } end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index c50b8bf7b13..9262ce08987 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -31,7 +31,10 @@ describe CommitStatus, :models do describe '#author' do subject { commit_status.author } - before { commit_status.author = User.new } + + before do + commit_status.author = User.new + end it { is_expected.to eq(commit_status.user) } end @@ -50,14 +53,18 @@ describe CommitStatus, :models do subject { commit_status.started? } context 'without started_at' do - before { commit_status.started_at = nil } + before do + commit_status.started_at = nil + end it { is_expected.to be_falsey } end %w[running success failed].each do |status| context "if commit status is #{status}" do - before { commit_status.status = status } + before do + commit_status.status = status + end it { is_expected.to be_truthy } end @@ -65,7 +72,9 @@ describe CommitStatus, :models do %w[pending canceled].each do |status| context "if commit status is #{status}" do - before { commit_status.status = status } + before do + commit_status.status = status + end it { is_expected.to be_falsey } end @@ -77,7 +86,9 @@ describe CommitStatus, :models do %w[pending running].each do |state| context "if commit_status.status is #{state}" do - before { commit_status.status = state } + before do + commit_status.status = state + end it { is_expected.to be_truthy } end @@ -85,7 +96,9 @@ describe CommitStatus, :models do %w[success failed canceled].each do |state| context "if commit_status.status is #{state}" do - before { commit_status.status = state } + before do + commit_status.status = state + end it { is_expected.to be_falsey } end @@ -97,7 +110,9 @@ describe CommitStatus, :models do %w[success failed canceled].each do |state| context "if commit_status.status is #{state}" do - before { commit_status.status = state } + before do + commit_status.status = state + end it { is_expected.to be_truthy } end @@ -105,7 +120,9 @@ describe CommitStatus, :models do %w[pending running].each do |state| context "if commit_status.status is #{state}" do - before { commit_status.status = state } + before do + commit_status.status = state + end it { is_expected.to be_falsey } end @@ -271,7 +288,9 @@ describe CommitStatus, :models do subject { commit_status.before_sha } context 'when no before_sha is set for pipeline' do - before { pipeline.before_sha = nil } + before do + pipeline.before_sha = nil + end it 'returns blank sha' do is_expected.to eq(Gitlab::Git::BLANK_SHA) @@ -280,7 +299,10 @@ describe CommitStatus, :models do context 'for before_sha set for pipeline' do let(:value) { '1234' } - before { pipeline.before_sha = value } + + before do + pipeline.before_sha = value + end it 'returns the set value' do is_expected.to eq(value) diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb index 4829ef17a20..97b7e48bb3c 100644 --- a/spec/models/concerns/access_requestable_spec.rb +++ b/spec/models/concerns/access_requestable_spec.rb @@ -14,7 +14,9 @@ describe AccessRequestable do let(:group) { create(:group, :public, :access_requestable) } let(:user) { create(:user) } - before { group.request_access(user) } + before do + group.request_access(user) + end it { expect(group.requesters.exists?(user_id: user)).to be_truthy } end @@ -32,7 +34,9 @@ describe AccessRequestable do let(:project) { create(:empty_project, :public, :access_requestable) } let(:user) { create(:user) } - before { project.request_access(user) } + before do + project.request_access(user) + end it { expect(project.requesters.exists?(user_id: user)).to be_truthy } end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 27890e33b49..1a9bda64191 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -200,7 +200,9 @@ describe Issuable do let(:project) { issue.project } context 'user is not a participant in the issue' do - before { allow(issue).to receive(:participants).with(user).and_return([]) } + before do + allow(issue).to receive(:participants).with(user).and_return([]) + end it 'returns false when no subcription exists' do expect(issue.subscribed?(user, project)).to be_falsey @@ -220,7 +222,9 @@ describe Issuable do end context 'user is a participant in the issue' do - before { allow(issue).to receive(:participants).with(user).and_return([user]) } + before do + allow(issue).to receive(:participants).with(user).and_return([user]) + end it 'returns false when no subcription exists' do expect(issue.subscribed?(user, project)).to be_truthy @@ -252,7 +256,9 @@ describe Issuable do end context "issue is assigned" do - before { issue.assignees << user } + before do + issue.assignees << user + end it "returns correct hook data" do expect(data[:assignees].first).to eq(user.hook_attrs) @@ -276,7 +282,9 @@ describe Issuable do context 'issue has labels' do let(:labels) { [create(:label), create(:label)] } - before { issue.update_attribute(:labels, labels)} + before do + issue.update_attribute(:labels, labels) + end it 'includes labels in the hook data' do expect(data[:labels]).to eq(labels.map(&:hook_attrs)) diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index e382c7120de..e2a29e0ae70 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -61,7 +61,9 @@ describe Issue, "Mentionable" do end context 'when the current user can see the issue' do - before { private_project.team << [user, Gitlab::Access::DEVELOPER] } + before do + private_project.team << [user, Gitlab::Access::DEVELOPER] + end it 'includes the reference' do expect(referenced_issues(user)).to contain_exactly(private_issue, public_issue) diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 675b730c557..cefe7fb6fea 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -19,12 +19,43 @@ describe Milestone, 'Milestoneish' do let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) } let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignees: [assignee], milestone: milestone) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) } + let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } + let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } + let(:label_3) { create(:label, title: 'label_3', project: project) } before do project.team << [member, :developer] project.team << [guest, :guest] end + describe '#sorted_issues' do + it 'sorts issues by label priority' do + issue.labels << label_1 + security_issue_1.labels << label_2 + closed_issue_1.labels << label_3 + + issues = milestone.sorted_issues(member) + + expect(issues.first).to eq(issue) + expect(issues.second).to eq(security_issue_1) + expect(issues.third).not_to eq(closed_issue_1) + end + end + + describe '#sorted_merge_requests' do + it 'sorts merge requests by label priority' do + merge_request_1 = create(:labeled_merge_request, labels: [label_2], source_project: project, source_branch: 'branch_1', milestone: milestone) + merge_request_2 = create(:labeled_merge_request, labels: [label_1], source_project: project, source_branch: 'branch_2', milestone: milestone) + merge_request_3 = create(:labeled_merge_request, labels: [label_3], source_project: project, source_branch: 'branch_3', milestone: milestone) + + merge_requests = milestone.sorted_merge_requests + + expect(merge_requests.first).to eq(merge_request_2) + expect(merge_requests.second).to eq(merge_request_1) + expect(merge_requests.third).to eq(merge_request_3) + end + end + describe '#closed_items_count' do it 'does not count confidential issues for non project members' do expect(milestone.closed_items_count(non_member)).to eq 2 diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index a0765a264cf..808247ebfd5 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -40,7 +40,10 @@ describe ReactiveCaching, caching: true do let(:instance) { CacheTest.new(666, &calculation) } describe '#with_reactive_cache' do - before { stub_reactive_cache } + before do + stub_reactive_cache + end + subject(:go!) { instance.result } context 'when cache is empty' do @@ -60,12 +63,17 @@ describe ReactiveCaching, caching: true do end context 'when the cache is full' do - before { stub_reactive_cache(instance, 4) } + before do + stub_reactive_cache(instance, 4) + end it { is_expected.to eq(2) } context 'and expired' do - before { invalidate_reactive_cache(instance) } + before do + invalidate_reactive_cache(instance) + end + it { is_expected.to be_nil } end end @@ -84,7 +92,9 @@ describe ReactiveCaching, caching: true do subject(:go!) { instance.exclusively_update_reactive_cache! } context 'when the lease is free and lifetime is not exceeded' do - before { stub_reactive_cache(instance, "preexisting") } + before do + stub_reactive_cache(instance, "preexisting") + end it 'takes and releases the lease' do expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return("000000") @@ -106,7 +116,10 @@ describe ReactiveCaching, caching: true do end context 'and #calculate_reactive_cache raises an exception' do - before { stub_reactive_cache(instance, "preexisting") } + before do + stub_reactive_cache(instance, "preexisting") + end + let(:calculation) { -> { raise "foo"} } it 'leaves the cache untouched' do diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 4b0bfa43abf..882afeccfc6 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -49,7 +49,10 @@ describe ApplicationSetting, 'TokenAuthenticatable' do end context 'token is generated' do - before { subject.send("reset_#{token_field}!") } + before do + subject.send("reset_#{token_field}!") + end + it 'persists a new token' do expect(subject.send(:read_attribute, token_field)).to be_a String end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 6f0d2db23c7..aad215d5f41 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -102,7 +102,7 @@ describe Deployment, models: true do end context 'with other actions' do - let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } context 'when matching action is defined' do let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_other_app') } @@ -130,7 +130,7 @@ describe Deployment, models: true do context 'when matching action is defined' do let(:build) { create(:ci_build) } let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } - let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } it { is_expected.to be_truthy } end diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb new file mode 100644 index 00000000000..3755f4a56f3 --- /dev/null +++ b/spec/models/diff_viewer/base_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' + +describe DiffViewer::Base, model: true do + include FakeBlobHelpers + + let(:project) { create(:project, :repository) } + let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + let(:viewer_class) do + Class.new(described_class) do + include DiffViewer::ServerSide + + self.extensions = %w(jpg) + self.binary = true + self.collapse_limit = 1.megabyte + self.size_limit = 5.megabytes + end + end + + let(:viewer) { viewer_class.new(diff_file) } + + describe '.can_render?' do + context 'when the extension is supported' do + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') } + + context 'when the binaryness matches' do + it 'returns true' do + expect(viewer_class.can_render?(diff_file)).to be_truthy + end + end + + context 'when the binaryness does not match' do + before do + allow(diff_file.old_blob).to receive(:binary?).and_return(false) + allow(diff_file.new_blob).to receive(:binary?).and_return(false) + end + + it 'returns false' do + expect(viewer_class.can_render?(diff_file)).to be_falsey + end + end + end + + context 'when the file type is supported' do + let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('LICENSE') } + + before do + viewer_class.file_types = %i(license) + viewer_class.binary = false + end + + context 'when the binaryness matches' do + it 'returns true' do + expect(viewer_class.can_render?(diff_file)).to be_truthy + end + end + + context 'when the binaryness does not match' do + before do + allow(diff_file.old_blob).to receive(:binary?).and_return(true) + allow(diff_file.new_blob).to receive(:binary?).and_return(true) + end + + it 'returns false' do + expect(viewer_class.can_render?(diff_file)).to be_falsey + end + end + end + + context 'when the extension and file type are not supported' do + it 'returns false' do + expect(viewer_class.can_render?(diff_file)).to be_falsey + end + end + + context 'when the file was renamed and only the old blob is supported' do + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') } + + before do + allow(diff_file).to receive(:renamed_file?).and_return(true) + allow(diff_file.new_blob).to receive(:extension).and_return('jpeg') + end + + it 'returns false' do + expect(viewer_class.can_render?(diff_file)).to be_falsey + end + end + end + + describe '#collapsed?' do + context 'when the combined blob size is larger than the collapse limit' do + before do + allow(diff_file.old_blob).to receive(:raw_size).and_return(512.kilobytes) + allow(diff_file.new_blob).to receive(:raw_size).and_return(513.kilobytes) + end + + it 'returns true' do + expect(viewer.collapsed?).to be_truthy + end + end + + context 'when the combined blob size is smaller than the collapse limit' do + it 'returns false' do + expect(viewer.collapsed?).to be_falsey + end + end + end + + describe '#too_large?' do + context 'when the combined blob size is larger than the size limit' do + before do + allow(diff_file.old_blob).to receive(:raw_size).and_return(2.megabytes) + allow(diff_file.new_blob).to receive(:raw_size).and_return(4.megabytes) + end + + it 'returns true' do + expect(viewer.too_large?).to be_truthy + end + end + + context 'when the blob size is smaller than the size limit' do + it 'returns false' do + expect(viewer.too_large?).to be_falsey + end + end + end + + describe '#render_error' do + context 'when the combined blob size is larger than the size limit' do + before do + allow(diff_file.old_blob).to receive(:raw_size).and_return(2.megabytes) + allow(diff_file.new_blob).to receive(:raw_size).and_return(4.megabytes) + end + + it 'returns :too_large' do + expect(viewer.render_error).to eq(:too_large) + end + end + + context 'when the combined blob size is smaller than the size limit' do + it 'returns nil' do + expect(viewer.render_error).to be_nil + end + end + end +end diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb new file mode 100644 index 00000000000..2d926e06936 --- /dev/null +++ b/spec/models/diff_viewer/server_side_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe DiffViewer::ServerSide, model: true do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + let(:viewer_class) do + Class.new(DiffViewer::Base) do + include DiffViewer::ServerSide + end + end + + subject { viewer_class.new(diff_file) } + + describe '#prepare!' do + it 'loads all diff file data' do + expect(diff_file.old_blob).to receive(:load_all_data!) + expect(diff_file.new_blob).to receive(:load_all_data!) + + subject.prepare! + end + end + + describe '#render_error' do + context 'when the diff file is stored externally' do + before do + allow(diff_file).to receive(:stored_externally?).and_return(true) + end + + it 'return :server_side_but_stored_externally' do + expect(subject.render_error).to eq(:server_side_but_stored_externally) + end + end + end +end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index fe69c8e351d..f8123cb518e 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -170,7 +170,7 @@ describe Environment, models: true do context 'when matching action is defined' do let(:build) { create(:ci_build) } let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } - let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } context 'when environment is available' do before do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index f4c3e6d503f..152e97e09bf 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -19,7 +19,10 @@ describe GenericCommitStatus, models: true do describe '#context' do subject { generic_commit_status.context } - before { generic_commit_status.context = 'my_context' } + + before do + generic_commit_status.context = 'my_context' + end it { is_expected.to eq(generic_commit_status.name) } end @@ -39,7 +42,9 @@ describe GenericCommitStatus, models: true do end context 'when user has ability to see datails' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'details path points to an external URL' do expect(status).to have_details diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 3d437ca0fcc..449b7c2f7d7 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -143,14 +143,20 @@ describe Group, models: true do describe '#add_user' do let(:user) { create(:user) } - before { group.add_user(user, GroupMember::MASTER) } + + before do + group.add_user(user, GroupMember::MASTER) + end it { expect(group.group_members.masters.map(&:user)).to include(user) } end describe '#add_users' do let(:user) { create(:user) } - before { group.add_users([user.id], GroupMember::GUEST) } + + before do + group.add_users([user.id], GroupMember::GUEST) + end it "updates the group permission" do expect(group.group_members.guests.map(&:user)).to include(user) @@ -162,7 +168,10 @@ describe Group, models: true do describe '#avatar_type' do let(:user) { create(:user) } - before { group.add_user(user, GroupMember::MASTER) } + + before do + group.add_user(user, GroupMember::MASTER) + end it "is true if avatar is image" do group.update_attribute(:avatar, 'uploads/avatar.png') @@ -182,7 +191,9 @@ describe Group, models: true do let(:avatar_path) { "/uploads/system/group/avatar/#{group.id}/dk.png" } context 'when avatar file is uploaded' do - before { group.add_master(user) } + before do + group.add_master(user) + end it 'shows correct avatar url' do expect(group.avatar_url).to eq(avatar_path) @@ -222,7 +233,9 @@ describe Group, models: true do end describe '#has_owner?' do - before { @members = setup_group_members(group) } + before do + @members = setup_group_members(group) + end it { expect(group.has_owner?(@members[:owner])).to be_truthy } it { expect(group.has_owner?(@members[:master])).to be_falsey } @@ -233,7 +246,9 @@ describe Group, models: true do end describe '#has_master?' do - before { @members = setup_group_members(group) } + before do + @members = setup_group_members(group) + end it { expect(group.has_master?(@members[:owner])).to be_falsey } it { expect(group.has_master?(@members[:master])).to be_truthy } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index bb4e70db2e9..12e7d646382 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -245,7 +245,9 @@ describe Issue, models: true do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end it { is_expected.to eq true } @@ -259,12 +261,18 @@ describe Issue, models: true do let(:to_project) { create(:empty_project) } context 'destination project allowed' do - before { to_project.team << [user, :reporter] } + before do + to_project.team << [user, :reporter] + end + it { is_expected.to eq true } end context 'destination project not allowed' do - before { to_project.team << [user, :guest] } + before do + to_project.team << [user, :guest] + end + it { is_expected.to eq false } end end @@ -549,7 +557,9 @@ describe Issue, models: true do end context 'when the user is the project owner' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'returns true for a regular issue' do issue = build(:issue, project: project) diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb new file mode 100644 index 00000000000..7276f5b5061 --- /dev/null +++ b/spec/models/merge_request_diff_file_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +describe MergeRequestDiffFile, type: :model do + describe '#utf8_diff' do + it 'does not raise error when a hash value is in binary' do + subject.diff = "\x05\x00\x68\x65\x6c\x6c\x6f" + + expect { subject.utf8_diff }.not_to raise_error + end + end +end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index ed9fde57bf7..4ad4abaa572 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -36,7 +36,9 @@ describe MergeRequestDiff, models: true do end context 'when the raw diffs are empty' do - before { mr_diff.update_attributes(st_diffs: '') } + before do + MergeRequestDiffFile.delete_all(merge_request_diff_id: mr_diff.id) + end it 'returns an empty DiffCollection' do expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) @@ -45,7 +47,10 @@ describe MergeRequestDiff, models: true do end context 'when the raw diffs have invalid content' do - before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + before do + MergeRequestDiffFile.delete_all(merge_request_diff_id: mr_diff.id) + mr_diff.update_attributes(st_diffs: ["--broken-diff"]) + end it 'returns an empty DiffCollection' do expect(mr_diff.raw_diffs.to_a).to be_empty diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 060754fab63..a56bc524a98 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -892,7 +892,9 @@ describe MergeRequest, models: true do end context 'when broken' do - before { allow(subject).to receive(:broken?) { true } } + before do + allow(subject).to receive(:broken?) { true } + end it 'becomes unmergeable' do expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') @@ -944,7 +946,9 @@ describe MergeRequest, models: true do end context 'when not open' do - before { subject.close } + before do + subject.close + end it 'returns false' do expect(subject.mergeable_state?).to be_falsey @@ -952,7 +956,9 @@ describe MergeRequest, models: true do end context 'when working in progress' do - before { subject.title = 'WIP MR' } + before do + subject.title = 'WIP MR' + end it 'returns false' do expect(subject.mergeable_state?).to be_falsey @@ -960,7 +966,9 @@ describe MergeRequest, models: true do end context 'when broken' do - before { allow(subject).to receive(:broken?) { true } } + before do + allow(subject).to receive(:broken?) { true } + end it 'returns false' do expect(subject.mergeable_state?).to be_falsey @@ -1389,7 +1397,7 @@ describe MergeRequest, models: true do end end - describe '#mergeable_with_slash_command?' do + describe '#mergeable_with_quick_action?' do def create_pipeline(status) pipeline = create(:ci_pipeline_with_one_job, project: project, @@ -1413,21 +1421,21 @@ describe MergeRequest, models: true do context 'when autocomplete_precheck is set to true' do it 'is mergeable by developer' do - expect(merge_request.mergeable_with_slash_command?(developer, autocomplete_precheck: true)).to be_truthy + expect(merge_request.mergeable_with_quick_action?(developer, autocomplete_precheck: true)).to be_truthy end it 'is not mergeable by normal user' do - expect(merge_request.mergeable_with_slash_command?(user, autocomplete_precheck: true)).to be_falsey + expect(merge_request.mergeable_with_quick_action?(user, autocomplete_precheck: true)).to be_falsey end end context 'when autocomplete_precheck is set to false' do it 'is mergeable by developer' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy end it 'is not mergeable by normal user' do - expect(merge_request.mergeable_with_slash_command?(user, last_diff_sha: mr_sha)).to be_falsey + expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey end context 'closed MR' do @@ -1436,7 +1444,7 @@ describe MergeRequest, models: true do end it 'is not mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey end end @@ -1446,19 +1454,19 @@ describe MergeRequest, models: true do end it 'is not mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey end end context 'sha differs from the MR diff_head_sha' do it 'is not mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: 'some other sha')).to be_falsey + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey end end context 'sha is not provided' do it 'is not mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer)).to be_falsey + expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey end end @@ -1468,7 +1476,7 @@ describe MergeRequest, models: true do end it 'is mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy end end @@ -1478,7 +1486,7 @@ describe MergeRequest, models: true do end it 'is not mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey end end @@ -1488,7 +1496,7 @@ describe MergeRequest, models: true do end it 'is mergeable' do - expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy + expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy end end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index aa1ce89ffd7..20b96c08a8f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -144,35 +144,6 @@ describe Milestone, models: true do end end - describe '#sort_issues' do - let(:milestone) { create(:milestone) } - - let(:issue1) { create(:issue, milestone: milestone, position: 1) } - let(:issue2) { create(:issue, milestone: milestone, position: 2) } - let(:issue3) { create(:issue, milestone: milestone, position: 3) } - let(:issue4) { create(:issue, position: 42) } - - it 'sorts the given issues' do - milestone.sort_issues([issue3.id, issue2.id, issue1.id]) - - issue1.reload - issue2.reload - issue3.reload - - expect(issue1.position).to eq(3) - expect(issue2.position).to eq(2) - expect(issue3.position).to eq(1) - end - - it 'ignores issues not part of the milestone' do - milestone.sort_issues([issue3.id, issue2.id, issue1.id, issue4.id]) - - issue4.reload - - expect(issue4.position).to eq(42) - end - end - describe '.search' do let(:milestone) { create(:milestone, title: 'foo', description: 'bar') } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 7a01cef9b4b..d4d4fc86343 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -26,14 +26,18 @@ describe Note, models: true do it { is_expected.to validate_presence_of(:project) } context 'when note is on commit' do - before { allow(subject).to receive(:for_commit?).and_return(true) } + before do + allow(subject).to receive(:for_commit?).and_return(true) + end it { is_expected.to validate_presence_of(:commit_id) } it { is_expected.not_to validate_presence_of(:noteable_id) } end context 'when note is not on commit' do - before { allow(subject).to receive(:for_commit?).and_return(false) } + before do + allow(subject).to receive(:for_commit?).and_return(false) + end it { is_expected.not_to validate_presence_of(:commit_id) } it { is_expected.to validate_presence_of(:noteable_id) } diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index d58673413c8..cc235ad467e 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -55,4 +55,34 @@ RSpec.describe NotificationSetting, type: :model do expect(user.notification_settings.for_projects.map(&:project)).to all(have_attributes(pending_delete: false)) end end + + describe 'event_enabled?' do + before do + subject.update!(user: create(:user)) + end + + context 'for an event with a matching column name' do + before do + subject.update!(events: { new_note: true }.to_json) + end + + it 'returns the value of the column' do + subject.update!(new_note: false) + + expect(subject.event_enabled?(:new_note)).to be(false) + end + + context 'when the column has a nil value' do + it 'returns the value from the events hash' do + expect(subject.event_enabled?(:new_note)).to be(false) + end + end + end + + context 'for an event without a matching column name' do + it 'returns false' do + expect(subject.event_enabled?(:foo_event)).to be(false) + end + end + end end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 4014d6129ee..e62fd69e567 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -24,7 +24,9 @@ describe BambooService, models: true, caching: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:build_key) } it { is_expected.to validate_presence_of(:bamboo_url) } @@ -60,7 +62,9 @@ describe BambooService, models: true, caching: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:build_key) } it { is_expected.not_to validate_presence_of(:bamboo_url) } diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb index 739cc72b2ff..5f17bbde390 100644 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -8,7 +8,9 @@ describe BugzillaService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } @@ -19,7 +21,9 @@ describe BugzillaService, models: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:project_url) } it { is_expected.not_to validate_presence_of(:issues_url) } diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 05b602d8106..dd529597067 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -23,7 +23,9 @@ describe BuildkiteService, models: true, caching: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:token) } @@ -31,7 +33,9 @@ describe BuildkiteService, models: true, caching: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:project_url) } it { is_expected.not_to validate_presence_of(:token) } diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index 953e664fb66..de55627dd27 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -8,13 +8,17 @@ describe CampfireService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:token) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:token) } end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 4ca1b8aa7b7..17355c1e6f1 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -23,7 +23,9 @@ describe ChatMessage::WikiPageMessage, models: true do context 'without markdown' do describe '#pretext' do context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + before do + args[:object_attributes][:action] = 'create' + end it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( @@ -33,7 +35,9 @@ describe ChatMessage::WikiPageMessage, models: true do end context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + before do + args[:object_attributes][:action] = 'update' + end it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( @@ -47,7 +51,9 @@ describe ChatMessage::WikiPageMessage, models: true do let(:color) { '#345' } context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + before do + args[:object_attributes][:action] = 'create' + end it 'returns the attachment for a new wiki page' do expect(subject.attachments).to eq([ @@ -60,7 +66,9 @@ describe ChatMessage::WikiPageMessage, models: true do end context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + before do + args[:object_attributes][:action] = 'update' + end it 'returns the attachment for an updated wiki page' do expect(subject.attachments).to eq([ @@ -81,7 +89,9 @@ describe ChatMessage::WikiPageMessage, models: true do describe '#pretext' do context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + before do + args[:object_attributes][:action] = 'create' + end it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( @@ -90,7 +100,9 @@ describe ChatMessage::WikiPageMessage, models: true do end context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + before do + args[:object_attributes][:action] = 'update' + end it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( @@ -101,7 +113,9 @@ describe ChatMessage::WikiPageMessage, models: true do describe '#attachments' do context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + before do + args[:object_attributes][:action] = 'create' + end it 'returns the attachment for a new wiki page' do expect(subject.attachments).to eq('Wiki page description') @@ -109,7 +123,9 @@ describe ChatMessage::WikiPageMessage, models: true do end context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + before do + args[:object_attributes][:action] = 'update' + end it 'returns the attachment for an updated wiki page' do expect(subject.attachments).to eq('Wiki page description') @@ -119,7 +135,9 @@ describe ChatMessage::WikiPageMessage, models: true do describe '#activity' do context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + before do + args[:object_attributes][:action] = 'create' + end it 'returns the attachment for a new wiki page' do expect(subject.activity).to eq({ @@ -132,7 +150,9 @@ describe ChatMessage::WikiPageMessage, models: true do end context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + before do + args[:object_attributes][:action] = 'update' + end it 'returns the attachment for an updated wiki page' do expect(subject.activity).to eq({ diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index 63320931e76..9e574762232 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -8,7 +8,9 @@ describe CustomIssueTrackerService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } @@ -19,7 +21,9 @@ describe CustomIssueTrackerService, models: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:project_url) } it { is_expected.not_to validate_presence_of(:issues_url) } diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 044737c6026..1400175427f 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -10,7 +10,9 @@ describe DroneCiService, models: true, caching: true do describe 'validations' do context 'active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:drone_url) } @@ -18,7 +20,9 @@ describe DroneCiService, models: true, caching: true do end context 'inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:token) } it { is_expected.not_to validate_presence_of(:drone_url) } diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb index e6f78898c82..d9b7010e5e5 100644 --- a/spec/models/project_services/emails_on_push_service_spec.rb +++ b/spec/models/project_services/emails_on_push_service_spec.rb @@ -3,13 +3,17 @@ require 'spec_helper' describe EmailsOnPushService do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:recipients) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:recipients) } end diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index bdeea1db1e3..291fc645a1c 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -9,14 +9,18 @@ describe ExternalWikiService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:external_wiki_url) } it_behaves_like 'issue tracker service URL attribute', :external_wiki_url end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:external_wiki_url) } end diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index a97e8c6e4ce..56ace04dd58 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -8,13 +8,17 @@ describe FlowdockService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:token) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:token) } end diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index a13fbae03eb..65c9e714bd1 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -8,14 +8,18 @@ describe GemnasiumService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:api_key) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:token) } it { is_expected.not_to validate_presence_of(:api_key) } diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 1200ae7eb22..c7c8e9651ab 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -8,13 +8,17 @@ describe HipchatService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:token) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:token) } end diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index d5a16226d9d..a5c4938b54e 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -10,13 +10,17 @@ describe IrkerService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:recipients) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:recipients) } end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 0ee050196e4..e2b8226124f 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -10,7 +10,9 @@ describe JiraService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:url) } it { is_expected.to validate_presence_of(:project_key) } @@ -18,7 +20,9 @@ describe JiraService, models: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:url) } end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 0dcf4a4b5d6..858ad595dbf 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -7,31 +7,15 @@ describe KubernetesService, models: true, caching: true do let(:project) { build_stubbed(:kubernetes_project) } let(:service) { project.kubernetes_service } - # We use Kubeclient to interactive with the Kubernetes API. It will - # GET /api/v1 for a list of resources the API supports. This must be stubbed - # in addition to any other HTTP requests we expect it to perform. - let(:discovery_url) { service.api_url + '/api/v1' } - let(:discovery_response) { { body: kube_discovery_body.to_json } } - - let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" } - let(:pods_response) { { body: kube_pods_body(kube_pod).to_json } } - - def stub_kubeclient_discover - WebMock.stub_request(:get, discovery_url).to_return(discovery_response) - end - - def stub_kubeclient_pods - stub_kubeclient_discover - WebMock.stub_request(:get, pods_url).to_return(pods_response) - end - describe "Associations" do it { is_expected.to belong_to :project } end describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.not_to validate_presence_of(:namespace) } it { is_expected.to validate_presence_of(:api_url) } @@ -66,7 +50,9 @@ describe KubernetesService, models: true, caching: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:api_url) } it { is_expected.not_to validate_presence_of(:token) } @@ -87,7 +73,9 @@ describe KubernetesService, models: true, caching: true do end context 'as template' do - before { subject.template = true } + before do + subject.template = true + end it 'sets the namespace to the default' do expect(kube_namespace).not_to be_nil @@ -96,7 +84,9 @@ describe KubernetesService, models: true, caching: true do end context 'with associated project' do - before { subject.project = project } + before do + subject.project = project + end it 'sets the namespace to the default' do expect(kube_namespace).not_to be_nil @@ -111,6 +101,34 @@ describe KubernetesService, models: true, caching: true do it "returns the default namespace" do is_expected.to eq(service.send(:default_namespace)) end + + context 'when namespace is specified' do + before do + service.namespace = 'my-namespace' + end + + it "returns the user-namespace" do + is_expected.to eq('my-namespace') + end + end + + context 'when service is not assigned to project' do + before do + service.project = nil + end + + it "does not return namespace" do + is_expected.to be_nil + end + end + end + + describe '#actual_namespace' do + subject { service.actual_namespace } + + it "returns the default namespace" do + is_expected.to eq(service.send(:default_namespace)) + end context 'when namespace is specified' do before do @@ -134,6 +152,8 @@ describe KubernetesService, models: true, caching: true do end describe '#test' do + let(:discovery_url) { 'https://kubernetes.example.com/api/v1' } + before do stub_kubeclient_discover end @@ -142,7 +162,8 @@ describe KubernetesService, models: true, caching: true do let(:discovery_url) { 'https://kubernetes.example.com/prefix/api/v1' } it 'tests with the prefix' do - service.api_url = 'https://kubernetes.example.com/prefix/' + service.api_url = 'https://kubernetes.example.com/prefix' + stub_kubeclient_discover expect(service.test[:success]).to be_truthy expect(WebMock).to have_requested(:get, discovery_url).once @@ -170,9 +191,9 @@ describe KubernetesService, models: true, caching: true do end context 'failure' do - let(:discovery_response) { { status: 404 } } - it 'fails to read the discovery endpoint' do + WebMock.stub_request(:get, service.api_url + '/api/v1').to_return(status: 404) + expect(service.test[:success]).to be_falsy expect(WebMock).to have_requested(:get, discovery_url).once end @@ -188,7 +209,9 @@ describe KubernetesService, models: true, caching: true do end context 'namespace is provided' do - before { subject.namespace = 'my-project' } + before do + subject.namespace = 'my-project' + end it 'sets the variables' do expect(subject.predefined_variables).to include( @@ -258,27 +281,36 @@ describe KubernetesService, models: true, caching: true do end describe '#calculate_reactive_cache' do - before { stub_kubeclient_pods } subject { service.calculate_reactive_cache } context 'when service is inactive' do - before { service.active = false } + before do + service.active = false + end it { is_expected.to be_nil } end context 'when kubernetes responds with valid pods' do + before do + stub_kubeclient_pods + end + it { is_expected.to eq(pods: [kube_pod]) } end - context 'when kubernetes responds with 500' do - let(:pods_response) { { status: 500 } } + context 'when kubernetes responds with 500s' do + before do + stub_kubeclient_pods(status: 500) + end it { expect { subject }.to raise_error(KubeException) } end - context 'when kubernetes responds with 404' do - let(:pods_response) { { status: 404 } } + context 'when kubernetes responds with 404s' do + before do + stub_kubeclient_pods(status: 404) + end it { is_expected.to eq(pods: []) } end diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb index facc034f69c..bd50a2d1470 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -11,14 +11,18 @@ describe MicrosoftTeamsService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:webhook) } it_behaves_like 'issue tracker service URL attribute', :webhook end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:webhook) } end diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index a76e909d04d..f4c1a9c94b6 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -8,13 +8,17 @@ describe PivotaltrackerService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:token) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:token) } end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index 1f9d3c07b51..71b53732164 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -14,13 +14,17 @@ describe PrometheusService, models: true, caching: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:api_url) } end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:api_url) } end diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index a7e7594a7d5..9171d9604ee 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -8,7 +8,9 @@ describe PushoverService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:api_key) } it { is_expected.to validate_presence_of(:user_key) } @@ -16,7 +18,9 @@ describe PushoverService, models: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:api_key) } it { is_expected.not_to validate_presence_of(:user_key) } diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index 0a7b237a051..6631d9040b1 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -8,7 +8,9 @@ describe RedmineService, models: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } @@ -19,7 +21,9 @@ describe RedmineService, models: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:project_url) } it { is_expected.not_to validate_presence_of(:issues_url) } diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 77b18e1c7d0..7349eb4149a 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -24,7 +24,9 @@ describe TeamcityService, models: true, caching: true do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:build_type) } it { is_expected.to validate_presence_of(:teamcity_url) } @@ -60,7 +62,9 @@ describe TeamcityService, models: true, caching: true do end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:build_type) } it { is_expected.not_to validate_presence_of(:teamcity_url) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 454eeb58ecd..63333b7af1f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1005,13 +1005,17 @@ describe Project, models: true do subject { project.shared_runners_enabled } context 'are enabled' do - before { stub_application_setting(shared_runners_enabled: true) } + before do + stub_application_setting(shared_runners_enabled: true) + end it { is_expected.to be_truthy } end context 'are disabled' do - before { stub_application_setting(shared_runners_enabled: false) } + before do + stub_application_setting(shared_runners_enabled: false) + end it { is_expected.to be_falsey } end @@ -1107,7 +1111,9 @@ describe Project, models: true do subject { project.pages_deployed? } context 'if public folder does exist' do - before { allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true) } + before do + allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true) + end it { is_expected.to be_truthy } end @@ -1365,7 +1371,9 @@ describe Project, models: true do subject { project.container_registry_url } - before { stub_container_registry_config(**registry_settings) } + before do + stub_container_registry_config(**registry_settings) + end context 'for enabled registry' do let(:registry_settings) do @@ -1389,7 +1397,9 @@ describe Project, models: true do let(:project) { create(:empty_project) } context 'when container registry is enabled' do - before { stub_container_registry_config(enabled: true) } + before do + stub_container_registry_config(enabled: true) + end context 'when tags are present for multi-level registries' do before do @@ -1427,7 +1437,9 @@ describe Project, models: true do end context 'when container registry is disabled' do - before { stub_container_registry_config(enabled: false) } + before do + stub_container_registry_config(enabled: false) + end it 'should not have image tags' do expect(project).not_to have_container_registry_tags @@ -1945,7 +1957,9 @@ describe Project, models: true do describe '#parent_changed?' do let(:project) { create(:empty_project) } - before { project.namespace_id = 7 } + before do + project.namespace_id = 7 + end it { expect(project.parent_changed?).to be_truthy } end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 497e3cdf415..ea3cd5fe10a 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -240,7 +240,9 @@ describe ProjectTeam, models: true do it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) } context 'but share_with_group_lock is true' do - before { project.namespace.update(share_with_group_lock: true) } + before do + project.namespace.update(share_with_group_lock: true) + end it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::NO_ACCESS) } it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) } diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 224067f58dd..3f5f4eea4a1 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -8,7 +8,10 @@ describe ProjectWiki, models: true do let(:project_wiki) { ProjectWiki.new(project, user) } subject { project_wiki } - before { project_wiki.wiki } + + before do + project_wiki.wiki + end describe "#path_with_namespace" do it "returns the project path with namespace with the .wiki extension" do diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index c1fe1b06c52..1754253e0f2 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -9,7 +9,10 @@ describe Route, models: true do end describe 'validations' do - before { route } + before do + expect(route).to be_persisted + end + it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_uniqueness_of(:path) } @@ -59,7 +62,9 @@ describe Route, models: true do context 'path update' do context 'when route name is set' do - before { route.update_attributes(path: 'bar') } + before do + route.update_attributes(path: 'bar') + end it 'updates children routes with new path' do expect(described_class.exists?(path: 'bar')).to be_truthy diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d5bd9946ab6..f6b7120e2c9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -451,6 +451,40 @@ describe User, models: true do end end + describe '#ensure_user_rights_and_limits' do + describe 'with external user' do + let(:user) { create(:user, external: true) } + + it 'receives callback when external changes' do + expect(user).to receive(:ensure_user_rights_and_limits) + + user.update_attributes(external: false) + end + + it 'ensures correct rights and limits for user' do + stub_config_setting(default_can_create_group: true) + + expect { user.update_attributes(external: false) }.to change { user.can_create_group }.to(true) + .and change { user.projects_limit }.to(current_application_settings.default_projects_limit) + end + end + + describe 'without external user' do + let(:user) { create(:user, external: false) } + + it 'receives callback when external changes' do + expect(user).to receive(:ensure_user_rights_and_limits) + + user.update_attributes(external: true) + end + + it 'ensures correct rights and limits for user' do + expect { user.update_attributes(external: true) }.to change { user.can_create_group }.to(false) + .and change { user.projects_limit }.to(0) + end + end + end + describe 'rss token' do it 'ensures an rss token on read' do user = create(:user, rss_token: nil) @@ -1584,7 +1618,9 @@ describe User, models: true do end context 'user is member of the top group' do - before { group.add_owner(user) } + before do + group.add_owner(user) + end if Group.supports_nested_groups? it 'returns all groups' do @@ -1602,7 +1638,9 @@ describe User, models: true do end context 'user is member of the first child (internal node), branch 1', :nested_groups do - before { nested_group_1.add_owner(user) } + before do + nested_group_1.add_owner(user) + end it 'returns the groups in the hierarchy' do is_expected.to match_array [ @@ -1613,7 +1651,9 @@ describe User, models: true do end context 'user is member of the first child (internal node), branch 2', :nested_groups do - before { nested_group_2.add_owner(user) } + before do + nested_group_2.add_owner(user) + end it 'returns the groups in the hierarchy' do is_expected.to match_array [ @@ -1624,7 +1664,9 @@ describe User, models: true do end context 'user is member of the last child (leaf node)', :nested_groups do - before { nested_group_1_1.add_owner(user) } + before do + nested_group_1_1.add_owner(user) + end it 'returns the groups in the hierarchy' do is_expected.to match_array [ diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 3f4ce222b60..48a139d4b83 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -10,7 +10,9 @@ describe Ci::BuildPolicy, :models do end shared_context 'public pipelines disabled' do - before { project.update_attribute(:public_builds, false) } + before do + project.update_attribute(:public_builds, false) + end end describe '#rules' do @@ -54,7 +56,9 @@ describe Ci::BuildPolicy, :models do let(:project) { create(:empty_project, :public) } context 'team member is a guest' do - before { project.team << [user, :guest] } + before do + project.team << [user, :guest] + end context 'when public builds are enabled' do it 'includes ability to read build' do @@ -72,7 +76,9 @@ describe Ci::BuildPolicy, :models do end context 'team member is a reporter' do - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end context 'when public builds are enabled' do it 'includes ability to read build' do diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 0d3af1f4499..848fd547e10 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -139,6 +139,18 @@ describe ProjectPolicy, models: true do is_expected.not_to include(:read_build, :read_pipeline) end end + + context 'when builds are disabled' do + before do + project.project_feature.update( + builds_access_level: ProjectFeature::DISABLED) + end + + it do + is_expected.not_to include(:read_build) + is_expected.to include(:read_pipeline) + end + end end context 'reporter' do diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index ddbed5f781e..d2b2528c57a 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -78,7 +78,9 @@ describe ProjectSnippetPolicy, models: true do context 'project team member external user' do subject { abilities(external_user, :internal) } - before { project.team << [external_user, :developer] } + before do + project.team << [external_user, :developer] + end it do is_expected.to include(:read_project_snippet) @@ -120,7 +122,9 @@ describe ProjectSnippetPolicy, models: true do context 'project team member normal user' do subject { abilities(regular_user, :private) } - before { project.team << [regular_user, :developer] } + before do + project.team << [regular_user, :developer] + end it do is_expected.to include(:read_project_snippet) @@ -131,7 +135,9 @@ describe ProjectSnippetPolicy, models: true do context 'project team member external user' do subject { abilities(external_user, :private) } - before { project.team << [external_user, :developer] } + before do + project.team << [external_user, :developer] + end it do is_expected.to include(:read_project_snippet) diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index bbdef0aeb1b..6d822b5cb4f 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -9,7 +9,9 @@ describe API::AwardEmoji do let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } let!(:note) { create(:note, project: project, noteable: issue) } - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do context 'on an issue' do diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 6b637a03b6f..b8ca73c321c 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -34,7 +34,9 @@ describe API::CommitStatuses do let!(:status6) { create_status(master, status: 'success') } context 'latest commit statuses' do - before { get api(get_url, reporter) } + before do + get api(get_url, reporter) + end it 'returns latest commit statuses' do expect(response).to have_http_status(200) @@ -48,7 +50,9 @@ describe API::CommitStatuses do end context 'all commit statuses' do - before { get api(get_url, reporter), all: 1 } + before do + get api(get_url, reporter), all: 1 + end it 'returns all commit statuses' do expect(response).to have_http_status(200) @@ -61,7 +65,9 @@ describe API::CommitStatuses do end context 'latest commit statuses for specific ref' do - before { get api(get_url, reporter), ref: 'develop' } + before do + get api(get_url, reporter), ref: 'develop' + end it 'returns latest commit statuses for specific ref' do expect(response).to have_http_status(200) @@ -72,7 +78,9 @@ describe API::CommitStatuses do end context 'latest commit statues for specific name' do - before { get api(get_url, reporter), name: 'coverage' } + before do + get api(get_url, reporter), name: 'coverage' + end it 'return latest commit statuses for specific name' do expect(response).to have_http_status(200) @@ -85,7 +93,9 @@ describe API::CommitStatuses do end context 'ci commit does not exist' do - before { get api(get_url, reporter) } + before do + get api(get_url, reporter) + end it 'returns empty array' do expect(response.status).to eq 200 @@ -95,7 +105,9 @@ describe API::CommitStatuses do end context "guest user" do - before { get api(get_url, guest) } + before do + get api(get_url, guest) + end it "does not return project commits" do expect(response).to have_http_status(403) @@ -103,7 +115,9 @@ describe API::CommitStatuses do end context "unauthorized user" do - before { get api(get_url) } + before do + get api(get_url) + end it "does not return project commits" do expect(response).to have_http_status(401) @@ -209,7 +223,9 @@ describe API::CommitStatuses do end context 'when status is invalid' do - before { post api(post_url, developer), state: 'invalid' } + before do + post api(post_url, developer), state: 'invalid' + end it 'does not create commit status' do expect(response).to have_http_status(400) @@ -217,7 +233,9 @@ describe API::CommitStatuses do end context 'when request without a state made' do - before { post api(post_url, developer) } + before do + post api(post_url, developer) + end it 'does not create commit status' do expect(response).to have_http_status(400) @@ -226,7 +244,10 @@ describe API::CommitStatuses do context 'when commit SHA is invalid' do let(:sha) { 'invalid_sha' } - before { post api(post_url, developer), state: 'running' } + + before do + post api(post_url, developer), state: 'running' + end it 'returns not found error' do expect(response).to have_http_status(404) @@ -248,7 +269,9 @@ describe API::CommitStatuses do end context 'reporter user' do - before { post api(post_url, reporter), state: 'running' } + before do + post api(post_url, reporter), state: 'running' + end it 'does not create commit status' do expect(response).to have_http_status(403) @@ -256,7 +279,9 @@ describe API::CommitStatuses do end context 'guest user' do - before { post api(post_url, guest), state: 'running' } + before do + post api(post_url, guest), state: 'running' + end it 'does not create commit status' do expect(response).to have_http_status(403) @@ -264,7 +289,9 @@ describe API::CommitStatuses do end context 'unauthorized user' do - before { post api(post_url) } + before do + post api(post_url) + end it 'does not create commit status' do expect(response).to have_http_status(401) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index b0c265b6453..0dad547735d 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -9,11 +9,15 @@ describe API::Commits do let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') } - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end describe "List repository commits" do context "authorized user" do - before { project.team << [user2, :reporter] } + before do + project.team << [user2, :reporter] + end it "returns project commits" do commit = project.repository.commit @@ -514,7 +518,9 @@ describe API::Commits do describe "Get the diff of a commit" do context "authorized user" do - before { project.team << [user2, :reporter] } + before do + project.team << [user2, :reporter] + end it "returns the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 4d9cd5f3a27..9c260f88f56 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -41,7 +41,9 @@ describe API::DeployKeys do end describe 'GET /projects/:id/deploy_keys' do - before { deploy_key } + before do + deploy_key + end it 'returns array of ssh keys' do get api("/projects/#{project.id}/deploy_keys", admin) @@ -161,7 +163,9 @@ describe API::DeployKeys do end describe 'DELETE /projects/:id/deploy_keys/:key_id' do - before { deploy_key } + before do + deploy_key + end it 'deletes existing key' do expect do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index d325c6eff9d..c5ec8be4f21 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -13,7 +13,9 @@ describe API::Files do let(:author_email) { 'user@example.org' } let(:author_name) { 'John Doe' } - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end def route(file_path = nil) "/projects/#{project.id}/repository/files/#{file_path}" diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index ed392acc607..191c60aba31 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -55,40 +55,62 @@ describe API::Helpers do subject { current_user } describe "Warden authentication" do - before { doorkeeper_guard_returns false } + before do + doorkeeper_guard_returns false + end context "with invalid credentials" do context "GET request" do - before { env['REQUEST_METHOD'] = 'GET' } + before do + env['REQUEST_METHOD'] = 'GET' + end + it { is_expected.to be_nil } end end context "with valid credentials" do - before { warden_authenticate_returns user } + before do + warden_authenticate_returns user + end context "GET request" do - before { env['REQUEST_METHOD'] = 'GET' } + before do + env['REQUEST_METHOD'] = 'GET' + end + it { is_expected.to eq(user) } end context "HEAD request" do - before { env['REQUEST_METHOD'] = 'HEAD' } + before do + env['REQUEST_METHOD'] = 'HEAD' + end + it { is_expected.to eq(user) } end context "PUT request" do - before { env['REQUEST_METHOD'] = 'PUT' } + before do + env['REQUEST_METHOD'] = 'PUT' + end + it { is_expected.to be_nil } end context "POST request" do - before { env['REQUEST_METHOD'] = 'POST' } + before do + env['REQUEST_METHOD'] = 'POST' + end + it { is_expected.to be_nil } end context "DELETE request" do - before { env['REQUEST_METHOD'] = 'DELETE' } + before do + env['REQUEST_METHOD'] = 'DELETE' + end + it { is_expected.to be_nil } end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index cf232e7ff69..6deaea956e0 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -15,21 +15,43 @@ describe API::Internal do end end - describe "GET /internal/broadcast_message" do - context "broadcast message exists" do - let!(:broadcast_message) { create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow ) } + describe 'GET /internal/broadcast_message' do + context 'broadcast message exists' do + let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } - it do - get api("/internal/broadcast_message"), secret_token: secret_token + it 'returns one broadcast message' do + get api('/internal/broadcast_message'), secret_token: secret_token expect(response).to have_http_status(200) - expect(json_response["message"]).to eq(broadcast_message.message) + expect(json_response['message']).to eq(broadcast_message.message) end end - context "broadcast message doesn't exist" do - it do - get api("/internal/broadcast_message"), secret_token: secret_token + context 'broadcast message does not exist' do + it 'returns nothing' do + get api('/internal/broadcast_message'), secret_token: secret_token + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end + end + + describe 'GET /internal/broadcast_messages' do + context 'broadcast message(s) exist' do + let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } + + it 'returns active broadcast message(s)' do + get api('/internal/broadcast_messages'), secret_token: secret_token + + expect(response).to have_http_status(200) + expect(json_response[0]['message']).to eq(broadcast_message.message) + end + end + + context 'broadcast message does not exist' do + it 'returns nothing' do + get api('/internal/broadcast_messages'), secret_token: secret_token expect(response).to have_http_status(200) expect(json_response).to be_empty @@ -299,8 +321,6 @@ describe API::Internal do end context "archived project" do - let(:personal_project) { create(:empty_project, namespace: user.namespace) } - before do project.team << [user, :developer] project.archive! @@ -423,6 +443,42 @@ describe API::Internal do expect(json_response['status']).to be_truthy end end + + context 'the project path was changed' do + let!(:old_path_to_repo) { project.repository.path_to_repo } + let!(:old_full_path) { project.full_path } + let(:project_moved_message) do + <<-MSG.strip_heredoc + Project '#{old_full_path}' was moved to '#{project.full_path}'. + + Please update your Git remote and try again: + + git remote set-url origin #{project.ssh_url_to_repo} + MSG + end + + before do + project.team << [user, :developer] + project.path = 'new_path' + project.save! + end + + it 'rejects the push' do + push_with_path(key, old_path_to_repo) + + expect(response).to have_http_status(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq(project_moved_message) + end + + it 'rejects the SSH pull' do + pull_with_path(key, old_path_to_repo) + + expect(response).to have_http_status(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq(project_moved_message) + end + end end describe 'GET /internal/merge_request_urls' do @@ -565,6 +621,17 @@ describe API::Internal do ) end + def pull_with_path(key, path_to_repo, protocol = 'ssh') + post( + api("/internal/allowed"), + key_id: key.id, + project: path_to_repo, + action: 'git-upload-pack', + secret_token: secret_token, + protocol: protocol + ) + end + def push(key, project, protocol = 'ssh', env: nil) post( api("/internal/allowed"), @@ -578,6 +645,19 @@ describe API::Internal do ) end + def push_with_path(key, path_to_repo, protocol = 'ssh', env: nil) + post( + api("/internal/allowed"), + changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', + key_id: key.id, + project: path_to_repo, + action: 'git-receive-pack', + secret_token: secret_token, + protocol: protocol, + env: env + ) + end + def archive(key, project) post( api("/internal/allowed"), diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index e5e5872dc1f..8d647eb1c7e 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -11,7 +11,7 @@ describe API::Jobs, :api do ref: project.default_branch) end - let!(:build) { create(:ci_build, pipeline: pipeline) } + let!(:job) { create(:ci_build, pipeline: pipeline) } let(:user) { create(:user) } let(:api_user) { user } @@ -42,13 +42,13 @@ describe API::Jobs, :api do end it 'returns pipeline data' do - json_build = json_response.first + json_job = json_response.first - expect(json_build['pipeline']).not_to be_empty - expect(json_build['pipeline']['id']).to eq build.pipeline.id - expect(json_build['pipeline']['ref']).to eq build.pipeline.ref - expect(json_build['pipeline']['sha']).to eq build.pipeline.sha - expect(json_build['pipeline']['status']).to eq build.pipeline.status + expect(json_job['pipeline']).not_to be_empty + expect(json_job['pipeline']['id']).to eq job.pipeline.id + expect(json_job['pipeline']['ref']).to eq job.pipeline.ref + expect(json_job['pipeline']['sha']).to eq job.pipeline.sha + expect(json_job['pipeline']['status']).to eq job.pipeline.status end context 'filter project with one scope element' do @@ -79,7 +79,7 @@ describe API::Jobs, :api do context 'unauthorized user' do let(:api_user) { nil } - it 'does not return project builds' do + it 'does not return project jobs' do expect(response).to have_http_status(401) end end @@ -105,13 +105,13 @@ describe API::Jobs, :api do end it 'returns pipeline data' do - json_build = json_response.first + json_job = json_response.first - expect(json_build['pipeline']).not_to be_empty - expect(json_build['pipeline']['id']).to eq build.pipeline.id - expect(json_build['pipeline']['ref']).to eq build.pipeline.ref - expect(json_build['pipeline']['sha']).to eq build.pipeline.sha - expect(json_build['pipeline']['status']).to eq build.pipeline.status + expect(json_job['pipeline']).not_to be_empty + expect(json_job['pipeline']['id']).to eq job.pipeline.id + expect(json_job['pipeline']['ref']).to eq job.pipeline.ref + expect(json_job['pipeline']['sha']).to eq job.pipeline.sha + expect(json_job['pipeline']['status']).to eq job.pipeline.status end context 'filter jobs with one scope element' do @@ -140,7 +140,7 @@ describe API::Jobs, :api do context 'jobs in different pipelines' do let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } - let!(:build2) { create(:ci_build, pipeline: pipeline2) } + let!(:job2) { create(:ci_build, pipeline: pipeline2) } it 'excludes jobs from other pipelines' do json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) } @@ -159,7 +159,7 @@ describe API::Jobs, :api do describe 'GET /projects/:id/jobs/:job_id' do before do - get api("/projects/#{project.id}/jobs/#{build.id}", api_user) + get api("/projects/#{project.id}/jobs/#{job.id}", api_user) end context 'authorized user' do @@ -169,12 +169,13 @@ describe API::Jobs, :api do end it 'returns pipeline data' do - json_build = json_response - expect(json_build['pipeline']).not_to be_empty - expect(json_build['pipeline']['id']).to eq build.pipeline.id - expect(json_build['pipeline']['ref']).to eq build.pipeline.ref - expect(json_build['pipeline']['sha']).to eq build.pipeline.sha - expect(json_build['pipeline']['status']).to eq build.pipeline.status + json_job = json_response + + expect(json_job['pipeline']).not_to be_empty + expect(json_job['pipeline']['id']).to eq job.pipeline.id + expect(json_job['pipeline']['ref']).to eq job.pipeline.ref + expect(json_job['pipeline']['sha']).to eq job.pipeline.sha + expect(json_job['pipeline']['status']).to eq job.pipeline.status end end @@ -189,11 +190,11 @@ describe API::Jobs, :api do describe 'GET /projects/:id/jobs/:job_id/artifacts' do before do - get api("/projects/#{project.id}/jobs/#{build.id}/artifacts", api_user) + get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user) end context 'job with artifacts' do - let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } + let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } context 'authorized user' do let(:download_headers) do @@ -204,7 +205,7 @@ describe API::Jobs, :api do it 'returns specific job artifacts' do expect(response).to have_http_status(200) expect(response.headers).to include(download_headers) - expect(response.body).to match_file(build.artifacts_file.file.file) + expect(response.body).to match_file(job.artifacts_file.file.file) end end @@ -224,14 +225,14 @@ describe API::Jobs, :api do describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do let(:api_user) { reporter } - let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } + let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } before do - build.success + job.success end - def get_for_ref(ref = pipeline.ref, job = build.name) - get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job + def get_for_ref(ref = pipeline.ref, job_name = job.name) + get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job_name end context 'when not logged in' do @@ -285,7 +286,7 @@ describe API::Jobs, :api do let(:download_headers) do { 'Content-Transfer-Encoding' => 'binary', 'Content-Disposition' => - "attachment; filename=#{build.artifacts_file.filename}" } + "attachment; filename=#{job.artifacts_file.filename}" } end it { expect(response).to have_http_status(200) } @@ -321,16 +322,16 @@ describe API::Jobs, :api do end describe 'GET /projects/:id/jobs/:job_id/trace' do - let(:build) { create(:ci_build, :trace, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace, pipeline: pipeline) } before do - get api("/projects/#{project.id}/jobs/#{build.id}/trace", api_user) + get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user) end context 'authorized user' do it 'returns specific job trace' do expect(response).to have_http_status(200) - expect(response.body).to eq(build.trace.raw) + expect(response.body).to eq(job.trace.raw) end end @@ -345,7 +346,7 @@ describe API::Jobs, :api do describe 'POST /projects/:id/jobs/:job_id/cancel' do before do - post api("/projects/#{project.id}/jobs/#{build.id}/cancel", api_user) + post api("/projects/#{project.id}/jobs/#{job.id}/cancel", api_user) end context 'authorized user' do @@ -375,10 +376,10 @@ describe API::Jobs, :api do end describe 'POST /projects/:id/jobs/:job_id/retry' do - let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } + let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } before do - post api("/projects/#{project.id}/jobs/#{build.id}/retry", api_user) + post api("/projects/#{project.id}/jobs/#{job.id}/retry", api_user) end context 'authorized user' do @@ -410,28 +411,29 @@ describe API::Jobs, :api do describe 'POST /projects/:id/jobs/:job_id/erase' do before do - post api("/projects/#{project.id}/jobs/#{build.id}/erase", user) + post api("/projects/#{project.id}/jobs/#{job.id}/erase", user) end context 'job is erasable' do - let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } it 'erases job content' do expect(response).to have_http_status(201) - expect(build).not_to have_trace - expect(build.artifacts_file.exists?).to be_falsy - expect(build.artifacts_metadata.exists?).to be_falsy + expect(job).not_to have_trace + expect(job.artifacts_file.exists?).to be_falsy + expect(job.artifacts_metadata.exists?).to be_falsy end it 'updates job' do - build.reload - expect(build.erased_at).to be_truthy - expect(build.erased_by).to eq(user) + job.reload + + expect(job.erased_at).to be_truthy + expect(job.erased_by).to eq(user) end end context 'job is not erasable' do - let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace, project: project, pipeline: pipeline) } it 'responds with forbidden' do expect(response).to have_http_status(403) @@ -439,25 +441,25 @@ describe API::Jobs, :api do end end - describe 'POST /projects/:id/jobs/:build_id/artifacts/keep' do + describe 'POST /projects/:id/jobs/:job_id/artifacts/keep' do before do - post api("/projects/#{project.id}/jobs/#{build.id}/artifacts/keep", user) + post api("/projects/#{project.id}/jobs/#{job.id}/artifacts/keep", user) end context 'artifacts did not expire' do - let(:build) do + let(:job) do create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days) end it 'keeps artifacts' do expect(response).to have_http_status(200) - expect(build.reload.artifacts_expire_at).to be_nil + expect(job.reload.artifacts_expire_at).to be_nil end end context 'no artifacts' do - let(:build) { create(:ci_build, project: project, pipeline: pipeline) } + let(:job) { create(:ci_build, project: project, pipeline: pipeline) } it 'responds with not found' do expect(response).to have_http_status(404) @@ -467,18 +469,18 @@ describe API::Jobs, :api do describe 'POST /projects/:id/jobs/:job_id/play' do before do - post api("/projects/#{project.id}/jobs/#{build.id}/play", api_user) + post api("/projects/#{project.id}/jobs/#{job.id}/play", api_user) end context 'on an playable job' do - let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) } + let(:job) { create(:ci_build, :manual, project: project, pipeline: pipeline) } context 'when user is authorized to trigger a manual action' do it 'plays the job' do expect(response).to have_http_status(200) expect(json_response['user']['id']).to eq(user.id) - expect(json_response['id']).to eq(build.id) - expect(build.reload).to be_pending + expect(json_response['id']).to eq(job.id) + expect(job.reload).to be_pending end end @@ -487,7 +489,7 @@ describe API::Jobs, :api do let(:api_user) { create(:user) } it 'does not trigger a manual action' do - expect(build.reload).to be_manual + expect(job.reload).to be_manual expect(response).to have_http_status(404) end end @@ -496,7 +498,7 @@ describe API::Jobs, :api do let(:api_user) { reporter } it 'does not trigger a manual action' do - expect(build.reload).to be_manual + expect(job.reload).to be_manual expect(response).to have_http_status(403) end end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index ab957c72984..f534332ca6c 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -4,11 +4,9 @@ describe API::Keys do let(:user) { create(:user) } let(:admin) { create(:admin) } let(:key) { create(:key, user: user) } - let(:email) { create(:email, user: user) } + let(:email) { create(:email, user: user) } describe 'GET /keys/:uid' do - before { admin } - context 'when unauthenticated' do it 'returns authentication error' do get api("/keys/#{key.id}") diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 0c6b55c1630..f7e2f1908bb 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -339,7 +339,9 @@ describe API::Labels do end context "when user is already subscribed to label" do - before { label1.subscribe(user, project) } + before do + label1.subscribe(user, project) + end it "returns 304" do post api("/projects/#{project.id}/labels/#{label1.id}/subscribe", user) @@ -358,7 +360,9 @@ describe API::Labels do end describe "POST /projects/:id/labels/:label_id/unsubscribe" do - before { label1.subscribe(user, project) } + before do + label1.subscribe(user, project) + end context "when label_id is a label title" do it "unsubscribes from the label" do @@ -381,7 +385,9 @@ describe API::Labels do end context "when user is already unsubscribed from label" do - before { label1.unsubscribe(user, project) } + before do + label1.unsubscribe(user, project) + end it "returns 304" do post api("/projects/#{project.id}/labels/#{label1.id}/unsubscribe", user) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 16e5efb2f5b..452ff4aba8e 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -334,14 +334,13 @@ describe API::MergeRequests do target_branch: 'master', author: user, labels: 'label, label2', - milestone_id: milestone.id, - remove_source_branch: true + milestone_id: milestone.id expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(%w(label label2)) expect(json_response['milestone']['id']).to eq(milestone.id) - expect(json_response['force_remove_source_branch']).to be_truthy + expect(json_response['force_remove_source_branch']).to be_falsy end it "returns 422 when source_branch equals target_branch" do @@ -404,6 +403,27 @@ describe API::MergeRequests do expect(response).to have_http_status(409) end end + + context 'accepts remove_source_branch parameter' do + let(:params) do + { title: 'Test merge_request', + source_branch: 'markdown', + target_branch: 'master', + author: user } + end + + it 'sets force_remove_source_branch to false' do + post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: false) + + expect(json_response['force_remove_source_branch']).to be_falsy + end + + it 'sets force_remove_source_branch to true' do + post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: true) + + expect(json_response['force_remove_source_branch']).to be_truthy + end + end end context 'forked projects' do diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index dd74351a2b1..ab5ea3e8f2c 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -5,8 +5,13 @@ describe API::Milestones do let!(:project) { create(:empty_project, namespace: user.namespace ) } let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } + let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } + let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } + let(:label_3) { create(:label, title: 'label_3', project: project) } - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end describe 'GET /projects/:id/milestones' do it 'returns project milestones' do @@ -226,6 +231,18 @@ describe API::Milestones do expect(json_response.first['milestone']['title']).to eq(milestone.title) end + it 'returns project issues sorted by label priority' do + issue_1 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_3]) + issue_2 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_1]) + issue_3 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_2]) + + get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + + expect(json_response.first['id']).to eq(issue_2.id) + expect(json_response.second['id']).to eq(issue_3.id) + expect(json_response.third['id']).to eq(issue_1.id) + end + it 'matches V4 response schema for a list of issues' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) @@ -242,8 +259,8 @@ describe API::Milestones do describe 'confidential issues' do let(:public_project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: public_project) } - let(:issue) { create(:issue, project: public_project, position: 2) } - let(:confidential_issue) { create(:issue, confidential: true, project: public_project, position: 1) } + let(:issue) { create(:issue, project: public_project) } + let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } before do public_project.team << [user, :developer] @@ -283,7 +300,10 @@ describe API::Milestones do expect(json_response.map { |issue| issue['id'] }).to include(issue.id) end - it 'returns issues ordered by position asc' do + it 'returns issues ordered by label priority' do + issue.labels << label_2 + confidential_issue.labels << label_1 + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) expect(response).to have_http_status(200) @@ -297,8 +317,8 @@ describe API::Milestones do end describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do - let(:merge_request) { create(:merge_request, source_project: project, position: 2) } - let(:another_merge_request) { create(:merge_request, :simple, source_project: project, position: 1) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:another_merge_request) { create(:merge_request, :simple, source_project: project) } before do milestone.merge_requests << merge_request @@ -316,6 +336,18 @@ describe API::Milestones do expect(json_response.first['milestone']['title']).to eq(milestone.title) end + it 'returns project merge_requests sorted by label priority' do + merge_request_1 = create(:labeled_merge_request, source_branch: 'branch_1', source_project: project, milestone: milestone, labels: [label_2]) + merge_request_2 = create(:labeled_merge_request, source_branch: 'branch_2', source_project: project, milestone: milestone, labels: [label_1]) + merge_request_3 = create(:labeled_merge_request, source_branch: 'branch_3', source_project: project, milestone: milestone, labels: [label_3]) + + get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) + + expect(json_response.first['id']).to eq(merge_request_2.id) + expect(json_response.second['id']).to eq(merge_request_1.id) + expect(json_response.third['id']).to eq(merge_request_3.id) + end + it 'returns a 404 error if milestone id not found' do get api("/projects/#{project.id}/milestones/1234/merge_requests", user) @@ -337,6 +369,8 @@ describe API::Milestones do it 'returns merge_requests ordered by position asc' do milestone.merge_requests << another_merge_request + another_merge_request.labels << label_1 + merge_request.labels << label_2 get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 6afcd237c3c..03f2b5950ee 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -28,7 +28,9 @@ describe API::Notes do system: true end - before { project.team << [user, :reporter] } + before do + project.team << [user, :reporter] + end describe "GET /projects/:id/noteable/:noteable_id/notes" do context "when noteable is an Issue" do @@ -58,7 +60,9 @@ describe API::Notes do end context "and issue is confidential" do - before { ext_issue.update_attributes(confidential: true) } + before do + ext_issue.update_attributes(confidential: true) + end it "returns 404" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) @@ -150,7 +154,9 @@ describe API::Notes do end context "when issue is confidential" do - before { issue.update_attributes(confidential: true) } + before do + issue.update_attributes(confidential: true) + end it "returns 404" do get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", private_user) diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 9e6957e9922..258085e503f 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -10,7 +10,9 @@ describe API::Pipelines do ref: project.default_branch, user: user) end - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end describe 'GET /projects/:id/pipelines ' do context 'authorized user' do @@ -285,7 +287,9 @@ describe API::Pipelines do describe 'POST /projects/:id/pipeline ' do context 'authorized user' do context 'with gitlab-ci.yml' do - before { stub_ci_pipeline_to_return_yaml_file } + before do + stub_ci_pipeline_to_return_yaml_file + end it 'creates and returns a new pipeline' do expect do @@ -419,7 +423,9 @@ describe API::Pipelines do context 'user without proper access rights' do let!(:reporter) { create(:user) } - before { project.team << [reporter, :reporter] } + before do + project.team << [reporter, :reporter] + end it 'rejects the action' do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 3e831373514..d92262a4c99 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -476,8 +476,9 @@ describe API::Projects do end describe 'POST /projects/user/:id' do - before { project } - before { admin } + before do + expect(project).to be_persisted + end it 'creates new project without path but with name and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1) @@ -581,7 +582,9 @@ describe API::Projects do end describe "POST /projects/:id/uploads" do - before { project } + before do + project + end it "uploads the file and returns its info" do post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") @@ -729,7 +732,9 @@ describe API::Projects do describe 'permissions' do context 'all projects' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'contains permission information' do get api("/projects", user) @@ -756,7 +761,9 @@ describe API::Projects do context 'group project' do let(:project2) { create(:empty_project, group: create(:group)) } - before { project2.group.add_owner(user) } + before do + project2.group.add_owner(user) + end it 'sets the owner and return 200' do get api("/projects/#{project2.id}", user) @@ -822,7 +829,9 @@ describe API::Projects do end describe 'GET /projects/:id/snippets' do - before { snippet } + before do + snippet + end it 'returns an array of project snippets' do get api("/projects/#{project.id}/snippets", user) @@ -879,7 +888,9 @@ describe API::Projects do end describe 'DELETE /projects/:id/snippets/:snippet_id' do - before { snippet } + before do + snippet + end it 'deletes existing project snippet' do expect do @@ -1074,14 +1085,16 @@ describe API::Projects do end describe 'PUT /projects/:id' do - before { project } - before { user } - before { user3 } - before { user4 } - before { project3 } - before { project4 } - before { project_member2 } - before { project_member } + before do + expect(project).to be_persisted + expect(user).to be_persisted + expect(user3).to be_persisted + expect(user4).to be_persisted + expect(project3).to be_persisted + expect(project4).to be_persisted + expect(project_member2).to be_persisted + expect(project_member).to be_persisted + end it 'returns 400 when nothing sent' do project_param = {} diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 9556c99dea1..d554c242916 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -190,17 +190,23 @@ describe API::Runner do pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, commands: "ls\ndate") end - before { project.runners << runner } + before do + project.runners << runner + end describe 'POST /api/v4/jobs/request' do let!(:last_update) {} let!(:new_update) { } let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' } - before { stub_container_registry_config(enabled: false) } + before do + stub_container_registry_config(enabled: false) + end shared_examples 'no jobs available' do - before { request_job } + before do + request_job + end context 'when runner sends version in User-Agent' do context 'for stable version' do @@ -277,7 +283,9 @@ describe API::Runner do end context 'when jobs are finished' do - before { job.success } + before do + job.success + end it_behaves_like 'no jobs available' end @@ -356,8 +364,11 @@ describe API::Runner do expect(json_response['token']).to eq(job.token) expect(json_response['job_info']).to eq(expected_job_info) expect(json_response['git_info']).to eq(expected_git_info) - expect(json_response['image']).to eq({ 'name' => 'ruby:2.1' }) - expect(json_response['services']).to eq([{ 'name' => 'postgres' }]) + expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' }) + expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil, + 'alias' => nil, 'command' => nil }, + { 'name' => 'docker:dind', 'entrypoint' => '/bin/sh', + 'alias' => 'docker', 'command' => 'sleep 30' }]) expect(json_response['steps']).to eq(expected_steps) expect(json_response['artifacts']).to eq(expected_artifacts) expect(json_response['cache']).to eq(expected_cache) @@ -505,10 +516,14 @@ describe API::Runner do end context 'when job has no tags' do - before { job.update(tags: []) } + before do + job.update(tags: []) + end context 'when runner is allowed to pick untagged jobs' do - before { runner.update_column(:run_untagged, true) } + before do + runner.update_column(:run_untagged, true) + end it 'picks job' do request_job @@ -518,7 +533,9 @@ describe API::Runner do end context 'when runner is not allowed to pick untagged jobs' do - before { runner.update_column(:run_untagged, false) } + before do + runner.update_column(:run_untagged, false) + end it_behaves_like 'no jobs available' end @@ -558,7 +575,9 @@ describe API::Runner do end context 'when registry is enabled' do - before { stub_container_registry_config(enabled: true, host_port: registry_url) } + before do + stub_container_registry_config(enabled: true, host_port: registry_url) + end it 'sends registry credentials key' do request_job @@ -569,7 +588,9 @@ describe API::Runner do end context 'when registry is disabled' do - before { stub_container_registry_config(enabled: false, host_port: registry_url) } + before do + stub_container_registry_config(enabled: false, host_port: registry_url) + end it 'does not send registry credentials' do request_job @@ -591,7 +612,9 @@ describe API::Runner do describe 'PUT /api/v4/jobs/:id' do let(:job) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) } - before { job.run! } + before do + job.run! + end context 'when status is given' do it 'mark job as succeeded' do @@ -646,7 +669,9 @@ describe API::Runner do let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } let(:update_interval) { 10.seconds.to_i } - before { initial_patch_the_trace } + before do + initial_patch_the_trace + end context 'when request is valid' do it 'gets correct response' do @@ -788,7 +813,9 @@ describe API::Runner do let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } - before { job.run! } + before do + job.run! + end describe 'POST /api/v4/jobs/:id/artifacts/authorize' do context 'when using token as parameter' do @@ -894,13 +921,17 @@ describe API::Runner do end context 'when uses regular file post' do - before { upload_artifacts(file_upload, headers_with_token, false) } + before do + upload_artifacts(file_upload, headers_with_token, false) + end it_behaves_like 'successful artifacts upload' end context 'when uses accelerated file post' do - before { upload_artifacts(file_upload, headers_with_token, true) } + before do + upload_artifacts(file_upload, headers_with_token, true) + end it_behaves_like 'successful artifacts upload' end @@ -1054,7 +1085,9 @@ describe API::Runner do allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir) end - after { FileUtils.remove_entry @tmpdir } + after do + FileUtils.remove_entry @tmpdir + end it' "fails to post artifacts for outside of tmp path"' do upload_artifacts(file_upload, headers_with_token) @@ -1076,7 +1109,9 @@ describe API::Runner do describe 'GET /api/v4/jobs/:id/artifacts' do let(:token) { job.token } - before { download_artifact } + before do + download_artifact + end context 'when job has artifacts' do let(:job) { create(:ci_build, :artifacts) } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 2398ae6219c..ede48b1c888 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -40,7 +40,10 @@ describe API::Settings, 'Settings' do plantuml_url: 'http://plantuml.example.com', default_snippet_visibility: 'internal', restricted_visibility_levels: ['public'], - default_artifacts_expire_in: '2 days' + default_artifacts_expire_in: '2 days', + help_page_text: 'custom help text', + help_page_hide_commercial_content: true, + help_page_support_url: 'http://example.com/help' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) expect(json_response['signin_enabled']).to be_falsey @@ -53,6 +56,9 @@ describe API::Settings, 'Settings' do expect(json_response['default_snippet_visibility']).to eq('internal') expect(json_response['restricted_visibility_levels']).to eq(['public']) expect(json_response['default_artifacts_expire_in']).to eq('2 days') + expect(json_response['help_page_text']).to eq('custom help text') + expect(json_response['help_page_hide_commercial_content']).to be_truthy + expect(json_response['help_page_support_url']).to eq('http://example.com/help') end end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 2eb191d6049..f65b475fe44 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -5,7 +5,9 @@ describe API::SystemHooks do let(:admin) { create(:admin) } let!(:hook) { create(:system_hook, url: "http://example.com") } - before { stub_request(:post, hook.url) } + before do + stub_request(:post, hook.url) + end describe "GET /hooks" do context "when no user" do diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index cb55985e3f5..f8af9295842 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -2,14 +2,18 @@ require 'spec_helper' describe API::Templates do context 'the Template Entity' do - before { get api('/templates/gitignores/Ruby') } + before do + get api('/templates/gitignores/Ruby') + end it { expect(json_response['name']).to eq('Ruby') } it { expect(json_response['content']).to include('*.gem') } end context 'the TemplateList Entity' do - before { get api('/templates/gitignores') } + before do + get api('/templates/gitignores') + end it { expect(json_response.first['name']).not_to be_nil } it { expect(json_response.first['content']).to be_nil } @@ -47,7 +51,9 @@ describe API::Templates do end context 'the License Template Entity' do - before { get api('/templates/licenses/mit') } + before do + get api('/templates/licenses/mit') + end it 'returns a license template' do expect(json_response['key']).to eq('mit') diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index ec51b96c86b..bc869ea1108 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -160,7 +160,9 @@ describe API::Users do end describe "POST /users" do - before { admin } + before do + admin + end it "creates user" do expect do @@ -349,7 +351,9 @@ describe API::Users do describe "PUT /users/:id" do let!(:admin_user) { create(:admin) } - before { admin } + before do + admin + end it "updates user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } @@ -373,6 +377,16 @@ describe API::Users do expect(user.reload.organization).to eq('GitLab') end + it 'updates user with avatar' do + put api("/users/#{user.id}", admin), { avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + + user.reload + + expect(user.avatar).to be_present + expect(response).to have_http_status(200) + expect(json_response['avatar_url']).to include(user.avatar_path) + end + it 'updates user with his own email' do put api("/users/#{user.id}", admin), email: user.email expect(response).to have_http_status(200) @@ -502,7 +516,9 @@ describe API::Users do end describe "POST /users/:id/keys" do - before { admin } + before do + admin + end it "does not create invalid ssh key" do post api("/users/#{user.id}/keys", admin), { title: "invalid key" } @@ -532,7 +548,9 @@ describe API::Users do end describe 'GET /user/:id/keys' do - before { admin } + before do + admin + end context 'when unauthenticated' do it 'returns authentication error' do @@ -563,7 +581,9 @@ describe API::Users do end describe 'DELETE /user/:id/keys/:key_id' do - before { admin } + before do + admin + end context 'when unauthenticated' do it 'returns authentication error' do @@ -601,7 +621,9 @@ describe API::Users do end describe "POST /users/:id/emails" do - before { admin } + before do + admin + end it "does not create invalid email" do post api("/users/#{user.id}/emails", admin), {} @@ -625,7 +647,9 @@ describe API::Users do end describe 'GET /user/:id/emails' do - before { admin } + before do + admin + end context 'when unauthenticated' do it 'returns authentication error' do @@ -662,7 +686,9 @@ describe API::Users do end describe 'DELETE /user/:id/emails/:email_id' do - before { admin } + before do + admin + end context 'when unauthenticated' do it 'returns authentication error' do @@ -708,7 +734,10 @@ describe API::Users do describe "DELETE /users/:id" do let!(:namespace) { user.namespace } let!(:issue) { create(:issue, author: user) } - before { admin } + + before do + admin + end it "deletes user" do Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) } @@ -1068,7 +1097,10 @@ describe API::Users do end describe 'POST /users/:id/block' do - before { admin } + before do + admin + end + it 'blocks existing user' do post api("/users/#{user.id}/block", admin) expect(response).to have_http_status(201) @@ -1096,7 +1128,10 @@ describe API::Users do describe 'POST /users/:id/unblock' do let(:blocked_user) { create(:user, state: 'blocked') } - before { admin } + + before do + admin + end it 'unblocks existing user' do post api("/users/#{user.id}/unblock", admin) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 286de277ae7..83c675792f4 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -137,6 +137,18 @@ describe Ci::API::Builds do end end end + + context 'when docker configuration options are used' do + let!(:build) { create(:ci_build, :extended_options, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + expect(json_response['options']['image']).to eq('ruby:2.1') + expect(json_response['options']['services']).to eq(['postgres', 'docker:dind']) + end + end end context 'when builds are finished' do @@ -229,7 +241,9 @@ describe Ci::API::Builds do end context 'when runner is allowed to pick untagged builds' do - before { runner.update_column(:run_untagged, true) } + before do + runner.update_column(:run_untagged, true) + end it 'picks build' do register_builds @@ -455,7 +469,9 @@ describe Ci::API::Builds do let(:token) { build.token } let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) } - before { build.run! } + before do + build.run! + end describe "POST /builds/:id/artifacts/authorize" do context "authorizes posting artifact to running build" do @@ -511,7 +527,9 @@ describe Ci::API::Builds do end context 'authorization token is invalid' do - before { post authorize_url, { token: 'invalid', filesize: 100 } } + before do + post authorize_url, { token: 'invalid', filesize: 100 } + end it 'responds with forbidden' do expect(response).to have_http_status(403) diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index 0b9733221d8..78b2be350cd 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -12,7 +12,9 @@ describe Ci::API::Runners do describe "POST /runners/register" do context 'when runner token is provided' do - before { post ci_api("/runners/register"), token: registration_token } + before do + post ci_api("/runners/register"), token: registration_token + end it 'creates runner with default values' do expect(response).to have_http_status 201 @@ -69,7 +71,10 @@ describe Ci::API::Runners do context 'when project token is provided' do let(:project) { FactoryGirl.create(:empty_project) } - before { post ci_api("/runners/register"), token: project.runners_token } + + before do + post ci_api("/runners/register"), token: project.runners_token + end it 'creates runner' do expect(response).to have_http_status 201 diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 6a83024d0d5..b08148eca3c 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -316,6 +316,26 @@ describe 'Git HTTP requests', lib: true do it_behaves_like 'pushes require Basic HTTP Authentication' end end + + context 'and the user requests a redirected path' do + let!(:redirect) { project.route.create_redirect('foo/bar') } + let(:path) { "#{redirect.path}.git" } + let(:project_moved_message) do + <<-MSG.strip_heredoc + Project '#{redirect.path}' was moved to '#{project.full_path}'. + + Please update your Git remote and try again: + + git remote set-url origin #{project.http_url_to_repo} + MSG + end + + it 'downloads get status 404 with "project was moved" message' do + clone_get(path, {}) + expect(response).to have_http_status(:not_found) + expect(response.body).to match(project_moved_message) + end + end end context "when the project is private" do @@ -505,6 +525,33 @@ describe 'Git HTTP requests', lib: true do Rack::Attack::Allow2Ban.reset(ip, options) end end + + context 'and the user requests a redirected path' do + let!(:redirect) { project.route.create_redirect('foo/bar') } + let(:path) { "#{redirect.path}.git" } + let(:project_moved_message) do + <<-MSG.strip_heredoc + Project '#{redirect.path}' was moved to '#{project.full_path}'. + + Please update your Git remote and try again: + + git remote set-url origin #{project.http_url_to_repo} + MSG + end + + it 'downloads get status 404 with "project was moved" message' do + clone_get(path, env) + expect(response).to have_http_status(:not_found) + expect(response.body).to match(project_moved_message) + end + + it 'uploads get status 404 with "project was moved" message' do + upload(path, env) do |response| + expect(response).to have_http_status(:not_found) + expect(response.body).to match(project_moved_message) + end + end + end end context "when the user doesn't have access to the project" do @@ -627,7 +674,9 @@ describe 'Git HTTP requests', lib: true do let(:path) { "/#{project.path_with_namespace}/info/refs" } context "when no params are added" do - before { get path } + before do + get path + end it "redirects to the .git suffix version" do expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs") @@ -636,7 +685,10 @@ describe 'Git HTTP requests', lib: true do context "when the upload-pack service is requested" do let(:params) { { service: 'git-upload-pack' } } - before { get path, params } + + before do + get path, params + end it "redirects to the .git suffix version" do expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") @@ -645,7 +697,10 @@ describe 'Git HTTP requests', lib: true do context "when the receive-pack service is requested" do let(:params) { { service: 'git-receive-pack' } } - before { get path, params } + + before do + get path, params + end it "redirects to the .git suffix version" do expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") @@ -654,7 +709,10 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } - before { get path, params } + + before do + get path, params + end it "redirects to the sign-in page" do expect(response).to redirect_to(new_user_session_path) @@ -669,7 +727,7 @@ describe 'Git HTTP requests', lib: true do end context "POST git-receive-pack" do - it "failes to find a route" do + it "fails to find a route" do expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) end end @@ -695,7 +753,9 @@ describe 'Git HTTP requests', lib: true do end context "when the file does not exist" do - before { get "/#{project.path_with_namespace}/blob/master/info/refs" } + before do + get "/#{project.path_with_namespace}/blob/master/info/refs" + end it "returns not found" do expect(response).to have_http_status(:not_found) diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 54d7cf5f10d..5e4cf05748e 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -6,7 +6,9 @@ describe JwtController do let(:service_name) { 'test' } let(:parameters) { { service: service_name } } - before { stub_const('JwtController::SERVICES', service_name => service_class) } + before do + stub_const('JwtController::SERVICES', service_name => service_class) + end context 'existing service' do subject! { get '/jwt/auth', parameters } diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 0a6778ae2ef..95d40138fea 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -93,13 +93,17 @@ describe 'project routing' do end context 'name with dot' do - before { allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq.keys', any_args).and_return(true) } + before do + allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq.keys', any_args).and_return(true) + end it { expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') } end context 'with nested group' do - before { allow(Project).to receive(:find_by_full_path).with('gitlab/subgroup/gitlabhq', any_args).and_return(true) } + before do + allow(Project).to receive(:find_by_full_path).with('gitlab/subgroup/gitlabhq', any_args).and_return(true) + end it { expect(get('/gitlab/subgroup/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlabhq') } end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index a62af13cf0c..a45839b16f5 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -286,7 +286,9 @@ end describe "Groups", "routing" do let(:name) { 'complex.group-namegit' } - before { allow_any_instance_of(GroupUrlConstrainer).to receive(:matches?).and_return(true) } + before do + allow_any_instance_of(GroupUrlConstrainer).to receive(:matches?).and_return(true) + end it "to #show" do expect(get("/groups/#{name}")).to route_to('groups#show', id: name) diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb new file mode 100644 index 00000000000..18df62dec3e --- /dev/null +++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/add_timestamps' + +describe RuboCop::Cop::Migration::AddTimestamps do + include CopHelper + + subject(:cop) { described_class.new } + let(:migration_with_add_timestamps) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + add_timestamps(:users) + end + end + ) + end + + let(:migration_without_add_timestamps) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + end + end + ) + end + + let(:migration_with_add_timestamps_with_timezone) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + add_timestamps_with_timezone(:users) + end + end + ) + end + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when the "add_timestamps" method is used' do + inspect_source(cop, migration_with_add_timestamps) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([7]) + end + end + + it 'does not register an offense when the "add_timestamps" method is not used' do + inspect_source(cop, migration_without_add_timestamps) + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + + it 'does not register an offense when the "add_timestamps_with_timezone" method is used' do + inspect_source(cop, migration_with_add_timestamps_with_timezone) + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, migration_with_add_timestamps) + inspect_source(cop, migration_without_add_timestamps) + inspect_source(cop, migration_with_add_timestamps_with_timezone) + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb new file mode 100644 index 00000000000..388b086ce6a --- /dev/null +++ b/spec/rubocop/cop/migration/datetime_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/datetime' + +describe RuboCop::Cop::Migration::Datetime do + include CopHelper + + subject(:cop) { described_class.new } + let(:migration_with_datetime) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + add_column(:users, :last_sign_in, :datetime) + end + end + ) + end + + let(:migration_without_datetime) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + end + end + ) + end + + let(:migration_with_datetime_with_timezone) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + add_column(:users, :last_sign_in, :datetime_with_timezone) + end + end + ) + end + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when the ":datetime" data type is used' do + inspect_source(cop, migration_with_datetime) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([7]) + end + end + + it 'does not register an offense when the ":datetime" data type is not used' do + inspect_source(cop, migration_without_datetime) + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + + it 'does not register an offense when the ":datetime_with_timezone" data type is used' do + inspect_source(cop, migration_with_datetime_with_timezone) + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, migration_with_datetime) + inspect_source(cop, migration_without_datetime) + inspect_source(cop, migration_with_datetime_with_timezone) + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb new file mode 100644 index 00000000000..cdf1423d0c5 --- /dev/null +++ b/spec/rubocop/cop/migration/timestamps_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/timestamps' + +describe RuboCop::Cop::Migration::Timestamps do + include CopHelper + + subject(:cop) { described_class.new } + let(:migration_with_timestamps) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + create_table :users do |t| + t.string :username, null: false + t.timestamps null: true + t.string :password + end + end + end + ) + end + + let(:migration_without_timestamps) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + create_table :users do |t| + t.string :username, null: false + t.string :password + end + end + end + ) + end + + let(:migration_with_timestamps_with_timezone) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + create_table :users do |t| + t.string :username, null: false + t.timestamps_with_timezone null: true + t.string :password + end + end + end + ) + end + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when the "timestamps" method is used' do + inspect_source(cop, migration_with_timestamps) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([8]) + end + end + + it 'does not register an offense when the "timestamps" method is not used' do + inspect_source(cop, migration_without_timestamps) + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + + it 'does not register an offense when the "timestamps_with_timezone" method is used' do + inspect_source(cop, migration_with_timestamps_with_timezone) + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, migration_with_timestamps) + inspect_source(cop, migration_without_timestamps) + inspect_source(cop, migration_with_timestamps_with_timezone) + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/rubocop/cop/rspec/single_line_hook_spec.rb b/spec/rubocop/cop/rspec/single_line_hook_spec.rb new file mode 100644 index 00000000000..6cf0831d3ad --- /dev/null +++ b/spec/rubocop/cop/rspec/single_line_hook_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/rspec/single_line_hook' + +describe RuboCop::Cop::RSpec::SingleLineHook do + include CopHelper + + subject(:cop) { described_class.new } + + # Override `CopHelper#inspect_source` to always appear to be in a spec file, + # so that our RSpec-only cop actually runs + def inspect_source(*args) + super(*args, 'foo_spec.rb') + end + + it 'registers an offense for a single-line `before` block' do + inspect_source(cop, 'before { do_something }') + + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq(['before { do_something }']) + end + + it 'registers an offense for a single-line `after` block' do + inspect_source(cop, 'after(:each) { undo_something }') + + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq(['after(:each) { undo_something }']) + end + + it 'registers an offense for a single-line `around` block' do + inspect_source(cop, 'around { |ex| do_something_else }') + + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq(['around { |ex| do_something_else }']) + end + + it 'ignores a multi-line `before` block' do + inspect_source(cop, ['before do', + ' do_something', + 'end']) + + expect(cop.offenses.size).to eq(0) + end + + it 'ignores a multi-line `after` block' do + inspect_source(cop, ['after(:each) do', + ' undo_something', + 'end']) + + expect(cop.offenses.size).to eq(0) + end + + it 'ignores a multi-line `around` block' do + inspect_source(cop, ['around do |ex|', + ' do_something_else', + 'end']) + + expect(cop.offenses.size).to eq(0) + end +end diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index e2511e8968c..b92c1c28ba8 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe BuildDetailsEntity do set(:user) { create(:admin) } - it 'inherits from BuildEntity' do - expect(described_class).to be < BuildEntity + it 'inherits from JobEntity' do + expect(described_class).to be < JobEntity end describe '#as_json' do @@ -29,7 +29,7 @@ describe BuildDetailsEntity do it 'contains the needed key value pairs' do expect(subject).to include(:coverage, :erased_at, :duration) - expect(subject).to include(:artifacts, :runner, :pipeline) + expect(subject).to include(:runner, :pipeline) expect(subject).to include(:raw_path, :merge_request) expect(subject).to include(:new_issue_path) end diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index d2ad6c44702..4c52a00b442 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -62,7 +62,9 @@ describe EnvironmentSerializer do subject { serializer.represent(resource) } context 'when there is a single environment' do - before { create(:environment, name: 'staging') } + before do + create(:environment, name: 'staging') + end it 'represents one standalone environment' do expect(subject.count).to eq 1 @@ -138,7 +140,9 @@ describe EnvironmentSerializer do context 'when resource is paginatable relation' do context 'when there is a single environment object in relation' do - before { create(:environment) } + before do + create(:environment) + end it 'serializes environments' do expect(subject.first).to have_key :id @@ -146,7 +150,9 @@ describe EnvironmentSerializer do end context 'when multiple environment objects are serialized' do - before { create_list(:environment, 3) } + before do + create_list(:environment, 3) + end it 'serializes appropriate number of objects' do expect(subject.count).to be 2 diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/job_entity_spec.rb index e51ff9fc709..5ca7bf2fcaf 100644 --- a/spec/serializers/build_entity_spec.rb +++ b/spec/serializers/job_entity_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe BuildEntity do +describe JobEntity do let(:user) { create(:user) } - let(:build) { create(:ci_build) } - let(:project) { build.project } + let(:job) { create(:ci_build) } + let(:project) { job.project } let(:request) { double('request') } before do @@ -12,12 +12,12 @@ describe BuildEntity do end let(:entity) do - described_class.new(build, request: request) + described_class.new(job, request: request) end subject { entity.as_json } - it 'contains paths to build page action' do + it 'contains paths to job page action' do expect(subject).to include(:build_path) end @@ -27,7 +27,7 @@ describe BuildEntity do end it 'contains whether it is playable' do - expect(subject[:playable]).to eq build.playable? + expect(subject[:playable]).to eq job.playable? end it 'contains timestamps' do @@ -39,9 +39,9 @@ describe BuildEntity do expect(subject[:status]).to include :icon, :favicon, :text, :label end - context 'when build is retryable' do + context 'when job is retryable' do before do - build.update(status: :failed) + job.update(status: :failed) end it 'contains cancel path' do @@ -49,9 +49,9 @@ describe BuildEntity do end end - context 'when build is cancelable' do + context 'when job is cancelable' do before do - build.update(status: :running) + job.update(status: :running) end it 'contains cancel path' do @@ -59,7 +59,7 @@ describe BuildEntity do end end - context 'when build is a regular build' do + context 'when job is a regular job' do it 'does not contain path to play action' do expect(subject).not_to include(:play_path) end @@ -69,8 +69,8 @@ describe BuildEntity do end end - context 'when build is a manual action' do - let(:build) { create(:ci_build, :manual) } + context 'when job is a manual action' do + let(:job) { create(:ci_build, :manual) } context 'when user is allowed to trigger action' do before do @@ -99,4 +99,25 @@ describe BuildEntity do end end end + + context 'when job is generic commit status' do + let(:job) { create(:generic_commit_status, target_url: 'http://google.com') } + + it 'contains paths to target action' do + expect(subject).to include(:build_path) + end + + it 'does not contain paths to other action paths' do + expect(subject).not_to include(:retry_path, :cancel_path, :play_path) + end + + it 'contains timestamps' do + expect(subject).to include(:created_at, :updated_at) + end + + it 'contains details' do + expect(subject).to include :status + expect(subject[:status]).to include :icon, :favicon, :text, :label + end + end end diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb index 03cc5ae9b63..d28dec9592a 100644 --- a/spec/serializers/pipeline_details_entity_spec.rb +++ b/spec/serializers/pipeline_details_entity_spec.rb @@ -51,7 +51,9 @@ describe PipelineDetailsEntity do end context 'user has ability to retry pipeline' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'retryable flag is true' do expect(subject[:flags][:retryable]).to eq true @@ -77,7 +79,9 @@ describe PipelineDetailsEntity do end context 'user has ability to cancel pipeline' do - before { project.add_developer(user) } + before do + project.add_developer(user) + end it 'cancelable flag is true' do expect(subject[:flags][:cancelable]).to eq true @@ -91,6 +95,20 @@ describe PipelineDetailsEntity do end end + context 'when pipeline has commit statuses' do + let(:pipeline) { create(:ci_empty_pipeline) } + + before do + create(:generic_commit_status, pipeline: pipeline) + end + + it 'contains stages' do + expect(subject).to include(:details) + expect(subject[:details]).to include(:stages) + expect(subject[:details][:stages].first).to include(name: 'external') + end + end + context 'when pipeline has YAML errors' do let(:pipeline) do create(:ci_pipeline, config: { rspec: { invalid: :value } }) diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index a059c2cc736..46650f3a80d 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -51,7 +51,9 @@ describe PipelineEntity do end context 'user has ability to retry pipeline' do - before { project.team << [user, :developer] } + before do + project.team << [user, :developer] + end it 'contains retry path' do expect(subject[:retry_path]).to be_present @@ -77,7 +79,9 @@ describe PipelineEntity do end context 'user has ability to cancel pipeline' do - before { project.add_developer(user) } + before do + project.add_developer(user) + end it 'contains cancel path' do expect(subject[:cancel_path]).to be_present diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 34b19fb9fc4..44813656aff 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -69,7 +69,9 @@ describe PipelineSerializer do let(:pagination) { { page: 1, per_page: 2 } } context 'when a single pipeline object is present in relation' do - before { create(:ci_empty_pipeline) } + before do + create(:ci_empty_pipeline) + end it 'serializes pipeline relation' do expect(subject.first).to have_key :id @@ -77,7 +79,9 @@ describe PipelineSerializer do end context 'when a multiple pipeline objects are being serialized' do - before { create_list(:ci_empty_pipeline, 3) } + before do + create_list(:ci_empty_pipeline, 3) + end it 'serializes appropriate number of objects' do expect(subject.count).to be 2 diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb index 64b3217b809..40e303f7b89 100644 --- a/spec/serializers/stage_entity_spec.rb +++ b/spec/serializers/stage_entity_spec.rb @@ -54,6 +54,17 @@ describe StageEntity do it 'exposes the group key' do expect(subject).to include :groups end + + context 'and contains commit status' do + before do + create(:generic_commit_status, pipeline: pipeline, stage: 'test') + end + + it 'contains commit status' do + groups = subject[:groups].map { |group| group[:name] } + expect(groups).to include('generic') + end + end end end end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index e273dfe1552..60cb7a9440f 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -34,7 +34,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end context 'for changed configuration' do - before { stub_application_setting(container_registry_token_expire_delay: expire_delay) } + before do + stub_application_setting(container_registry_token_expire_delay: expire_delay) + end it { expect(expires_at).to be_within(2.seconds).of(Time.now + expire_delay.minutes) } end @@ -117,7 +119,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end context 'allow developer to push images' do - before { project.team << [current_user, :developer] } + before do + project.team << [current_user, :developer] + end let(:current_params) do { scope: "repository:#{project.path_with_namespace}:push" } @@ -128,7 +132,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end context 'allow reporter to pull images' do - before { project.team << [current_user, :reporter] } + before do + project.team << [current_user, :reporter] + end context 'when pulling from root level repository' do let(:current_params) do @@ -141,7 +147,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end context 'return a least of privileges' do - before { project.team << [current_user, :reporter] } + before do + project.team << [current_user, :reporter] + end let(:current_params) do { scope: "repository:#{project.path_with_namespace}:push,pull" } @@ -152,7 +160,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end context 'disallow guest to pull or push images' do - before { project.team << [current_user, :guest] } + before do + project.team << [current_user, :guest] + end let(:current_params) do { scope: "repository:#{project.path_with_namespace}:pull,push" } @@ -355,7 +365,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'for project without container registry' do let(:project) { create(:empty_project, :public, container_registry_enabled: false) } - before { project.update(container_registry_enabled: false) } + before do + project.update(container_registry_enabled: false) + end context 'disallow when pulling' do let(:current_params) do diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb index c44e6b2a48b..efefa8e8eca 100644 --- a/spec/services/ci/update_build_queue_service_spec.rb +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -9,7 +9,9 @@ describe Ci::UpdateBuildQueueService, :services do let(:runner) { create(:ci_runner) } context 'when there are runner that can pick build' do - before { build.project.runners << runner } + before do + build.project.runners << runner + end it 'ticks runner queue value' do expect { subject.execute(build) } @@ -36,7 +38,9 @@ describe Ci::UpdateBuildQueueService, :services do end context 'when there are no runners that can pick build' do - before { build.tag_list = [:docker] } + before do + build.tag_list = [:docker] + end it 'does not tick runner queue value' do expect { subject.execute(build) } diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 5398b5c3f7e..6cf4342ad4c 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -204,7 +204,9 @@ describe CreateDeploymentService, services: true do let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) } context "while updating the 'first_deployed_to_production_at' time" do - before { merge_request.mark_as_merged } + before do + merge_request.mark_as_merged + end context "for merge requests merged before the current deploy" do it "sets the time if the deploy's environment is 'production'" do diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index bcb62429275..fbd9026640c 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -14,7 +14,9 @@ describe Groups::CreateService, '#execute', services: true do end context "cannot create group with restricted visibility level" do - before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_visibility_levels).and_return([Gitlab::VisibilityLevel::PUBLIC]) } + before do + allow_any_instance_of(ApplicationSetting).to receive(:restricted_visibility_levels).and_return([Gitlab::VisibilityLevel::PUBLIC]) + end it { is_expected.not_to be_persisted } end @@ -25,7 +27,9 @@ describe Groups::CreateService, '#execute', services: true do let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) } context 'as group owner' do - before { group.add_owner(user) } + before do + group.add_owner(user) + end it { is_expected.to be_persisted } end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index dab1a3469f7..ae9d2b2855d 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -155,7 +155,9 @@ describe Issues::CreateService, services: true do context 'issue create service' do context 'assignees' do - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'removes assignee when user id is invalid' do opts = { title: 'Title', description: 'Description', assignee_ids: [-1] } @@ -204,9 +206,9 @@ describe Issues::CreateService, services: true do end end - it_behaves_like 'new issuable record that supports slash commands' + it_behaves_like 'new issuable record that supports quick actions' - context 'Slash commands' do + context 'Quick actions' do context 'with assignee and milestone in params and command' do let(:opts) do { diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 9f8346d52bb..d1dd1466d95 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -251,12 +251,18 @@ describe Issues::MoveService, services: true do end context 'user is reporter only in new project' do - before { new_project.team << [user, :reporter] } + before do + new_project.team << [user, :reporter] + end + it { expect { move }.to raise_error(StandardError, /permissions/) } end context 'user is reporter only in old project' do - before { old_project.team << [user, :reporter] } + before do + old_project.team << [user, :reporter] + end + it { expect { move }.to raise_error(StandardError, /permissions/) } end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a78866a2c32..c26642f5015 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -295,7 +295,9 @@ describe Issues::UpdateService, services: true do end context 'when issue has the `label` label' do - before { issue.labels << label } + before do + issue.labels << label + end it 'does not send notifications for existing labels' do opts = { label_ids: [label.id, label2.id] } @@ -329,7 +331,9 @@ describe Issues::UpdateService, services: true do it { expect(issue.tasks?).to eq(true) } context 'when tasks are marked as completed' do - before { update_issue(description: "- [x] Task 1\n- [X] Task 2") } + before do + update_issue(description: "- [x] Task 1\n- [X] Task 2") + end it 'creates system note about task status change' do note1 = find_note('marked the task **Task 1** as completed') @@ -417,7 +421,9 @@ describe Issues::UpdateService, services: true do context 'when remove_label_ids and label_ids are passed' do let(:params) { { label_ids: [], remove_label_ids: [label.id] } } - before { issue.update_attributes(labels: [label, label3]) } + before do + issue.update_attributes(labels: [label, label3]) + end it 'ignores the label_ids parameter' do expect(result.label_ids).not_to be_empty @@ -431,7 +437,9 @@ describe Issues::UpdateService, services: true do context 'when add_label_ids and remove_label_ids are passed' do let(:params) { { add_label_ids: [label3.id], remove_label_ids: [label.id] } } - before { issue.update_attributes(labels: [label]) } + before do + issue.update_attributes(labels: [label]) + end it 'adds the passed labels' do expect(result.label_ids).to include(label3.id) diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index 5ce8e17976b..5a05ab3ea50 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -5,7 +5,9 @@ describe Members::CreateService, services: true do let(:user) { create(:user) } let(:project_user) { create(:user) } - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'adds user to members' do params = { user_ids: project_user.id.to_s, access_level: Gitlab::Access::GUEST } diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 6f9d1208b1d..01ef52396d7 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -206,7 +206,9 @@ describe MergeRequests::BuildService, services: true do context 'branch starts with external issue IID followed by a hyphen' do let(:source_branch) { '12345-fix-issue' } - before { allow(project).to receive(:default_issues_tracker?).and_return(false) } + before do + allow(project).to receive(:default_issues_tracker?).and_return(false) + end it 'sets the title to: Resolves External Issue $issue-iid' do expect(merge_request.title).to eq('Resolve External Issue 12345') diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 2963f62cc7d..2026b7f6510 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -108,7 +108,7 @@ describe MergeRequests::CreateService, services: true do end end - it_behaves_like 'new issuable record that supports slash commands' do + it_behaves_like 'new issuable record that supports quick actions' do let(:default_params) do { source_branch: 'feature', @@ -117,7 +117,7 @@ describe MergeRequests::CreateService, services: true do end end - context 'Slash commands' do + context 'Quick actions' do context 'with assignee and milestone in params and command' do let(:merge_request) { described_class.new(project, user, opts).execute } let(:milestone) { create(:milestone, project: project) } @@ -150,7 +150,9 @@ describe MergeRequests::CreateService, services: true do context 'asssignee_id' do let(:assignee) { create(:user) } - before { project.team << [user, :master] } + before do + project.team << [user, :master] + end it 'removes assignee_id when user id is invalid' do opts = { title: 'Title', description: 'Description', assignee_id: -1 } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index d96f819e66a..b3b188a805f 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -81,7 +81,9 @@ describe MergeRequests::MergeService, services: true do end context "when jira_issue_transition_id is not present" do - before { allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(nil) } + before do + allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(nil) + end it "does not close issue" do allow(jira_tracker).to receive_messages(jira_issue_transition_id: nil) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 091c193aaa6..fd46020bbdb 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -356,7 +356,9 @@ describe MergeRequests::UpdateService, services: true do end context 'when issue has the `label` label' do - before { merge_request.labels << label } + before do + merge_request.labels << label + end it 'does not send notifications for existing labels' do opts = { label_ids: [label.id, label2.id] } @@ -388,12 +390,16 @@ describe MergeRequests::UpdateService, services: true do end context 'when MergeRequest has tasks' do - before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) } + before do + update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) + end it { expect(@merge_request.tasks?).to eq(true) } context 'when tasks are marked as completed' do - before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) } + before do + update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) + end it 'creates system note about task status change' do note1 = find_note('marked the task **Task 1** as completed') diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index c9954dc3603..9a98499826f 100644 --- a/spec/services/notes/slash_commands_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -1,15 +1,17 @@ require 'spec_helper' -describe Notes::SlashCommandsService, services: true do +describe Notes::QuickActionsService, services: true do shared_context 'note on noteable' do let(:project) { create(:empty_project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:assignee) { create(:user) } - before { project.team << [assignee, :master] } + before do + project.team << [assignee, :master] + end end - shared_examples 'note on noteable that does not support slash commands' do + shared_examples 'note on noteable that does not support quick actions' do include_context 'note on noteable' before do @@ -43,7 +45,7 @@ describe Notes::SlashCommandsService, services: true do end end - shared_examples 'note on noteable that supports slash commands' do + shared_examples 'note on noteable that supports quick actions' do include_context 'note on noteable' before do @@ -208,15 +210,15 @@ describe Notes::SlashCommandsService, services: true do describe '#execute' do let(:service) { described_class.new(project, master) } - it_behaves_like 'note on noteable that supports slash commands' do + it_behaves_like 'note on noteable that supports quick actions' do let(:note) { build(:note_on_issue, project: project) } end - it_behaves_like 'note on noteable that supports slash commands' do + it_behaves_like 'note on noteable that supports quick actions' do let(:note) { build(:note_on_merge_request, project: project) } end - it_behaves_like 'note on noteable that does not support slash commands' do + it_behaves_like 'note on noteable that does not support quick actions' do let(:note) { build(:note_on_commit, project: project) } end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index de3bbc6b6a1..f1e00c1163b 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1539,8 +1539,7 @@ describe NotificationService, services: true do # When resource is nil it means global notification def update_custom_notification(event, user, resource: nil, value: true) setting = user.notification_settings_for(resource) - setting.events[event] = value - setting.save + setting.update!(event => value) end def add_users_with_subscription(project, issuable) diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index aa63fe3a5c1..cf38c7c75e5 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -10,10 +10,14 @@ describe PagesService, services: true do end context 'execute asynchronously for pages job' do - before { build.name = 'pages' } + before do + build.name = 'pages' + end context 'on success' do - before { build.success } + before do + build.success + end it 'executes worker' do expect(PagesWorker).to receive(:perform_async) @@ -23,7 +27,9 @@ describe PagesService, services: true do %w(pending running failed canceled).each do |status| context "on #{status}" do - before { build.status = status } + before do + build.status = status + end it 'does not execute worker' do expect(PagesWorker).not_to receive(:perform_async) diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index b2fb5c91313..4fd9cb23ae1 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -19,24 +19,24 @@ describe PreviewMarkdownService do end end - context 'new note with slash commands' do + context 'new note with quick actions' do let(:issue) { create(:issue, project: project) } let(:params) do { text: "Please do it\n/assign #{user.to_reference}", - slash_commands_target_type: 'Issue', - slash_commands_target_id: issue.id + quick_actions_target_type: 'Issue', + quick_actions_target_id: issue.id } end let(:service) { described_class.new(project, user, params) } - it 'removes slash commands from text' do + it 'removes quick actions from text' do result = service.execute expect(result[:text]).to eq 'Please do it' end - it 'explains slash commands effect' do + it 'explains quick actions effect' do result = service.execute expect(result[:commands]).to eq "Assigns #{user.to_reference}." @@ -47,21 +47,21 @@ describe PreviewMarkdownService do let(:params) do { text: "My work\n/estimate 2y", - slash_commands_target_type: 'MergeRequest' + quick_actions_target_type: 'MergeRequest' } end let(:service) { described_class.new(project, user, params) } - it 'removes slash commands from text' do + it 'removes quick actions from text' do result = service.execute expect(result[:text]).to eq 'My work' end - it 'explains slash commands effect' do + it 'explains quick actions effect' do result = service.execute expect(result[:commands]).to eq 'Sets time estimate to 2y.' - end + end end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index b957517c715..5d2f4cf17fb 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -59,12 +59,16 @@ describe Projects::TransferService, services: true do context 'visibility level' do let(:internal_group) { create(:group, :internal) } - before { internal_group.add_owner(user) } + before do + internal_group.add_owner(user) + end context 'when namespace visibility level < project visibility level' do let(:public_project) { create(:project, :public, :repository, namespace: user.namespace) } - before { transfer_project(public_project, user, internal_group) } + before do + transfer_project(public_project, user, internal_group) + end it { expect(public_project.visibility_level).to eq(internal_group.visibility_level) } end @@ -72,7 +76,9 @@ describe Projects::TransferService, services: true do context 'when namespace visibility level > project visibility level' do let(:private_project) { create(:project, :private, :repository, namespace: user.namespace) } - before { transfer_project(private_project, user, internal_group) } + before do + transfer_project(private_project, user, internal_group) + end it { expect(private_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) } end diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 4db491fd5f3..c9e63efbc14 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlashCommands::InterpretService, services: true do +describe QuickActions::InterpretService, services: true do let(:project) { create(:empty_project, :public) } let(:developer) { create(:user) } let(:developer2) { create(:user) } @@ -378,7 +378,9 @@ describe SlashCommands::InterpretService, services: true do context 'assign command with multiple assignees' do let(:content) { "/assign @#{developer.username} @#{developer2.username}" } - before{ project.team << [developer2, :developer] } + before do + project.team << [developer2, :developer] + end context 'Issue' do it 'fetches assignee and populates assignee_id if content contains /assign' do @@ -798,7 +800,11 @@ describe SlashCommands::InterpretService, services: true do context 'if the project has multiple boards' do let(:issuable) { issue } - before { create(:board, project: project) } + + before do + create(:board, project: project) + end + it_behaves_like 'empty command' end diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb index 74cba8c014b..5e6e43b7a90 100644 --- a/spec/services/spam_service_spec.rb +++ b/spec/services/spam_service_spec.rb @@ -70,7 +70,9 @@ describe SpamService, services: true do end context 'when not indicated as spam by akismet' do - before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) } + before do + allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) + end it 'returns false' do expect(check_spam(issue, request, false)).to be_falsey diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1979347a178..fdef6fd5221 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,6 +44,7 @@ RSpec.configure do |config| config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::ControllerHelpers, type: :view + config.include Devise::Test::IntegrationHelpers, type: :feature config.include Warden::Test::Helpers, type: :request config.include LoginHelpers, type: :feature config.include SearchHelpers, type: :feature @@ -56,6 +57,7 @@ RSpec.configure do |config| config.include StubGitlabCalls config.include StubGitlabData config.include ApiHelpers, :api + config.include Rails.application.routes.url_helpers, type: :routing config.include MigrationsHelpers, :migration config.infer_spec_type_from_file_location! @@ -106,15 +108,13 @@ RSpec.configure do |config| Sidekiq.redis(&:flushall) end - config.around(:example, :migration) do |example| - begin - ActiveRecord::Migrator - .migrate(migrations_paths, previous_migration.version) + config.before(:example, :migration) do + ActiveRecord::Migrator + .migrate(migrations_paths, previous_migration.version) + end - example.run - ensure - ActiveRecord::Migrator.migrate(migrations_paths) - end + config.after(:example, :migration) do + ActiveRecord::Migrator.migrate(migrations_paths) end config.around(:each, :nested_groups) do |example| diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index c34e76fa72f..4aa81a03558 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -35,4 +35,13 @@ RSpec.configure do |config| TestEnv.eager_load_driver_server $capybara_server_already_started = true end + + config.after(:each, :js) do + # capybara/rspec already calls Capybara.reset_sessions! in an `after` hook, + # but `block_and_wait_for_requests_complete` is called before it so by + # calling it explicitely here, we prevent any new requests from being fired + # See https://github.com/teamcapybara/capybara/blob/ffb41cfad620de1961bb49b1562a9fa9b28c0903/lib/capybara/rspec.rb#L20-L25 + Capybara.reset_sessions! + block_and_wait_for_requests_complete + end end diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb index 4dfa29849ee..978b0b9cc30 100644 --- a/spec/support/chat_slash_commands_shared_examples.rb +++ b/spec/support/chat_slash_commands_shared_examples.rb @@ -87,7 +87,7 @@ RSpec.shared_examples 'chat slash commands service' do end it 'triggers the command' do - expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute) + expect_any_instance_of(Gitlab::SlashCommands::Command).to receive(:execute) subject.trigger(params) end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index fa82dc5e9f9..50869099bb7 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -1,8 +1,8 @@ # Specifications for behavior common to all objects with executable attributes. # It takes a `issuable_type`, and expect an `issuable`. -shared_examples 'issuable record that supports slash commands in its description and notes' do |issuable_type| - include SlashCommandsHelpers +shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type| + include QuickActionsHelpers let(:master) { create(:user) } let(:assignee) { create(:user, username: 'bob') } @@ -17,7 +17,7 @@ shared_examples 'issuable record that supports slash commands in its description project.team << [master, :master] project.team << [assignee, :developer] project.team << [guest, :guest] - login_with(master) + gitlab_sign_in(master) end after do @@ -105,8 +105,8 @@ shared_examples 'issuable record that supports slash commands in its description context "when current user cannot close #{issuable_type}" do before do - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -140,8 +140,8 @@ shared_examples 'issuable record that supports slash commands in its description context "when current user cannot reopen #{issuable_type}" do before do - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -170,8 +170,8 @@ shared_examples 'issuable record that supports slash commands in its description context "when current user cannot change title of #{issuable_type}" do before do - logout - login_with(guest) + gitlab_sign_out + gitlab_sign_in(guest) visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -260,7 +260,7 @@ shared_examples 'issuable record that supports slash commands in its description end describe "preview of note on #{issuable_type}" do - it 'removes slash commands from note and explains them' do + it 'removes quick actions from note and explains them' do visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) page.within('.js-main-target-form') do diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb index 9280fad4ace..c92f78b324c 100644 --- a/spec/support/kubernetes_helpers.rb +++ b/spec/support/kubernetes_helpers.rb @@ -1,7 +1,26 @@ module KubernetesHelpers include Gitlab::Kubernetes - def kube_discovery_body + def kube_response(body) + { body: body.to_json } + end + + def kube_pods_response + kube_response(kube_pods_body) + end + + def stub_kubeclient_discover + WebMock.stub_request(:get, service.api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body)) + end + + def stub_kubeclient_pods(response = nil) + stub_kubeclient_discover + pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" + + WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) + end + + def kube_v1_discovery_body { "kind" => "APIResourceList", "resources" => [ @@ -10,17 +29,19 @@ module KubernetesHelpers } end - def kube_pods_body(*pods) - { "kind" => "PodList", - "items" => [kube_pod] } + def kube_pods_body + { + "kind" => "PodList", + "items" => [kube_pod] + } end # This is a partial response, it will have many more elements in reality but # these are the ones we care about at the moment - def kube_pod(app: "valid-pod-label") + def kube_pod(name: "kube-pod", app: "valid-pod-label") { "metadata" => { - "name" => "kube-pod", + "name" => name, "creationTimestamp" => "2016-11-25T19:55:19Z", "labels" => { "app" => app } }, diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index e6da852e728..879386b5437 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -6,15 +6,15 @@ module LoginHelpers # Examples: # # # Create a user automatically - # login_as(:user) + # gitlab_sign_in(:user) # # # Create an admin automatically - # login_as(:admin) + # gitlab_sign_in(:admin) # # # Provide an existing User record # user = create(:user) - # login_as(user) - def login_as(user_or_role) + # gitlab_sign_in(user) + def gitlab_sign_in(user_or_role, **kwargs) @user = if user_or_role.is_a?(User) user_or_role @@ -22,26 +22,44 @@ module LoginHelpers create(user_or_role) end - login_with(@user) + gitlab_sign_in_with(@user, **kwargs) end - # Internal: Login as the specified user + def gitlab_sign_in_via(provider, user, uid) + mock_auth_hash(provider, uid, user.email) + visit new_user_session_path + click_link provider + end + + # Requires Javascript driver. + def gitlab_sign_out + find(".header-user-dropdown-toggle").click + click_link "Sign out" + # check the sign_in button + expect(page).to have_button('Sign in') + end + + # Logout without JavaScript driver + def gitlab_sign_out_direct + page.driver.submit :delete, '/users/sign_out', {} + end + + private + + # Private: Login as the specified user # # user - User instance to login with # remember - Whether or not to check "Remember me" (default: false) - def login_with(user, remember: false) + def gitlab_sign_in_with(user, remember: false) visit new_user_session_path + fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember + click_button "Sign in" - Thread.current[:current_user] = user - end - def login_via(provider, user, uid) - mock_auth_hash(provider, uid, user.email) - visit new_user_session_path - click_link provider + Thread.current[:current_user] = user end def mock_auth_hash(provider, uid, email) @@ -71,17 +89,4 @@ module LoginHelpers }) Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml] end - - # Requires Javascript driver. - def logout - find(".header-user-dropdown-toggle").click - click_link "Sign out" - # check the sign_in button - expect(page).to have_button('Sign in') - end - - # Logout without JavaScript driver - def logout_direct - page.driver.submit :delete, '/users/sign_out', {} - end end diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb index ed14bcec9f2..ebfabcd8f24 100644 --- a/spec/support/matchers/gitaly_matchers.rb +++ b/spec/support/matchers/gitaly_matchers.rb @@ -1,5 +1,10 @@ -RSpec::Matchers.define :gitaly_request_with_repo_path do |path| - match { |actual| actual.repository.path == path } +RSpec::Matchers.define :gitaly_request_with_path do |storage_name, relative_path| + match do |actual| + repository = actual.repository + + repository.storage_name == storage_name && + repository.relative_path == relative_path + end end RSpec::Matchers.define :gitaly_request_with_params do |params| diff --git a/spec/support/milestone_tabs_examples.rb b/spec/support/milestone_tabs_examples.rb index 7cfc1e06975..70b499198bf 100644 --- a/spec/support/milestone_tabs_examples.rb +++ b/spec/support/milestone_tabs_examples.rb @@ -15,7 +15,9 @@ shared_examples 'milestone tabs' do describe '#merge_requests' do context 'as html' do - before { go(:merge_requests, format: 'html') } + before do + go(:merge_requests, format: 'html') + end it 'redirects to milestone#show' do expect(response).to redirect_to(milestone_path) @@ -23,7 +25,9 @@ shared_examples 'milestone tabs' do end context 'as json' do - before { go(:merge_requests, format: 'json') } + before do + go(:merge_requests, format: 'json') + end it 'renders the merge requests tab template to a string' do expect(response).to render_template('shared/milestones/_merge_requests_tab') @@ -34,7 +38,9 @@ shared_examples 'milestone tabs' do describe '#participants' do context 'as html' do - before { go(:participants, format: 'html') } + before do + go(:participants, format: 'html') + end it 'redirects to milestone#show' do expect(response).to redirect_to(milestone_path) @@ -42,7 +48,9 @@ shared_examples 'milestone tabs' do end context 'as json' do - before { go(:participants, format: 'json') } + before do + go(:participants, format: 'json') + end it 'renders the participants tab template to a string' do expect(response).to render_template('shared/milestones/_participants_tab') @@ -53,7 +61,9 @@ shared_examples 'milestone tabs' do describe '#labels' do context 'as html' do - before { go(:labels, format: 'html') } + before do + go(:labels, format: 'html') + end it 'redirects to milestone#show' do expect(response).to redirect_to(milestone_path) @@ -61,7 +71,9 @@ shared_examples 'milestone tabs' do end context 'as json' do - before { go(:labels, format: 'json') } + before do + go(:labels, format: 'json') + end it 'renders the labels tab template to a string' do expect(response).to render_template('shared/milestones/_labels_tab') diff --git a/spec/support/project_features_apply_to_issuables_shared_examples.rb b/spec/support/project_features_apply_to_issuables_shared_examples.rb index f8b7d0527ba..81b51509e0b 100644 --- a/spec/support/project_features_apply_to_issuables_shared_examples.rb +++ b/spec/support/project_features_apply_to_issuables_shared_examples.rb @@ -18,7 +18,7 @@ shared_examples 'project features apply to issuables' do |klass| before do _ = issuable - login_as(user) if user + gitlab_sign_in(user) if user visit path end diff --git a/spec/support/slash_commands_helpers.rb b/spec/support/quick_actions_helpers.rb index 4bfe481115f..d2aaae7518f 100644 --- a/spec/support/slash_commands_helpers.rb +++ b/spec/support/quick_actions_helpers.rb @@ -1,4 +1,4 @@ -module SlashCommandsHelpers +module QuickActionsHelpers def write_note(text) Sidekiq::Testing.fake! do page.within('.js-main-target-form') do diff --git a/spec/support/reference_parser_shared_examples.rb b/spec/support/reference_parser_shared_examples.rb index 8eb74635a60..bd83cb88058 100644 --- a/spec/support/reference_parser_shared_examples.rb +++ b/spec/support/reference_parser_shared_examples.rb @@ -3,7 +3,9 @@ RSpec.shared_examples "referenced feature visibility" do |*related_features| related_features.map { |feature| (feature + "_access_level").to_sym } end - before { link['data-project'] = project.id.to_s } + before do + link['data-project'] = project.id.to_s + end context "when feature is disabled" do it "does not create reference" do @@ -13,7 +15,9 @@ RSpec.shared_examples "referenced feature visibility" do |*related_features| end context "when feature is enabled only for team members" do - before { set_features_fields_to(ProjectFeature::PRIVATE) } + before do + set_features_fields_to(ProjectFeature::PRIVATE) + end it "does not create reference for non member" do non_member = create(:user) diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb index 1dd3663b944..9399745f900 100644 --- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb +++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb @@ -1,7 +1,7 @@ # Specifications for behavior common to all objects with executable attributes. # It can take a `default_params`. -shared_examples 'new issuable record that supports slash commands' do +shared_examples 'new issuable record that supports quick actions' do let!(:project) { create(:project, :repository) } let(:user) { create(:user).tap { |u| project.team << [u, :master] } } let(:assignee) { create(:user) } @@ -11,7 +11,9 @@ shared_examples 'new issuable record that supports slash commands' do let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) } let(:issuable) { described_class.new(project, user, params).execute } - before { project.team << [assignee, :master] } + before do + project.team << [assignee, :master] + end context 'with labels in command only' do let(:example_params) do diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb index 8947f20562f..ffbce6c42bf 100644 --- a/spec/support/services/issuable_update_service_shared_examples.rb +++ b/spec/support/services/issuable_update_service_shared_examples.rb @@ -4,7 +4,9 @@ shared_examples 'issuable update service' do end context 'changing state' do - before { expect(project).to receive(:execute_hooks).once } + before do + expect(project).to receive(:execute_hooks).once + end context 'to reopened' do it 'executes hooks only once' do diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 7e35ebb6c97..a7deb038703 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -11,14 +11,18 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe 'Validations' do context 'when service is active' do - before { subject.active = true } + before do + subject.active = true + end it { is_expected.to validate_presence_of(:webhook) } it_behaves_like 'issue tracker service URL attribute', :webhook end context 'when service is inactive' do - before { subject.active = false } + before do + subject.active = false + end it { is_expected.not_to validate_presence_of(:webhook) } end diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb index b407b8097d2..0fa74f911f6 100644 --- a/spec/support/time_tracking_shared_examples.rb +++ b/spec/support/time_tracking_shared_examples.rb @@ -54,7 +54,7 @@ shared_examples 'issuable time tracker' do it 'shows the help state when icon is clicked' do page.within '.time-tracking-component-wrap' do find('.help-button').click - expect(page).to have_content 'Track time with slash commands' + expect(page).to have_content 'Track time with quick actions' expect(page).to have_content 'Learn more' end end @@ -64,7 +64,7 @@ shared_examples 'issuable time tracker' do find('.help-button').click find('.close-help-button').click - expect(page).not_to have_content 'Track time with slash commands' + expect(page).not_to have_content 'Track time with quick actions' expect(page).not_to have_content 'Learn more' end end @@ -78,8 +78,8 @@ shared_examples 'issuable time tracker' do end end -def submit_time(slash_command) - fill_in 'note[note]', with: slash_command +def submit_time(quick_action) + fill_in 'note[note]', with: quick_action find('.js-comment-submit-button').trigger('click') wait_for_requests end diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb index 7cf5a65eeed..1986d202c4a 100644 --- a/spec/support/unique_ip_check_shared_examples.rb +++ b/spec/support/unique_ip_check_shared_examples.rb @@ -31,7 +31,9 @@ end shared_examples 'user login operation with unique ip limit' do include_context 'unique ips sign in limit' do - before { current_application_settings.update!(unique_ips_limit_per_user: 1) } + before do + current_application_settings.update!(unique_ips_limit_per_user: 1) + end it 'allows user authenticating from the same ip' do expect { operation_from_ip('ip') }.not_to raise_error @@ -47,7 +49,9 @@ end shared_examples 'user login request with unique ip limit' do |success_status = 200| include_context 'unique ips sign in limit' do - before { current_application_settings.update!(unique_ips_limit_per_user: 1) } + before do + current_application_settings.update!(unique_ips_limit_per_user: 1) + end it 'allows user authenticating from the same ip' do expect(request_from_ip('ip')).to have_http_status(success_status) diff --git a/spec/support/updating_mentions_shared_examples.rb b/spec/support/updating_mentions_shared_examples.rb index e0c59a5c280..eeec3e1d79b 100644 --- a/spec/support/updating_mentions_shared_examples.rb +++ b/spec/support/updating_mentions_shared_examples.rb @@ -2,7 +2,9 @@ RSpec.shared_examples 'updating mentions' do |service_class| let(:mentioned_user) { create(:user) } let(:service_class) { service_class } - before { project.team << [mentioned_user, :developer] } + before do + project.team << [mentioned_user, :developer] + end def update_mentionable(opts) reset_delivered_emails! @@ -15,7 +17,9 @@ RSpec.shared_examples 'updating mentions' do |service_class| end context 'in title' do - before { update_mentionable(title: mentioned_user.to_reference) } + before do + update_mentionable(title: mentioned_user.to_reference) + end it 'emails only the newly-mentioned user' do should_only_email(mentioned_user) @@ -23,7 +27,9 @@ RSpec.shared_examples 'updating mentions' do |service_class| end context 'in description' do - before { update_mentionable(description: mentioned_user.to_reference) } + before do + update_mentionable(description: mentioned_user.to_reference) + end it 'emails only the newly-mentioned user' do should_only_email(mentioned_user) diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb index 05ec9026141..b5c3c0f55b8 100644 --- a/spec/support/wait_for_requests.rb +++ b/spec/support/wait_for_requests.rb @@ -7,7 +7,7 @@ module WaitForRequests def block_and_wait_for_requests_complete Gitlab::Testing::RequestBlockerMiddleware.block_requests! wait_for('pending requests complete') do - Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? + Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? && finished_all_requests? end ensure Gitlab::Testing::RequestBlockerMiddleware.allow_requests! @@ -40,22 +40,16 @@ module WaitForRequests end def finished_all_vue_resource_requests? - page.evaluate_script('window.activeVueResources || 0').zero? + Capybara.page.evaluate_script('window.activeVueResources || 0').zero? end def finished_all_ajax_requests? - return true if page.evaluate_script('typeof jQuery === "undefined"') + return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"') - page.evaluate_script('jQuery.active').zero? + Capybara.page.evaluate_script('jQuery.active').zero? end def javascript_test? Capybara.current_driver == Capybara.javascript_driver end end - -RSpec.configure do |config| - config.after(:each, :js) do - block_and_wait_for_requests_complete - end -end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 0ff1a988a9e..1e5f55a738a 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -241,6 +241,10 @@ describe 'gitlab:app namespace rake task' do project_a project_b + # Avoid asking gitaly about the root ref (which will fail beacuse of the + # mocked storages) + allow_any_instance_of(Repository).to receive(:empty_repo?).and_return(false) + # We only need a backup of the repositories for this test ENV["SKIP"] = "db,uploads,builds,artifacts,lfs,registry" create_backup diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index 4a636decafd..cfa6c9ca8ce 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -79,8 +79,14 @@ describe 'gitlab:gitaly namespace rake task' do describe 'storage_config' do it 'prints storage configuration in a TOML format' do config = { - 'default' => { 'path' => '/path/to/default' }, - 'nfs_01' => { 'path' => '/path/to/nfs_01' } + 'default' => { + 'path' => '/path/to/default', + 'gitaly_address' => 'unix:/path/to/my.socket' + }, + 'nfs_01' => { + 'path' => '/path/to/nfs_01', + 'gitaly_address' => 'unix:/path/to/my.socket' + } } allow(Gitlab.config.repositories).to receive(:storages).and_return(config) @@ -89,6 +95,7 @@ describe 'gitlab:gitaly namespace rake task' do expected_output = <<~TOML # Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)} # This is in TOML format suitable for use in Gitaly's config.toml file. + socket_path = "/path/to/my.socket" [[storage]] name = "default" path = "/path/to/default" diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb new file mode 100644 index 00000000000..e89a8cb9626 --- /dev/null +++ b/spec/views/profiles/show.html.haml_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'profiles/show' do + let(:user) { create(:user) } + + before do + assign(:user, user) + allow(controller).to receive(:current_user).and_return(user) + end + + context 'when the profile page is opened' do + it 'displays the correct elements' do + render + + expect(rendered).to have_field('user_name', user.name) + expect(rendered).to have_field('user_id', user.id) + end + end +end diff --git a/spec/views/projects/diffs/_viewer.html.haml_spec.rb b/spec/views/projects/diffs/_viewer.html.haml_spec.rb new file mode 100644 index 00000000000..32469202508 --- /dev/null +++ b/spec/views/projects/diffs/_viewer.html.haml_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe 'projects/diffs/_viewer.html.haml', :view do + include FakeBlobHelpers + + let(:project) { create(:project, :repository) } + let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } + + let(:viewer_class) do + Class.new(DiffViewer::Base) do + include DiffViewer::Rich + + self.partial_name = 'text' + end + end + + let(:viewer) { viewer_class.new(diff_file) } + + before do + assign(:project, project) + + controller.params[:controller] = 'projects/commit' + controller.params[:action] = 'show' + controller.params[:namespace_id] = project.namespace.to_param + controller.params[:project_id] = project.to_param + controller.params[:id] = commit.id + end + + def render_view + render partial: 'projects/diffs/viewer', locals: { viewer: viewer } + end + + context 'when there is a render error' do + before do + allow(viewer).to receive(:render_error).and_return(:too_large) + end + + it 'renders the error' do + render_view + + expect(view).to render_template('projects/diffs/_render_error') + end + end + + context 'when the viewer is collapsed' do + before do + allow(diff_file).to receive(:collapsed?).and_return(true) + end + + it 'renders the collapsed view' do + render_view + + expect(view).to render_template('projects/diffs/_collapsed') + end + end + + context 'when there is no render error' do + it 'prepares the viewer' do + expect(viewer).to receive(:prepare!) + + render_view + end + + it 'renders the viewer' do + render_view + + expect(view).to render_template('projects/diffs/viewers/_text') + end + end +end diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb index d7d0a5bf56a..cae6bee2776 100644 --- a/spec/views/shared/notes/_form.html.haml_spec.rb +++ b/spec/views/shared/notes/_form.html.haml_spec.rb @@ -20,8 +20,8 @@ describe 'shared/notes/_form' do context "with a note on #{noteable}" do let(:note) { build(:"note_on_#{noteable}", project: project) } - it 'says that markdown and slash commands are supported' do - expect(rendered).to have_content('Markdown and slash commands are supported') + it 'says that markdown and quick actions are supported' do + expect(rendered).to have_content('Markdown and quick actions are supported') end end end @@ -29,7 +29,7 @@ describe 'shared/notes/_form' do context 'with a note on a commit' do let(:note) { build(:note_on_commit, project: project) } - it 'says that only markdown is supported, not slash commands' do + it 'says that only markdown is supported, not quick actions' do expect(rendered).to have_content('Markdown is supported') end end diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index a0ed85cc0b3..5b6b38e0f76 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -71,7 +71,9 @@ describe EmailsOnPushWorker do end context "when there are no errors in sending" do - before { perform } + before do + perform + end it "sends a mail with the correct subject" do expect(email.subject).to include('adds bar folder and branch-test text file') diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb index 73cbadc13d9..b47b4a02a68 100644 --- a/spec/workers/expire_build_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_artifacts_worker_spec.rb @@ -5,10 +5,14 @@ describe ExpireBuildArtifactsWorker do let(:worker) { described_class.new } - before { Sidekiq::Worker.clear_all } + before do + Sidekiq::Worker.clear_all + end describe '#perform' do - before { build } + before do + build + end subject! do Sidekiq::Testing.fake! { worker.perform } diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 8c5303b61cc..f443bb2c9b4 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -23,7 +23,9 @@ describe GitGarbageCollectWorker do end shared_examples 'gc tasks' do - before { allow(subject).to receive(:bitmaps_enabled?).and_return(bitmaps_enabled) } + before do + allow(subject).to receive(:bitmaps_enabled?).and_return(bitmaps_enabled) + end it 'incremental repack adds a new packfile' do create_objects(project) diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 44163c735ba..6c4df7350ea 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -32,7 +32,7 @@ describe PostReceive do context "with an absolute path as the project identifier" do it "searches the project by full path" do - expect(Project).to receive(:find_by_full_path).with(project.full_path).and_call_original + expect(Project).to receive(:find_by_full_path).with(project.full_path, follow_redirects: true).and_call_original described_class.new.perform(pwd(project), key_id, base64_changes) end @@ -89,7 +89,9 @@ describe PostReceive do end context "does not create a Ci::Pipeline" do - before { stub_ci_pipeline_yaml_file(nil) } + before do + stub_ci_pipeline_yaml_file(nil) + end it { expect{ subject }.not_to change{ Ci::Pipeline.count } } end diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb index 8434b0c8e5b..549635f7f33 100644 --- a/spec/workers/stuck_ci_jobs_worker_spec.rb +++ b/spec/workers/stuck_ci_jobs_worker_spec.rb @@ -34,7 +34,9 @@ describe StuckCiJobsWorker do let(:status) { 'pending' } context 'when job is not stuck' do - before { allow_any_instance_of(Ci::Build).to receive(:stuck?).and_return(false) } + before do + allow_any_instance_of(Ci::Build).to receive(:stuck?).and_return(false) + end context 'when job was not updated for more than 1 day ago' do let(:updated_at) { 2.days.ago } @@ -53,7 +55,9 @@ describe StuckCiJobsWorker do end context 'when job is stuck' do - before { allow_any_instance_of(Ci::Build).to receive(:stuck?).and_return(true) } + before do + allow_any_instance_of(Ci::Build).to receive(:stuck?).and_return(true) + end context 'when job was not updated for more than 1 hour ago' do let(:updated_at) { 2.hours.ago } @@ -93,7 +97,9 @@ describe StuckCiJobsWorker do let(:status) { 'running' } let(:updated_at) { 2.days.ago } - before { job.project.update(pending_delete: true) } + before do + job.project.update(pending_delete: true) + end it 'does not drop job' do expect_any_instance_of(Ci::Build).not_to receive(:drop) diff --git a/yarn.lock b/yarn.lock index 1db64aead8d..b902d5235d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -882,20 +882,20 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" -body-parser@^1.12.4: - version "1.16.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.0.tgz#924a5e472c6229fb9d69b85a20d5f2532dec788b" +body-parser@^1.16.1: + version "1.17.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" dependencies: bytes "2.4.0" content-type "~1.0.2" - debug "2.6.0" + debug "2.6.7" depd "~1.1.0" - http-errors "~1.5.1" + http-errors "~1.6.1" iconv-lite "0.4.15" on-finished "~2.3.0" - qs "6.2.1" + qs "6.4.0" raw-body "~2.2.0" - type-is "~1.6.14" + type-is "~1.6.15" boom@2.x.x: version "2.10.1" @@ -1265,14 +1265,6 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.0.tgz#53f7d43c51c5e43f81c8fdd03321c631be68d611" - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" - concat-stream@^1.4.6: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" @@ -1305,12 +1297,12 @@ connect-history-api-fallback@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169" -connect@^3.3.5: - version "3.5.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.5.0.tgz#b357525a0b4c1f50599cd983e1d9efeea9677198" +connect@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.2.tgz#694e8d20681bfe490282c8ab886be98f09f42fe7" dependencies: - debug "~2.2.0" - finalhandler "0.5.0" + debug "2.6.7" + finalhandler "1.0.3" parseurl "~1.3.1" utils-merge "1.0.0" @@ -1538,10 +1530,6 @@ de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" -debug@0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" - debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -1554,18 +1542,18 @@ debug@2.3.3: dependencies: ms "0.7.2" -debug@2.6.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" - dependencies: - ms "0.7.2" - debug@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" dependencies: ms "2.0.0" +debug@^2.1.0, debug@^2.1.1, debug@^2.2.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" + dependencies: + ms "0.7.2" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1778,9 +1766,9 @@ end-of-stream@1.0.0: dependencies: once "~1.3.0" -engine.io-client@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.2.tgz#c38767547f2a7d184f5752f6f0ad501006703766" +engine.io-client@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab" dependencies: component-emitter "1.2.1" component-inherit "0.0.3" @@ -1791,7 +1779,7 @@ engine.io-client@1.8.2: parsejson "0.0.3" parseqs "0.0.5" parseuri "0.0.5" - ws "1.1.1" + ws "1.1.2" xmlhttprequest-ssl "1.5.3" yeast "0.1.2" @@ -1806,16 +1794,16 @@ engine.io-parser@1.3.2: has-binary "0.1.7" wtf-8 "1.0.0" -engine.io@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.2.tgz#6b59be730b348c0125b0a4589de1c355abcf7a7e" +engine.io@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4" dependencies: accepts "1.3.3" base64id "1.0.0" cookie "0.3.1" debug "2.3.3" engine.io-parser "1.3.2" - ws "1.1.1" + ws "1.1.2" enhanced-resolve@^3.0.0: version "3.1.0" @@ -1884,10 +1872,6 @@ es6-promise@^3.0.2, es6-promise@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" -es6-promise@~4.0.3: - version "4.0.5" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.0.5.tgz#7882f30adde5b240ccfa7f7d78c548330951ae42" - es6-set@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" @@ -2219,15 +2203,6 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extract-zip@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.5.0.tgz#92ccf6d81ef70a9fa4c1747114ccef6d8688a6c4" - dependencies: - concat-stream "1.5.0" - debug "0.7.4" - mkdirp "0.5.0" - yauzl "2.4.1" - extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" @@ -2258,12 +2233,6 @@ faye-websocket@~0.7.3: dependencies: websocket-driver ">=0.3.6" -fd-slicer@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" - dependencies: - pend "~1.2.0" - figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -2313,17 +2282,7 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" -finalhandler@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" - dependencies: - debug "~2.2.0" - escape-html "~1.0.3" - on-finished "~2.3.0" - statuses "~1.3.0" - unpipe "~1.0.0" - -finalhandler@~1.0.3: +finalhandler@1.0.3, finalhandler@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" dependencies: @@ -2407,13 +2366,11 @@ from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" -fs-extra@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" +fs-access@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" + null-check "^1.0.0" fs.realpath@^1.0.0: version "1.0.0" @@ -2551,7 +2508,7 @@ got@^3.2.0: read-all-stream "^3.0.0" timed-out "^2.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2628,13 +2585,6 @@ hash.js@^1.0.0: dependencies: inherits "^2.0.1" -hasha@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1" - dependencies: - is-stream "^1.0.1" - pinkie-promise "^2.0.0" - hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -2695,7 +2645,7 @@ http-deceiver@^1.2.4: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@~1.5.0, http-errors@~1.5.1: +http-errors@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" dependencies: @@ -2987,7 +2937,7 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" -is-stream@^1.0.0, is-stream@^1.0.1: +is-stream@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3124,9 +3074,9 @@ istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" -jasmine-core@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297" +jasmine-core@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815" jasmine-jquery@^2.1.1: version "2.1.1" @@ -3225,12 +3175,6 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - optionalDependencies: - graceful-fs "^4.1.6" - jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -3261,6 +3205,13 @@ jszip@^3.1.3: pako "~1.0.2" readable-stream "~2.0.6" +karma-chrome-launcher@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" + dependencies: + fs-access "^1.0.0" + which "^1.2.1" + karma-coverage-istanbul-reporter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-0.2.0.tgz#5766263338adeb0026f7e4ac7a89a5f056c5642c" @@ -3277,13 +3228,6 @@ karma-mocha-reporter@^2.2.2: dependencies: chalk "1.1.3" -karma-phantomjs-launcher@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.2.tgz#19e1041498fd75563ed86730a22c1fe579fa8fb1" - dependencies: - lodash "^4.0.1" - phantomjs-prebuilt "^2.1.7" - karma-sourcemap-loader@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz#91322c77f8f13d46fed062b042e1009d4c4505d8" @@ -3300,16 +3244,16 @@ karma-webpack@^2.0.2: source-map "^0.1.41" webpack-dev-middleware "^1.0.11" -karma@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/karma/-/karma-1.4.1.tgz#41981a71d54237606b0a3ea8c58c90773f41650e" +karma@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.0.tgz#6f7a1a406446fa2e187ec95398698f4cee476269" dependencies: bluebird "^3.3.0" - body-parser "^1.12.4" + body-parser "^1.16.1" chokidar "^1.4.1" colors "^1.1.0" combine-lists "^1.0.0" - connect "^3.3.5" + connect "^3.6.0" core-js "^2.2.0" di "^0.0.1" dom-serialize "^2.2.0" @@ -3321,20 +3265,16 @@ karma@^1.4.1: lodash "^3.8.0" log4js "^0.6.31" mime "^1.3.4" - minimatch "^3.0.0" + minimatch "^3.0.2" optimist "^0.6.1" qjobs "^1.1.4" range-parser "^1.2.0" - rimraf "^2.3.3" + rimraf "^2.6.0" safe-buffer "^5.0.1" - socket.io "1.7.2" + socket.io "1.7.3" source-map "^0.5.3" - tmp "0.0.28" - useragent "^2.1.10" - -kew@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b" + tmp "0.0.31" + useragent "^2.1.12" kind-of@^3.0.2: version "3.1.0" @@ -3342,12 +3282,6 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.0.2" -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - optionalDependencies: - graceful-fs "^4.1.9" - latest-version@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb" @@ -3556,7 +3490,7 @@ lodash@^3.8.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.0.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3704,12 +3638,6 @@ minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -mkdirp@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" - dependencies: - minimist "0.0.8" - mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -3907,6 +3835,10 @@ npmlog@^4.0.1: gauge "~2.7.1" set-blocking "~2.0.0" +null-check@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" + num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -4165,24 +4097,6 @@ pdfjs-dist@^1.8.252: node-ensure "^0.0.0" worker-loader "^0.8.0" -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - -phantomjs-prebuilt@^2.1.7: - version "2.1.14" - resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.14.tgz#d53d311fcfb7d1d08ddb24014558f1188c516da0" - dependencies: - es6-promise "~4.0.3" - extract-zip "~1.5.0" - fs-extra "~1.0.0" - hasha "~2.2.0" - kew "~0.7.0" - progress "~1.1.8" - request "~2.79.0" - request-progress "~2.0.1" - which "~1.2.10" - pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -4518,7 +4432,7 @@ process@^0.11.0, process@~0.11.0: version "0.11.9" resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1" -progress@^1.1.8, progress@~1.1.8: +progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -4573,10 +4487,6 @@ qjobs@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73" -qs@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" - qs@6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" @@ -4701,7 +4611,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.0, readable-stream@~2.0.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" dependencies: @@ -4855,13 +4765,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request-progress@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08" - dependencies: - throttleit "^1.0.0" - -request@^2.79.0, request@~2.79.0: +request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -4934,7 +4838,13 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@~2.5.1, rimraf@~2.5.4: +rimraf@2, rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +rimraf@~2.5.1, rimraf@~2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: @@ -5094,15 +5004,15 @@ socket.io-adapter@0.5.0: debug "2.3.3" socket.io-parser "2.3.1" -socket.io-client@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.2.tgz#39fdb0c3dd450e321b7e40cfd83612ec533dd644" +socket.io-client@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377" dependencies: backo2 "1.0.2" component-bind "1.0.0" component-emitter "1.2.1" debug "2.3.3" - engine.io-client "1.8.2" + engine.io-client "1.8.3" has-binary "0.1.7" indexof "0.0.1" object-component "0.0.3" @@ -5119,16 +5029,16 @@ socket.io-parser@2.3.1: isarray "0.0.1" json3 "3.3.2" -socket.io@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.2.tgz#83bbbdf2e79263b378900da403e7843e05dc3b71" +socket.io@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b" dependencies: debug "2.3.3" - engine.io "1.8.2" + engine.io "1.8.3" has-binary "0.1.7" object-assign "4.1.0" socket.io-adapter "0.5.0" - socket.io-client "1.7.2" + socket.io-client "1.7.3" socket.io-parser "2.3.1" sockjs-client@1.0.1: @@ -5269,7 +5179,7 @@ stats-webpack-plugin@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/stats-webpack-plugin/-/stats-webpack-plugin-0.4.3.tgz#b2f618202f28dd04ab47d7ecf54ab846137b7aea" -"statuses@>= 1.3.1 < 2", statuses@~1.3.0, statuses@~1.3.1: +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -5449,10 +5359,6 @@ three@^0.84.0: version "0.84.0" resolved "https://registry.yarnpkg.com/three/-/three-0.84.0.tgz#95be85a55a0fa002aa625ed559130957dcffd918" -throttleit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" - through@2, through@^2.3.6, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -5481,9 +5387,9 @@ tiny-emitter@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-1.1.0.tgz#ab405a21ffed814a76c19739648093d70654fecb" -tmp@0.0.28, tmp@0.0.x: - version "0.0.28" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" +tmp@0.0.31, tmp@0.0.x: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" dependencies: os-tmpdir "~1.0.1" @@ -5541,14 +5447,14 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.14, type-is@~1.6.15: +type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" dependencies: media-typer "0.3.0" mime-types "~2.1.15" -typedarray@^0.0.6, typedarray@~0.0.5: +typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -5653,9 +5559,9 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" -useragent@^2.1.10: - version "2.1.12" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.12.tgz#aa7da6cdc48bdc37ba86790871a7321d64edbaa2" +useragent@^2.1.12: + version "2.1.13" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.13.tgz#bba43e8aa24d5ceb83c2937473e102e21df74c10" dependencies: lru-cache "2.2.x" tmp "0.0.x" @@ -5883,7 +5789,7 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@^1.1.1, which@~1.2.10: +which@^1.1.1, which@^1.2.1: version "1.2.12" resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" dependencies: @@ -5942,9 +5848,9 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" -ws@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018" +ws@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" dependencies: options ">=0.0.5" ultron "1.0.x" @@ -6015,12 +5921,6 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yauzl@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" - dependencies: - fd-slicer "~1.0.1" - yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" |