summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/dispatcher.js12
-rw-r--r--app/assets/javascripts/droplab/plugins/ajax.js13
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js3
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js60
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js1
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js6
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js55
-rw-r--r--app/assets/javascripts/gl_dropdown.js6
-rw-r--r--app/assets/javascripts/labels_select.js14
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js4
-rw-r--r--app/assets/javascripts/profile/gl_crop.js2
-rw-r--r--app/assets/javascripts/projects/project_new.js64
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss25
-rw-r--r--app/controllers/projects/boards/issues_controller.rb3
-rw-r--r--app/helpers/search_helper.rb16
-rw-r--r--app/models/notification_setting.rb34
-rw-r--r--app/services/web_hook_service.rb8
-rw-r--r--app/views/groups/issues.html.haml6
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml3
-rw-r--r--changelogs/unreleased/33620-remove-events-from-notification_settings.yml4
-rw-r--r--changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml4
-rw-r--r--changelogs/unreleased/35815-webhook-log-encoding-error.yml4
-rw-r--r--changelogs/unreleased/add-filtered-search-group-issues-ce.yml4
-rw-r--r--changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml4
-rw-r--r--changelogs/unreleased/search-flickering.yml4
-rw-r--r--changelogs/unreleased/winh-derive-project-name.yml4
-rw-r--r--db/post_migrate/20170728101014_remove_events_from_notification_settings.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--doc/articles/index.md13
-rw-r--r--doc/ci/README.md1
-rw-r--r--lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb15
-rw-r--r--lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb11
-rw-r--r--lib/gitlab/prometheus/queries/query_additional_metrics.rb23
-rw-r--r--package.json1
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb2
-rw-r--r--spec/controllers/admin/dashboard_controller_spec.rb4
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb16
-rw-r--r--spec/controllers/dashboard_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/conflicts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb12
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb4
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb2
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb14
-rw-r--r--spec/controllers/snippets_controller_spec.rb2
-rw-r--r--spec/factories/notification_settings.rb1
-rw-r--r--spec/factories/uploads.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb4
-rw-r--r--spec/features/discussion_comments/commit_spec.rb2
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb2
-rw-r--r--spec/features/groups/issues_spec.rb10
-rw-r--r--spec/features/issuables/markdown_references_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/reportable_note/commit_spec.rb2
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb2
-rw-r--r--spec/finders/members_finder_spec.rb2
-rw-r--r--spec/helpers/button_helper_spec.rb2
-rw-r--r--spec/helpers/ci_status_helper_spec.rb2
-rw-r--r--spec/helpers/diff_helper_spec.rb2
-rw-r--r--spec/helpers/issuables_helper_spec.rb4
-rw-r--r--spec/helpers/markup_helper_spec.rb2
-rw-r--r--spec/helpers/notes_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb6
-rw-r--r--spec/helpers/search_helper_spec.rb34
-rw-r--r--spec/javascripts/droplab/plugins/ajax_spec.js36
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js96
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js63
-rw-r--r--spec/javascripts/fixtures/balsamiq.rb2
-rw-r--r--spec/javascripts/fixtures/dashboard.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb2
-rw-r--r--spec/javascripts/fixtures/pdf.rb2
-rw-r--r--spec/javascripts/fixtures/projects.rb2
-rw-r--r--spec/javascripts/fixtures/raw.rb2
-rw-r--r--spec/javascripts/lib/utils/ajax_cache_spec.js9
-rw-r--r--spec/javascripts/projects/project_new_spec.js127
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb2
-rw-r--r--spec/lib/container_registry/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb6
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb12
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/gl_repository_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/issuable_sorter_spec.rb42
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb4
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb11
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb10
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb2
-rw-r--r--spec/lib/gitlab/user_access_spec.rb2
-rw-r--r--spec/migrations/rename_system_namespaces_spec.rb16
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb2
-rw-r--r--spec/models/concerns/participable_spec.rb6
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb4
-rw-r--r--spec/models/deployment_spec.rb2
-rw-r--r--spec/models/diff_discussion_spec.rb2
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/forked_project_link_spec.rb2
-rw-r--r--spec/models/gpg_signature_spec.rb2
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/notification_setting_spec.rb12
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb16
-rw-r--r--spec/models/project_team_spec.rb2
-rw-r--r--spec/models/repository_spec.rb2
-rw-r--r--spec/models/sent_notification_spec.rb4
-rw-r--r--spec/policies/ci/build_policy_spec.rb2
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb2
-rw-r--r--spec/policies/environment_policy_spec.rb8
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb2
-rw-r--r--spec/requests/api/files_spec.rb4
-rw-r--r--spec/requests/api/notification_settings_spec.rb4
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb2
-rw-r--r--spec/requests/api/todos_spec.rb2
-rw-r--r--spec/requests/api/triggers_spec.rb2
-rw-r--r--spec/requests/api/v3/files_spec.rb2
-rw-r--r--spec/requests/api/v3/project_hooks_spec.rb4
-rw-r--r--spec/requests/api/v3/triggers_spec.rb2
-rw-r--r--spec/requests/lfs_http_spec.rb6
-rw-r--r--spec/requests/request_profiler_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/play_build_service_spec.rb2
-rw-r--r--spec/services/labels/create_service_spec.rb4
-rw-r--r--spec/services/labels/update_service_spec.rb2
-rw-r--r--spec/services/milestones/destroy_service_spec.rb2
-rw-r--r--spec/services/users/destroy_service_spec.rb4
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb2
-rw-r--r--spec/services/web_hook_service_spec.rb17
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb9
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb11
-rw-r--r--spec/support/prometheus/additional_metrics_shared_examples.rb51
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb9
-rw-r--r--spec/uploaders/file_uploader_spec.rb2
-rw-r--r--spec/validators/dynamic_path_validator_spec.rb2
-rw-r--r--spec/views/layouts/nav/_project.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_failed_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.html.haml_spec.rb2
-rw-r--r--spec/workers/create_gpg_signature_worker_spec.rb4
-rw-r--r--spec/workers/process_commit_worker_spec.rb2
-rw-r--r--vendor/assets/javascripts/cropper.js2993
-rw-r--r--yarn.lock8
167 files changed, 1005 insertions, 3349 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index a2664c0301e..178e72a1127 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -139,6 +139,8 @@ import GpgBadges from './gpg_badges';
.init();
}
+ const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
+
switch (page) {
case 'profiles:preferences:show':
initExperimentalFlags();
@@ -155,7 +157,7 @@ import GpgBadges from './gpg_badges';
break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
- if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
+ if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
filteredSearchManager.setup();
}
@@ -183,11 +185,17 @@ import GpgBadges from './gpg_badges';
break;
case 'dashboard:issues':
case 'dashboard:merge_requests':
- case 'groups:issues':
case 'groups:merge_requests':
new ProjectSelect();
initLegacyFilters();
break;
+ case 'groups:issues':
+ if (filteredSearchEnabled) {
+ const filteredSearchManager = new gl.FilteredSearchManager('issues');
+ filteredSearchManager.setup();
+ }
+ new ProjectSelect();
+ break;
case 'dashboard:todos:index':
new Todos();
break;
diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js
index c0da5866139..267b53fa4f2 100644
--- a/app/assets/javascripts/droplab/plugins/ajax.js
+++ b/app/assets/javascripts/droplab/plugins/ajax.js
@@ -11,6 +11,16 @@ const Ajax = {
if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data);
},
+ preprocessing: function preprocessing(config, data) {
+ let results = data;
+
+ if (config.preprocessing && !data.preprocessed) {
+ results = config.preprocessing(data);
+ AjaxCache.override(config.endpoint, results);
+ }
+
+ return results;
+ },
init: function init(hook) {
var self = this;
self.destroyed = false;
@@ -31,7 +41,8 @@ const Ajax = {
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
- AjaxCache.retrieve(config.endpoint)
+ return AjaxCache.retrieve(config.endpoint)
+ .then(self.preprocessing.bind(null, config))
.then((data) => self._loadData(data, config, self))
.catch(config.onError);
},
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 2615d626c4c..0bc4b6f22a9 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -6,7 +6,7 @@ import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(options = {}) {
- const { input, endpoint, symbol } = options;
+ const { input, endpoint, symbol, preprocessing } = options;
super(options);
this.symbol = symbol;
this.config = {
@@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
+ preprocessing,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index ef8fe071012..9c7a4d9f6ad 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -50,6 +50,66 @@ class DropdownUtils {
return updatedItem;
}
+ static mergeDuplicateLabels(dataMap, newLabel) {
+ const updatedMap = dataMap;
+ const key = newLabel.title;
+
+ const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key);
+
+ if (!hasKeyProperty) {
+ updatedMap[key] = newLabel;
+ } else {
+ const existing = updatedMap[key];
+
+ if (!existing.multipleColors) {
+ existing.multipleColors = [existing.color];
+ }
+
+ existing.multipleColors.push(newLabel.color);
+ }
+
+ return updatedMap;
+ }
+
+ static duplicateLabelColor(labelColors) {
+ const colors = labelColors;
+ const spacing = 100 / colors.length;
+
+ // Reduce the colors to 4
+ colors.length = Math.min(colors.length, 4);
+
+ const color = colors.map((c, i) => {
+ const percentFirst = Math.floor(spacing * i);
+ const percentSecond = Math.floor(spacing * (i + 1));
+ return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
+ }).join(', ');
+
+ return `linear-gradient(${color})`;
+ }
+
+ static duplicateLabelPreprocessing(data) {
+ const results = [];
+ const dataMap = {};
+
+ data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
+
+ Object.keys(dataMap)
+ .forEach((key) => {
+ const label = dataMap[key];
+
+ if (label.multipleColors) {
+ label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
+ label.text_color = '#000000';
+ }
+
+ results.push(label);
+ });
+
+ results.preprocessed = true;
+
+ return results;
+ }
+
static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 61cef435209..47cecd5b5f7 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -54,6 +54,7 @@ class FilteredSearchDropdownManager {
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json`,
symbol: '~',
+ preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing,
},
element: this.container.querySelector('#js-dropdown-label'),
},
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 7872e9e68ad..3ce8b8607ad 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -20,13 +20,13 @@ class FilteredSearchManager {
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
});
this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
- const projectPath = this.searchHistoryDropdownElement ?
- this.searchHistoryDropdownElement.dataset.projectFullPath : 'project';
+ const fullPath = this.searchHistoryDropdownElement ?
+ this.searchHistoryDropdownElement.dataset.fullPath : 'project';
let recentSearchesPagePrefix = 'issue-recent-searches';
if (this.page === 'merge_requests') {
recentSearchesPagePrefix = 'merge-request-recent-searches';
}
- const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`;
+ const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`;
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index e9278140af0..243ee4d723a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -58,29 +58,54 @@ class FilteredSearchVisualTokens {
`;
}
+ static setTokenStyle(tokenContainer, backgroundColor, textColor) {
+ const token = tokenContainer;
+
+ // Labels with linear gradient should not override default background color
+ if (backgroundColor.indexOf('linear-gradient') === -1) {
+ token.style.backgroundColor = backgroundColor;
+ }
+
+ token.style.color = textColor;
+
+ if (textColor === '#FFFFFF') {
+ const removeToken = token.querySelector('.remove-token');
+ removeToken.classList.add('inverted');
+ }
+
+ return token;
+ }
+
+ static preprocessLabel(labelsEndpoint, labels) {
+ let processed = labels;
+
+ if (!labels.preprocessed) {
+ processed = gl.DropdownUtils.duplicateLabelPreprocessing(labels);
+ AjaxCache.override(labelsEndpoint, processed);
+ processed.preprocessed = true;
+ }
+
+ return processed;
+ }
+
static updateLabelTokenColor(tokenValueContainer, tokenValue) {
const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
const baseEndpoint = filteredSearchInput.dataset.baseEndpoint;
const labelsEndpoint = `${baseEndpoint}/labels.json`;
return AjaxCache.retrieve(labelsEndpoint)
- .then((labels) => {
- const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue);
-
- if (!matchingLabel) {
- return;
- }
+ .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
+ .then((labels) => {
+ const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue);
- const tokenValueStyle = tokenValueContainer.style;
- tokenValueStyle.backgroundColor = matchingLabel.color;
- tokenValueStyle.color = matchingLabel.text_color;
+ if (!matchingLabel) {
+ return;
+ }
- if (matchingLabel.text_color === '#FFFFFF') {
- const removeToken = tokenValueContainer.querySelector('.remove-token');
- removeToken.classList.add('inverted');
- }
- })
- .catch(() => new Flash('An error occurred while fetching label colors.'));
+ FilteredSearchVisualTokens
+ .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color);
+ })
+ .catch(() => new Flash('An error occurred while fetching label colors.'));
}
static updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 3babe273100..9475498e176 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -730,10 +730,10 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) {
if (this.options.filterable) {
- $(':focus').blur();
-
this.dropdown.one('transitionend', () => {
- this.filterInput.focus();
+ if (this.dropdown.is('.open')) {
+ this.filterInput.focus();
+ }
});
if (triggerFocus) {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 8d7d3d73571..f0e02ca0fb2 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -3,6 +3,7 @@
/* global ListLabel */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import DropdownUtils from './filtered_search/dropdown_utils';
(function() {
this.LabelsSelect = (function() {
@@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
}
}
if (label.duplicate) {
- spacing = 100 / label.color.length;
- // Reduce the colors to 4
- label.color = label.color.filter(function(color, i) {
- return i < 4;
- });
- color = _.map(label.color, function(color, i) {
- var percentFirst, percentSecond;
- percentFirst = Math.floor(spacing * i);
- percentSecond = Math.floor(spacing * (i + 1));
- return color + " " + percentFirst + "%," + color + " " + percentSecond + "% ";
- }).join(',');
- color = "linear-gradient(" + color + ")";
+ color = gl.DropdownUtils.duplicateLabelColor(label.color);
}
else {
if (label.color != null) {
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index 7477b5a5214..629d8f44e18 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -6,6 +6,10 @@ class AjaxCache extends Cache {
this.pendingRequests = { };
}
+ override(endpoint, data) {
+ this.internalStorage[endpoint] = data;
+ }
+
retrieve(endpoint, forceRetrieve) {
if (this.hasData(endpoint) && !forceRetrieve) {
return Promise.resolve(this.get(endpoint));
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index cf1566eeb87..f32b2413725 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,6 +1,6 @@
/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
-import 'vendor/cropper';
+import 'cropper';
((global) => {
// Matches everything but the file name
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 2091b275c3d..1dc1dbf356d 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,6 +1,40 @@
-document.addEventListener('DOMContentLoaded', () => {
+let hasUserDefinedProjectPath = false;
+
+const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
+ if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
+ return;
+ }
+
+ let importUrl = $projectImportUrl.val().trim();
+ if (importUrl.length === 0) {
+ return;
+ }
+
+ /*
+ \/?: remove trailing slash
+ (\.git\/?)?: remove trailing .git (with optional trailing slash)
+ (\?.*)?: remove query string
+ (#.*)?: remove fragment identifier
+ */
+ importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
+
+ // extract everything after the last slash
+ const pathMatch = /\/([^/]+)$/.exec(importUrl);
+ if (pathMatch) {
+ $projectPath.val(pathMatch[1]);
+ }
+};
+
+const bindEvents = () => {
+ const $newProjectForm = $('#new_project');
const importBtnTooltip = 'Please enter a valid project name.';
const $importBtnWrapper = $('.import_gitlab_project');
+ const $projectImportUrl = $('#project_import_url');
+ const $projectPath = $('#project_path');
+
+ if ($newProjectForm.length !== 1) {
+ return;
+ }
$('.how_to_import_link').on('click', (e) => {
e.preventDefault();
@@ -13,19 +47,19 @@ document.addEventListener('DOMContentLoaded', () => {
$('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href');
- $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$('#project_path').val()}`);
+ $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
});
- $('.btn_import_gitlab_project').attr('disabled', !$('#project_path').val().trim().length);
+ $('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
$importBtnWrapper.attr('title', importBtnTooltip);
- $('#new_project').on('submit', () => {
- const $path = $('#project_path');
- $path.val($path.val().trim());
+ $newProjectForm.on('submit', () => {
+ $projectPath.val($projectPath.val().trim());
});
- $('#project_path').on('keyup', () => {
- if ($('#project_path').val().trim().length) {
+ $projectPath.on('keyup', () => {
+ hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
+ if (hasUserDefinedProjectPath) {
$('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title', '');
$importBtnWrapper.removeClass('has-tooltip');
@@ -35,9 +69,17 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
- $('#project_import_url').disable();
+ $projectImportUrl.disable();
+ $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
+
$('.import_git').on('click', () => {
- const $projectImportUrl = $('#project_import_url');
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
});
-});
+};
+
+document.addEventListener('DOMContentLoaded', bindEvents);
+
+export default {
+ bindEvents,
+ deriveProjectPathFromUrl,
+};
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index a2de4598167..fcd4c72b430 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -185,3 +185,28 @@
text-overflow: ellipsis;
}
}
+
+// TODO: fallback to global style
+.atwho-view {
+ .atwho-view-ul {
+ padding: 8px 1px;
+
+ li {
+ padding: 8px 16px;
+ border: 0;
+
+ &.cur {
+ background-color: $gray-darker;
+ color: $gl-text-color;
+
+ small {
+ color: inherit;
+ }
+ }
+
+ strong {
+ color: $gl-text-color;
+ }
+ }
+ }
+}
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index da9b789d617..653e7bc7e40 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -66,7 +66,8 @@ module Projects
end
def filter_params
- params.merge(board_id: params[:board_id], id: params[:list_id]).compact
+ params.merge(board_id: params[:board_id], id: params[:list_id])
+ .reject { |_, value| value.nil? }
end
def move_params
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index fd7ab59ce64..ae0e0aa3cf9 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -127,15 +127,23 @@ module SearchHelper
end
def search_filter_input_options(type)
- {
+ opts = {
id: "filtered-search-#{type}",
placeholder: 'Search or filter results...',
data: {
- 'project-id' => @project.id,
- 'username-params' => @users.to_json(only: [:id, :username]),
- 'base-endpoint' => project_path(@project)
+ 'username-params' => @users.to_json(only: [:id, :username])
}
}
+
+ if @project.present?
+ opts[:data]['project-id'] = @project.id
+ opts[:data]['base-endpoint'] = project_path(@project)
+ else
+ # Group context
+ opts[:data]['base-endpoint'] = group_canonical_path(@group)
+ end
+
+ opts
end
# Sanitize a HTML field for search display. Most tags are stripped out and the
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 81844b1e2ca..9b1cac64c44 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,4 +1,8 @@
class NotificationSetting < ActiveRecord::Base
+ include IgnorableColumn
+
+ ignore_column :events
+
enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 }
default_value_for :level, NotificationSetting.levels[:global]
@@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline
].freeze
- store :events, coder: JSON
- before_save :convert_events
-
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
@@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base
setting
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()
-
- bool.nil? ? !!events[event] : bool
- end
-
- alias_method :"#{event}?", event
- end
-
# Allow people to receive failed pipeline notifications if they already have
# custom notifications enabled, as these are more like mentions than the other
# 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)
+ respond_to?(event) && !!public_send(event)
end
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 27c3ba197ac..2825478926a 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -101,7 +101,7 @@ class WebHookService
request_headers: build_headers(hook_name),
request_data: request_data,
response_headers: format_response_headers(response),
- response_body: response.body,
+ response_body: safe_response_body(response),
response_status: response.code,
internal_error_message: error_message
)
@@ -124,4 +124,10 @@ class WebHookService
def format_response_headers(response)
response.headers.each_capitalized.to_h
end
+
+ def safe_response_body(response)
+ return '' unless response.body
+
+ response.body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
+ end
end
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 735d9390699..f83ebbf09ef 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -4,6 +4,10 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
+- content_for :page_specific_javascripts do
+ = webpack_bundle_tag 'common_vue'
+ = webpack_bundle_tag 'filtered_search'
+
- if show_new_nav? && group_issues_exists
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
@@ -20,7 +24,7 @@
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue"
- = render 'shared/issuable/filter', type: :issues
+ = render 'shared/issuable/search_bar', type: :issues
.row-content-block.second-block
Only issues from the
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 3428d6e0445..1ad00461d76 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -1,5 +1,6 @@
- type = local_assigns.fetch(:type)
- block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
+- full_path = @project.present? ? @project.full_path : @group.full_path
.issues-filters
.issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal }
@@ -18,7 +19,7 @@
dropdown_class: "filtered-search-history-dropdown",
content_class: "filtered-search-history-dropdown-content",
title: "Recent searches" }) do
- .js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } }
+ .js-filtered-search-history-dropdown{ data: { full_path: full_path } }
.filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
diff --git a/changelogs/unreleased/33620-remove-events-from-notification_settings.yml b/changelogs/unreleased/33620-remove-events-from-notification_settings.yml
new file mode 100644
index 00000000000..f5f3ef3fb82
--- /dev/null
+++ b/changelogs/unreleased/33620-remove-events-from-notification_settings.yml
@@ -0,0 +1,4 @@
+---
+title: Remove events column from notification settings table
+merge_request:
+author:
diff --git a/changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml b/changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml
new file mode 100644
index 00000000000..ac480993d85
--- /dev/null
+++ b/changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Issue board when using Ruby 2.4
+merge_request: 13220
+author:
diff --git a/changelogs/unreleased/35815-webhook-log-encoding-error.yml b/changelogs/unreleased/35815-webhook-log-encoding-error.yml
new file mode 100644
index 00000000000..76ec235086c
--- /dev/null
+++ b/changelogs/unreleased/35815-webhook-log-encoding-error.yml
@@ -0,0 +1,4 @@
+---
+title: Fix encoding error for WebHook logging
+merge_request: 13230
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/add-filtered-search-group-issues-ce.yml b/changelogs/unreleased/add-filtered-search-group-issues-ce.yml
new file mode 100644
index 00000000000..f83f4173890
--- /dev/null
+++ b/changelogs/unreleased/add-filtered-search-group-issues-ce.yml
@@ -0,0 +1,4 @@
+---
+title: Add filtered search to group issue dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml b/changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml
new file mode 100644
index 00000000000..c1e831306df
--- /dev/null
+++ b/changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml
@@ -0,0 +1,4 @@
+---
+title: Add support for kube_namespace in Metrics queries
+merge_request: 16169
+author:
diff --git a/changelogs/unreleased/search-flickering.yml b/changelogs/unreleased/search-flickering.yml
new file mode 100644
index 00000000000..951a5a0292a
--- /dev/null
+++ b/changelogs/unreleased/search-flickering.yml
@@ -0,0 +1,4 @@
+---
+title: Fix search box losing focus when typing
+merge_request:
+author:
diff --git a/changelogs/unreleased/winh-derive-project-name.yml b/changelogs/unreleased/winh-derive-project-name.yml
new file mode 100644
index 00000000000..2244d21d768
--- /dev/null
+++ b/changelogs/unreleased/winh-derive-project-name.yml
@@ -0,0 +1,4 @@
+---
+title: Derive project path from import URL
+merge_request: 13131
+author:
diff --git a/db/post_migrate/20170728101014_remove_events_from_notification_settings.rb b/db/post_migrate/20170728101014_remove_events_from_notification_settings.rb
new file mode 100644
index 00000000000..cd533391d8d
--- /dev/null
+++ b/db/post_migrate/20170728101014_remove_events_from_notification_settings.rb
@@ -0,0 +1,9 @@
+class RemoveEventsFromNotificationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ remove_column :notification_settings, :events, :text
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5fbbdea6eaa..4ba218e1e0d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170725145659) do
+ActiveRecord::Schema.define(version: 20170728101014) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -981,7 +981,6 @@ ActiveRecord::Schema.define(version: 20170725145659) do
t.integer "level", default: 0, null: false
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"
diff --git a/doc/articles/index.md b/doc/articles/index.md
index d7e662d737f..9d2e5956029 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -12,7 +12,7 @@ where they were originally published.
## Authentication
-Learn how to explore GitLab's supported [authentications methods](../topics/authentication/index.md):
+Explore GitLab's supported [authentications methods](../topics/authentication/index.md):
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
@@ -34,6 +34,7 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) | Tutorial | 2016/10/12 |
| [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) | Tutorial | 2016/08/11 |
| [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | Technical overview | 2016/06/09 |
+| [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) | Technical overview | 2016/05/23 |
| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017/05/15 |
| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016/03/10 |
@@ -84,17 +85,18 @@ Install, upgrade, integrate, migrate to GitLab:
## Software development
-Learn how to explore the best of GitLab's software development's capabilities:
+Explore the best of GitLab's software development's capabilities:
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 |
| [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/)| Concepts | 2017/06/29 |
| [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) | Concepts | 2017/05/22 |
+| [Demo: Auto-Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/) | Technical overview | 2017/05/16 |
| [Demo: GitLab Service Desk](https://about.gitlab.com/2017/05/09/demo-service-desk/) | Feature highlight | 2017/05/09 |
-| [Demo - Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/) | Feature highlight | 2017/04/25 |
-| [Demo – Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/) | Feature highlight | 2017/04/18 |
-| [Demo - Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/) | Feature highlight | 2017/03/17 |
+| [Demo: Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/) | Feature highlight | 2017/04/25 |
+| [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/) | Feature highlight | 2017/04/18 |
+| [Demo: Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/) | Feature highlight | 2017/03/17 |
| [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/) | Technical overview | 2016/11/14 |
| [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Technical overview | 2016/10/25 |
| [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/) | Concepts | 2016/08/16 |
@@ -109,4 +111,3 @@ Learn how to explore the best of GitLab's software development's capabilities:
| [Why we are not leaving the cloud](https://about.gitlab.com/2017/03/02/why-we-are-not-leaving-the-cloud/) | Concepts | 2017/03/02 |
| [Why We Chose Vue.js](https://about.gitlab.com/2016/10/20/why-we-chose-vue/) | Concepts | 2016/10/20 |
| [Markdown Kramdown Tips & Tricks](https://about.gitlab.com/2016/07/19/markdown-kramdown-tips-and-tricks/) | Technical overview | 2016/07/19 |
-
diff --git a/doc/ci/README.md b/doc/ci/README.md
index ca7266ac68f..10ea9467942 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -23,6 +23,7 @@ The first steps towards your GitLab CI journey.
- [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
- [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
- **Videos:**
+ - [Demo (Streamed live on Jul 17, 2017): GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195)
- [Demo (March, 2017): how to get started using CI/CD with GitLab](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
- [Webcast (April, 2016): getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/)
- **Third-party videos:**
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
index 67c69d9ccf3..69d055c901c 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
@@ -6,14 +6,13 @@ module Gitlab
def query(deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
- query_context = {
- environment_slug: deployment.environment.slug,
- environment_filter: %{container_name!="POD",environment="#{deployment.environment.slug}"},
- timeframe_start: (deployment.created_at - 30.minutes).to_f,
- timeframe_end: (deployment.created_at + 30.minutes).to_f
- }
-
- query_metrics(query_context)
+ query_metrics(
+ common_query_context(
+ deployment.environment,
+ timeframe_start: (deployment.created_at - 30.minutes).to_f,
+ timeframe_end: (deployment.created_at + 30.minutes).to_f
+ )
+ )
end
end
end
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
index b5a679ddd79..db4708b22e4 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
@@ -6,14 +6,9 @@ module Gitlab
def query(environment_id)
Environment.find_by(id: environment_id).try do |environment|
- query_context = {
- environment_slug: environment.slug,
- environment_filter: %{container_name!="POD",environment="#{environment.slug}"},
- timeframe_start: 8.hours.ago.to_f,
- timeframe_end: Time.now.to_f
- }
-
- query_metrics(query_context)
+ query_metrics(
+ common_query_context(environment, timeframe_start: 8.hours.ago.to_f, timeframe_end: Time.now.to_f)
+ )
end
end
end
diff --git a/lib/gitlab/prometheus/queries/query_additional_metrics.rb b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
index e44be770544..7ac6162b54d 100644
--- a/lib/gitlab/prometheus/queries/query_additional_metrics.rb
+++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
@@ -42,15 +42,18 @@ module Gitlab
end
def process_query(context, query)
- query_with_result = query.dup
+ query = query.dup
result =
if query.key?(:query_range)
- client_query_range(query[:query_range] % context, start: context[:timeframe_start], stop: context[:timeframe_end])
+ query[:query_range] %= context
+ client_query_range(query[:query_range], start: context[:timeframe_start], stop: context[:timeframe_end])
else
- client_query(query[:query] % context, time: context[:timeframe_end])
+ query[:query] %= context
+ client_query(query[:query], time: context[:timeframe_end])
end
- query_with_result[:result] = result&.map(&:deep_symbolize_keys)
- query_with_result
+
+ query[:result] = result&.map(&:deep_symbolize_keys)
+ query
end
def available_metrics
@@ -67,6 +70,16 @@ module Gitlab
result.select { |group| group.metrics.any? }
end
+
+ def common_query_context(environment, timeframe_start:, timeframe_end:)
+ {
+ timeframe_start: timeframe_start,
+ timeframe_end: timeframe_end,
+ ci_environment_slug: environment.slug,
+ kube_namespace: environment.project.kubernetes_service&.actual_namespace || '',
+ environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
+ }
+ end
end
end
end
diff --git a/package.json b/package.json
index fd944531a6a..6b6577ec06e 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.2",
"core-js": "^2.4.1",
+ "cropper": "^2.3.0",
"css-loader": "^0.28.0",
"d3": "^3.5.11",
"deckar01-task_list": "^2.0.0",
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 2565622f8df..23ab2b141cf 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -4,7 +4,7 @@ describe Admin::ApplicationSettingsController do
include StubENV
let(:group) { create(:group) }
- let(:project) { create(:project, namespace: group) }
+ let(:project) { create(:empty_project, namespace: group) }
let(:admin) { create(:admin) }
let(:user) { create(:user)}
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
index 6eb9f7867d5..b290c45cdd4 100644
--- a/spec/controllers/admin/dashboard_controller_spec.rb
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -8,8 +8,8 @@ describe Admin::DashboardController do
it 'does not retrieve projects that are pending deletion' do
sign_in(create(:admin))
- project = create(:project)
- pending_delete_project = create(:project, pending_delete: true)
+ project = create(:empty_project)
+ pending_delete_project = create(:empty_project, pending_delete: true)
get :index
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 58486f33229..fa5660050ec 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -65,7 +65,7 @@ describe AutocompleteController do
end
context 'non-member login for public project' do
- let!(:project) { create(:project, :public) }
+ let!(:project) { create(:empty_project, :public) }
before do
sign_in(non_member)
@@ -127,7 +127,7 @@ describe AutocompleteController do
end
context 'unauthenticated user' do
- let(:public_project) { create(:project, :public) }
+ let(:public_project) { create(:empty_project, :public) }
let(:body) { JSON.parse(response.body) }
describe 'GET #users with public project' do
@@ -231,8 +231,8 @@ describe AutocompleteController do
end
context 'GET projects' do
- let(:authorized_project) { create(:project) }
- let(:authorized_search_project) { create(:project, name: 'rugged') }
+ let(:authorized_project) { create(:empty_project) }
+ let(:authorized_search_project) { create(:empty_project, name: 'rugged') }
before do
sign_in(user)
@@ -289,8 +289,8 @@ describe AutocompleteController do
context 'authorized projects apply limit' do
before do
- authorized_project2 = create(:project)
- authorized_project3 = create(:project)
+ authorized_project2 = create(:empty_project)
+ authorized_project3 = create(:empty_project)
authorized_project.add_master(user)
authorized_project2.add_master(user)
@@ -315,8 +315,8 @@ describe AutocompleteController do
context 'authorized projects with offset' do
before do
- authorized_project2 = create(:project)
- authorized_project3 = create(:project)
+ authorized_project2 = create(:empty_project)
+ authorized_project3 = create(:empty_project)
authorized_project.add_master(user)
authorized_project2.add_master(user)
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 566d8515198..05561946e9b 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe DashboardController do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
before do
project.team << [user, :master]
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
index 9278ac8edd8..393d38c6e6b 100644
--- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::MergeRequests::ConflictsController do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:merge_request_with_conflicts) do
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index f9d8f0f5fcf..fc4cec53374 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::MergeRequests::CreationsController do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:fork_project) { create(:forked_project_with_submodules) }
@@ -83,7 +83,7 @@ describe Projects::MergeRequests::CreationsController do
end
context 'when the source branch is in a different project to the target' do
- let(:other_project) { create(:project) }
+ let(:other_project) { create(:project, :repository) }
before do
other_project.team << [user, :master]
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 53fe2bdb189..fd6cb3c5dbb 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::MergeRequests::DiffsController do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -36,7 +36,7 @@ describe Projects::MergeRequests::DiffsController do
context 'with forked projects with submodules' do
render_views
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:fork_project) { create(:forked_project_with_submodules) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 0bfe83f1d98..bb67db268fa 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::MergeRequestsController do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:merge_request_with_conflicts) do
@@ -106,7 +106,7 @@ describe Projects::MergeRequestsController do
end
describe 'GET index' do
- let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
def get_merge_requests(page = nil)
get :index,
@@ -150,6 +150,8 @@ describe Projects::MergeRequestsController do
context 'when filtering by opened state' do
context 'with opened merge requests' do
it 'lists those merge requests' do
+ expect(merge_request).to be_persisted
+
get_merge_requests
expect(assigns(:merge_requests)).to include(merge_request)
@@ -191,7 +193,7 @@ describe Projects::MergeRequestsController do
end
context 'there is no source project' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:fork_project) { create(:forked_project_with_submodules) }
let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
@@ -429,7 +431,7 @@ describe Projects::MergeRequestsController do
context "when the user is owner" do
let(:owner) { create(:user) }
let(:namespace) { create(:namespace, owner: owner) }
- let(:project) { create(:project, namespace: namespace) }
+ let(:project) { create(:project, :repository, namespace: namespace) }
before do
sign_in owner
@@ -587,7 +589,7 @@ describe Projects::MergeRequestsController do
describe 'GET ci_environments_status' do
context 'the environment is from a forked project' do
- let!(:forked) { create(:project) }
+ let!(:forked) { create(:project, :repository) }
let!(:environment) { create(:environment, project: forked) }
let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
let(:admin) { create(:admin) }
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 3b88d5b0d7d..4a6eb76e71f 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -167,10 +167,10 @@ describe Projects::NotesController do
end
context 'when creating a commit comment from an MR fork' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:fork_project) do
- create(:project).tap do |fork|
+ create(:project, :repository).tap do |fork|
create(:forked_project_link, forked_to_project: fork, forked_from_project: project)
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index c8de275ca3e..ed63e4dec26 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -61,7 +61,7 @@ describe Projects::PipelinesController do
create_build('post deploy', 3, 'pages 0')
end
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:pipeline) do
create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id)
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index cc444f31797..1f082ce7102 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -156,7 +156,7 @@ describe Projects::SnippetsController do
end
describe 'PUT #update' do
- let(:project) { create :project, :public }
+ let(:project) { create :empty_project, :public }
let(:snippet) { create :project_snippet, author: user, project: project, visibility_level: visibility_level }
def update_snippet(snippet_params = {}, additional_params = {})
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 192cca45d99..9f19cc30582 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -196,7 +196,7 @@ describe ProjectsController do
context "redirection from http://someproject.git" do
it 'redirects to project page (format.html)' do
- project = create(:project, :public)
+ project = create(:empty_project, :public)
get :show, namespace_id: project.namespace, id: project, format: :git
@@ -254,7 +254,7 @@ describe ProjectsController do
describe '#transfer' do
render_views
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:admin) { create(:admin) }
let(:new_namespace) { create(:namespace) }
@@ -311,8 +311,8 @@ describe ProjectsController do
end
context "when the project is forked" do
- let(:project) { create(:project) }
- let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:fork_project) { create(:project, :repository, forked_from_project: project) }
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
@@ -390,7 +390,7 @@ describe ProjectsController do
end
context 'with forked project' do
- let(:project_fork) { create(:project, namespace: user.namespace) }
+ let(:project_fork) { create(:project, :repository, namespace: user.namespace) }
before do
create(:forked_project_link, forked_to_project: project_fork)
@@ -408,7 +408,7 @@ describe ProjectsController do
end
context 'when project not forked' do
- let(:unforked_project) { create(:project, namespace: user.namespace) }
+ let(:unforked_project) { create(:empty_project, namespace: user.namespace) }
it 'does nothing if project was not forked' do
delete(:remove_fork,
@@ -430,7 +430,7 @@ describe ProjectsController do
end
describe "GET refs" do
- let(:public_project) { create(:project, :public) }
+ let(:public_project) { create(:project, :public, :repository) }
it "gets a list of branches and tags" do
get :refs, namespace_id: public_project.namespace, id: public_project
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 475ceda11fe..a6edcca08f4 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -273,7 +273,7 @@ describe SnippetsController do
end
describe 'PUT #update' do
- let(:project) { create :project }
+ let(:project) { create :empty_project }
let(:snippet) { create :personal_snippet, author: user, project: project, visibility_level: visibility_level }
def update_snippet(snippet_params = {}, additional_params = {})
diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb
index b5e96d18b8f..ee41997e41a 100644
--- a/spec/factories/notification_settings.rb
+++ b/spec/factories/notification_settings.rb
@@ -3,6 +3,5 @@ FactoryGirl.define do
source factory: :empty_project
user
level 3
- events []
end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 3222c41c3d8..f49dbe6dc9e 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :upload do
- model { build(:project) }
+ model { build(:empty_project) }
path { "uploads/-/system/project/avatar/avatar.jpg" }
size 100.kilobytes
uploader "AvatarUploader"
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index abb9e5eef96..29ac96adc0e 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
feature 'Dashboard Projects' do
let(:user) { create(:user) }
- let(:project) { create(:project, name: 'awesome stuff') }
- let(:project2) { create(:project, :public, name: 'Community project') }
+ let(:project) { create(:project, :repository, name: 'awesome stuff') }
+ let(:project2) { create(:empty_project, :public, name: 'Community project') }
before do
project.team << [user, :developer]
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index fa83ad5d17c..0375d0bf8ff 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -4,7 +4,7 @@ describe 'Discussion Comments Merge Request', :js do
include RepoHelpers
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index 042f39f47e0..b0019c32189 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Discussion Comments Merge Request', :js do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 449a99a2c7b..cdf7aceb13c 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Group issues page' do
+ include FilteredSearchHelpers
+
let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
@@ -31,12 +33,10 @@ feature 'Group issues page' do
let(:path) { issues_group_path(group) }
it 'filters by only group users' do
- click_button('Assignee')
-
- wait_for_requests
+ filtered_search.set('assignee:')
- expect(find('.dropdown-menu-assignee')).to have_link(user.name)
- expect(find('.dropdown-menu-assignee')).not_to have_link(user2.name)
+ expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
+ expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
end
end
end
diff --git a/spec/features/issuables/markdown_references_spec.rb b/spec/features/issuables/markdown_references_spec.rb
index 169381d703a..04d38165a15 100644
--- a/spec/features/issuables/markdown_references_spec.rb
+++ b/spec/features/issuables/markdown_references_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
describe 'Markdown References', :js do
let(:user) { create(:user) }
- let(:actual_project) { create(:project, :public) }
+ let(:actual_project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project)}
let(:issue_actual_project) { create(:issue, project: actual_project) }
let!(:other_project) { create(:empty_project, :public) }
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 9855cfd85c4..62ac9fd0e95 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Editing file blob', js: true do
include TreeHelper
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
let(:branch) { 'master' }
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 1074eb62b33..3bf25221e36 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -4,7 +4,7 @@ describe 'Reportable note on commit', :js do
include RepoHelpers
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
before do
project.add_master(user)
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index a491abdb6cb..bb296546e06 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Reportable note on merge request', :js do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 300ba8422e8..9fb9ffa95e7 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe MembersFinder, '#execute' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, :access_requestable, parent: group) }
- let(:project) { create(:project, namespace: nested_group) }
+ let(:project) { create(:empty_project, namespace: nested_group) }
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index 7ecb75da8ce..7c6f3b101d7 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ButtonHelper do
describe 'http_clone_button' do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:has_tooltip_class) { 'has-tooltip' }
def element
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index e6bb953e9d8..3f8df388014 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -48,7 +48,7 @@ describe CiStatusHelper do
describe "#pipeline_status_cache_key" do
it "builds a cache key for pipeline status" do
pipeline_status = Gitlab::Cache::Ci::ProjectPipelineStatus.new(
- build(:project),
+ build_stubbed(:empty_project),
pipeline_info: {
sha: "123abc",
status: "success"
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 060c112e20d..f81a9b6492c 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe DiffHelper do
include RepoHelpers
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.raw_diffs }
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 7789cfa3554..1aad8a85ab7 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -180,7 +180,7 @@ describe IssuablesHelper do
context 'when show_full_reference falsey' do
context 'when @group present' do
it 'display issuable reference to @group' do
- project = build_stubbed(:project)
+ project = build_stubbed(:empty_project)
assign(:show_full_reference, nil)
assign(:group, project.namespace)
@@ -193,7 +193,7 @@ describe IssuablesHelper do
context 'when @project present' do
it 'display issuable reference to @project' do
- project = build_stubbed(:project)
+ project = build_stubbed(:empty_project)
assign(:show_full_reference, nil)
assign(:group, nil)
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 70eb01c9c44..6dfffb7ac63 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -42,7 +42,7 @@ describe MarkupHelper do
describe "override default project" do
let(:actual) { issue.to_reference }
- let(:second_project) { create(:project, :public) }
+ let(:second_project) { create(:empty_project, :public) }
let(:second_issue) { create(:issue, project: second_project) }
it 'links to the issue' do
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 56f252ba273..da1044cef3f 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -40,7 +40,7 @@ describe NotesHelper do
end
describe '#discussion_path' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
context 'for a merge request discusion' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) }
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index faf26931efc..c447c061919 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -46,7 +46,7 @@ describe ProjectsHelper do
end
describe "readme_cache_key" do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
before do
helper.instance_variable_set(:@project, project)
@@ -64,7 +64,7 @@ describe ProjectsHelper do
end
describe "#project_list_cache_key", clean_gitlab_redis_shared_state: true do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
it "includes the route" do
expect(helper.project_list_cache_key(project)).to include(project.route.cache_key)
@@ -251,7 +251,7 @@ describe ProjectsHelper do
end
describe '#sanitized_import_error' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
before do
allow(project).to receive(:repository_storage_path).and_return('/base/repo/path')
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index b7e547dc1f5..4dab21440a6 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -68,4 +68,38 @@ describe SearchHelper do
end
end
end
+
+ describe 'search_filter_input_options' do
+ context 'project' do
+ before do
+ @project = create(:project, :repository)
+ end
+
+ it 'includes id with type' do
+ expect(search_filter_input_options('type')[:id]).to eq('filtered-search-type')
+ end
+
+ it 'includes project-id' do
+ expect(search_filter_input_options('')[:data]['project-id']).to eq(@project.id)
+ end
+
+ it 'includes project base-endpoint' do
+ expect(search_filter_input_options('')[:data]['base-endpoint']).to eq(project_path(@project))
+ end
+ end
+
+ context 'group' do
+ before do
+ @group = create(:group, name: 'group')
+ end
+
+ it 'does not includes project-id' do
+ expect(search_filter_input_options('')[:data]['project-id']).to eq(nil)
+ end
+
+ it 'includes group base-endpoint' do
+ expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/groups#{group_path(@group)}")
+ end
+ end
+ end
end
diff --git a/spec/javascripts/droplab/plugins/ajax_spec.js b/spec/javascripts/droplab/plugins/ajax_spec.js
new file mode 100644
index 00000000000..085f25764fe
--- /dev/null
+++ b/spec/javascripts/droplab/plugins/ajax_spec.js
@@ -0,0 +1,36 @@
+import AjaxCache from '~/lib/utils/ajax_cache';
+import Ajax from '~/droplab/plugins/ajax';
+
+describe('Ajax', () => {
+ describe('preprocessing', () => {
+ const config = {};
+
+ describe('is not configured', () => {
+ it('passes the data through', () => {
+ const data = ['data'];
+ expect(Ajax.preprocessing(config, data)).toEqual(data);
+ });
+ });
+
+ describe('is configured', () => {
+ const processedArray = ['processed'];
+
+ beforeEach(() => {
+ config.preprocessing = () => processedArray;
+ spyOn(config, 'preprocessing').and.callFake(() => processedArray);
+ });
+
+ it('calls preprocessing', () => {
+ Ajax.preprocessing(config, []);
+ expect(config.preprocessing.calls.count()).toBe(1);
+ });
+
+ it('overrides AjaxCache', () => {
+ spyOn(AjaxCache, 'override').and.callFake((endpoint, results) => expect(results).toEqual(processedArray));
+
+ Ajax.preprocessing(config, []);
+ expect(AjaxCache.override.calls.count()).toBe(1);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index f55726379f3..244f170ab7a 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -191,6 +191,102 @@ describe('Dropdown Utils', () => {
});
});
+ describe('mergeDuplicateLabels', () => {
+ const dataMap = {
+ label: {
+ title: 'label',
+ color: '#FFFFFF',
+ },
+ };
+
+ it('should add label to dataMap if it is not a duplicate', () => {
+ const newLabel = {
+ title: 'new-label',
+ color: '#000000',
+ };
+
+ const updated = gl.DropdownUtils.mergeDuplicateLabels(dataMap, newLabel);
+ expect(updated[newLabel.title]).toEqual(newLabel);
+ });
+
+ it('should merge colors if label is a duplicate', () => {
+ const duplicate = {
+ title: 'label',
+ color: '#000000',
+ };
+
+ const updated = gl.DropdownUtils.mergeDuplicateLabels(dataMap, duplicate);
+ expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]);
+ });
+ });
+
+ describe('duplicateLabelColor', () => {
+ it('should linear-gradient 2 colors', () => {
+ const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']);
+ expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)');
+ });
+
+ it('should linear-gradient 3 colors', () => {
+ const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']);
+ expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)');
+ });
+
+ it('should linear-gradient 4 colors', () => {
+ const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD']);
+ expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)');
+ });
+
+ it('should not linear-gradient more than 4 colors', () => {
+ const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD', '#EEEEEE']);
+ expect(gradient.indexOf('#EEEEEE') === -1).toEqual(true);
+ });
+ });
+
+ describe('duplicateLabelPreprocessing', () => {
+ it('should set preprocessed to true', () => {
+ const results = gl.DropdownUtils.duplicateLabelPreprocessing([]);
+ expect(results.preprocessed).toEqual(true);
+ });
+
+ it('should not mutate existing data if there are no duplicates', () => {
+ const data = [{
+ title: 'label1',
+ color: '#FFFFFF',
+ }, {
+ title: 'label2',
+ color: '#000000',
+ }];
+ const results = gl.DropdownUtils.duplicateLabelPreprocessing(data);
+
+ expect(results.length).toEqual(2);
+ expect(results[0]).toEqual(data[0]);
+ expect(results[1]).toEqual(data[1]);
+ });
+
+ describe('duplicate labels', () => {
+ const data = [{
+ title: 'label',
+ color: '#FFFFFF',
+ }, {
+ title: 'label',
+ color: '#000000',
+ }];
+ const results = gl.DropdownUtils.duplicateLabelPreprocessing(data);
+
+ it('should merge duplicate labels', () => {
+ expect(results.length).toEqual(1);
+ });
+
+ it('should convert multiple colored labels into linear-gradient', () => {
+ expect(results[0].color).toEqual(gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']));
+ });
+
+ it('should set multiple colored label text color to black', () => {
+ expect(results[0].text_color).toEqual('#000000');
+ });
+ });
+ });
+
describe('setDataValueIfSelected', () => {
beforeEach(() => {
spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput')
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index fa4343ffbc8..67166802c70 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -797,6 +797,69 @@ describe('Filtered Search Visual Tokens', () => {
});
});
+ describe('setTokenStyle', () => {
+ let originalTextColor;
+
+ beforeEach(() => {
+ originalTextColor = bugLabelToken.style.color;
+ });
+
+ it('should set backgroundColor', () => {
+ const originalBackgroundColor = bugLabelToken.style.backgroundColor;
+ const token = subject.setTokenStyle(bugLabelToken, 'blue', 'white');
+ expect(token.style.backgroundColor).toEqual('blue');
+ expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor);
+ });
+
+ it('should not set backgroundColor when it is a linear-gradient', () => {
+ const token = subject.setTokenStyle(bugLabelToken, 'linear-gradient(135deg, red, blue)', 'white');
+ expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor);
+ });
+
+ it('should set textColor', () => {
+ const token = subject.setTokenStyle(bugLabelToken, 'white', 'black');
+ expect(token.style.color).toEqual('black');
+ expect(token.style.color).not.toEqual(originalTextColor);
+ });
+
+ it('should add inverted class when textColor is #FFFFFF', () => {
+ const token = subject.setTokenStyle(bugLabelToken, 'black', '#FFFFFF');
+ expect(token.style.color).toEqual('rgb(255, 255, 255)');
+ expect(token.style.color).not.toEqual(originalTextColor);
+ expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true);
+ });
+ });
+
+ describe('preprocessLabel', () => {
+ const endpoint = 'endpoint';
+
+ it('does not preprocess more than once', () => {
+ let labels = [];
+
+ spyOn(gl.DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []);
+
+ labels = gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels);
+ gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels);
+
+ expect(gl.DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1);
+ });
+
+ describe('not preprocessed before', () => {
+ it('returns preprocessed labels', () => {
+ let labels = [];
+ expect(labels.preprocessed).not.toEqual(true);
+ labels = gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels);
+ expect(labels.preprocessed).toEqual(true);
+ });
+
+ it('overrides AjaxCache with preprocessed results', () => {
+ spyOn(AjaxCache, 'override').and.callFake(() => {});
+ gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, []);
+ expect(AjaxCache.override.calls.count()).toEqual(1);
+ });
+ });
+ });
+
describe('updateLabelTokenColor', () => {
const jsonFixtureName = 'labels/project_labels.json';
const dummyEndpoint = '/dummy/endpoint';
diff --git a/spec/javascripts/fixtures/balsamiq.rb b/spec/javascripts/fixtures/balsamiq.rb
index b5372821bf5..234e246119a 100644
--- a/spec/javascripts/fixtures/balsamiq.rb
+++ b/spec/javascripts/fixtures/balsamiq.rb
@@ -4,7 +4,7 @@ describe 'Balsamiq file', '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'balsamiq-project') }
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'balsamiq-project') }
before(:all) do
clean_frontend_fixtures('blob/balsamiq/')
diff --git a/spec/javascripts/fixtures/dashboard.rb b/spec/javascripts/fixtures/dashboard.rb
index e83db8daaf2..b2114e65d27 100644
--- a/spec/javascripts/fixtures/dashboard.rb
+++ b/spec/javascripts/fixtures/dashboard.rb
@@ -5,7 +5,7 @@ describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controll
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
+ let(:project) { create(:empty_project, namespace: namespace, path: 'builds-project') }
render_views
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 7e2f364ffa4..f9d8b5c569c 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -5,7 +5,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
let(:pipeline) do
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
index ac5b06ace6d..4481a187f63 100644
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ b/spec/javascripts/fixtures/merge_requests_diffs.rb
@@ -6,7 +6,7 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
diff --git a/spec/javascripts/fixtures/pdf.rb b/spec/javascripts/fixtures/pdf.rb
index 6b2422a7986..ef9976b9fd3 100644
--- a/spec/javascripts/fixtures/pdf.rb
+++ b/spec/javascripts/fixtures/pdf.rb
@@ -4,7 +4,7 @@ describe 'PDF file', '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'pdf-project') }
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'pdf-project') }
before(:all) do
clean_frontend_fixtures('blob/pdf/')
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index 6c33b240e5c..b828eee6629 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -5,7 +5,7 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
+ let(:project) { create(:empty_project, namespace: namespace, path: 'builds-project') }
render_views
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb
index 17533443d76..25f5a3b0bb3 100644
--- a/spec/javascripts/fixtures/raw.rb
+++ b/spec/javascripts/fixtures/raw.rb
@@ -4,7 +4,7 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'raw-project') }
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'raw-project') }
before(:all) do
clean_frontend_fixtures('blob/notebook/')
diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js
index 2c946802dcd..49971bd91e2 100644
--- a/spec/javascripts/lib/utils/ajax_cache_spec.js
+++ b/spec/javascripts/lib/utils/ajax_cache_spec.js
@@ -77,6 +77,15 @@ describe('AjaxCache', () => {
});
});
+ describe('override', () => {
+ it('overrides existing cache', () => {
+ AjaxCache.internalStorage.endpoint = 'existing-endpoint';
+ AjaxCache.override('endpoint', 'new-endpoint');
+
+ expect(AjaxCache.internalStorage.endpoint).toEqual('new-endpoint');
+ });
+ });
+
describe('retrieve', () => {
let ajaxSpy;
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
new file mode 100644
index 00000000000..850768f0e4f
--- /dev/null
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -0,0 +1,127 @@
+import projectNew from '~/projects/project_new';
+
+describe('New Project', () => {
+ let $projectImportUrl;
+ let $projectPath;
+
+ beforeEach(() => {
+ setFixtures(`
+ <input id="project_import_url" />
+ <input id="project_path" />
+ `);
+
+ $projectImportUrl = $('#project_import_url');
+ $projectPath = $('#project_path');
+ });
+
+ describe('deriveProjectPathFromUrl', () => {
+ const dummyImportUrl = `${gl.TEST_HOST}/dummy/import/url.git`;
+
+ beforeEach(() => {
+ projectNew.bindEvents();
+ $projectPath.val('').keyup().val(dummyImportUrl);
+ });
+
+ it('does not change project path for disabled $projectImportUrl', () => {
+ $projectImportUrl.attr('disabled', true);
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual(dummyImportUrl);
+ });
+
+ describe('for enabled $projectImportUrl', () => {
+ beforeEach(() => {
+ $projectImportUrl.attr('disabled', false);
+ });
+
+ it('does not change project path if it is set by user', () => {
+ $projectPath.keyup();
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual(dummyImportUrl);
+ });
+
+ it('does not change project path for empty $projectImportUrl', () => {
+ $projectImportUrl.val('');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual(dummyImportUrl);
+ });
+
+ it('does not change project path for whitespace $projectImportUrl', () => {
+ $projectImportUrl.val(' ');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual(dummyImportUrl);
+ });
+
+ it('does not change project path for $projectImportUrl without slashes', () => {
+ $projectImportUrl.val('has-no-slash');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual(dummyImportUrl);
+ });
+
+ it('changes project path to last $projectImportUrl component', () => {
+ $projectImportUrl.val('/this/is/last');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('last');
+ });
+
+ it('ignores trailing slashes in $projectImportUrl', () => {
+ $projectImportUrl.val('/has/trailing/slash/');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('slash');
+ });
+
+ it('ignores fragment identifier in $projectImportUrl', () => {
+ $projectImportUrl.val('/this/has/a#fragment-identifier/');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('a');
+ });
+
+ it('ignores query string in $projectImportUrl', () => {
+ $projectImportUrl.val('/url/with?query=string');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('with');
+ });
+
+ it('ignores trailing .git in $projectImportUrl', () => {
+ $projectImportUrl.val('/repository.git');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('repository');
+ });
+
+ it('changes project path for HTTPS URL in $projectImportUrl', () => {
+ $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('project');
+ });
+
+ it('changes project path for SSH URL in $projectImportUrl', () => {
+ $projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git');
+
+ projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+ expect($projectPath.val()).toEqual('gitlab-ce');
+ });
+ });
+ });
+});
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index b9ca68e8935..81f244a4539 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Banzai::Filter::ReferenceFilter do
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
describe '#each_node' do
it 'iterates over the nodes in a document' do
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index cb4ae3be525..e76463b5e7c 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe ContainerRegistry::Tag do
let(:group) { create(:group, name: 'group') }
- let(:project) { create(:project, path: 'test', group: group) }
+ let(:project) { create(:project, :repository, path: 'test', group: group) }
let(:repository) do
create(:container_repository, name: '', project: project)
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index 1547bd3228c..da789bf3705 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Badge::Coverage::Report do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:job_name) { nil }
let(:badge) do
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 0daf41a7c86..1fab3b29424 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
- let!(:project) { create(:project) }
+ let!(:project) { create(:project, :repository) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { "projects/#{project.id}/pipeline_status" }
@@ -18,7 +18,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' }
let(:ref) { 'master' }
let(:pipeline_info) { { sha: sha, status: status, ref: ref } }
- let!(:project_without_status) { create(:project) }
+ let!(:project_without_status) { create(:project, :repository) }
describe '.load_in_batch_for_projects' do
it 'preloads pipeline_status on projects' do
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index d8757c601ab..854aaa34c73 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:max_events) { 2 }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { create(:user, :admin) }
let(:start_time_attrs) { Issue.arel_table[:created_at] }
let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index a1b3fe8509e..28ea7d4c303 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'cycle analytics events' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index df7d1b5d27a..254ce06381e 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -94,7 +94,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
end
it "renames the route for projects of the namespace" do
- project = create(:project, path: "project-path", namespace: namespace)
+ project = create(:project, :repository, path: "project-path", namespace: namespace)
subject.rename_path_for_routable(migration_namespace(namespace))
@@ -120,7 +120,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
context "the-path namespace -> subgroup -> the-path0 project" do
it "updates the route of the project correctly" do
subgroup = create(:group, path: "subgroup", parent: namespace)
- project = create(:project, path: "the-path0", namespace: subgroup)
+ project = create(:project, :repository, path: "the-path0", namespace: subgroup)
subject.rename_path_for_routable(migration_namespace(namespace))
@@ -165,7 +165,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
it 'renames all the routes for the namespace' do
child = create(:group, path: 'child', parent: namespace)
- project = create(:project, namespace: child, path: 'the-project')
+ project = create(:project, :repository, namespace: child, path: 'the-project')
other_one = create(:namespace, path: 'the-path-is-similar')
subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index 803e923b4a5..95695ca8c16 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -94,7 +94,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
describe '#move_repositories' do
let(:namespace) { create(:group, name: 'hello-group') }
it 'moves a project for a namespace' do
- create(:project, namespace: namespace, path: 'hello-project')
+ create(:project, :repository, namespace: namespace, path: 'hello-project')
expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git')
subject.move_repositories(namespace, 'hello-group', 'bye-group')
@@ -104,7 +104,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
it 'moves a namespace in a subdirectory correctly' do
child_namespace = create(:group, name: 'sub-group', parent: namespace)
- create(:project, namespace: child_namespace, path: 'hello-project')
+ create(:project, :repository, namespace: child_namespace, path: 'hello-project')
expected_path = File.join(TestEnv.repos_path, 'hello-group', 'renamed-sub-group', 'hello-project.git')
@@ -115,7 +115,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
it 'moves a parent namespace with subdirectories' do
child_namespace = create(:group, name: 'sub-group', parent: namespace)
- create(:project, namespace: child_namespace, path: 'hello-project')
+ create(:project, :repository, namespace: child_namespace, path: 'hello-project')
expected_path = File.join(TestEnv.repos_path, 'renamed-group', 'sub-group', 'hello-project.git')
subject.move_repositories(child_namespace, 'hello-group', 'renamed-group')
@@ -166,7 +166,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
describe '#rename_namespace_dependencies' do
it "moves the the repository for a project in the namespace" do
- create(:project, namespace: namespace, path: "the-path-project")
+ create(:project, :repository, namespace: namespace, path: "the-path-project")
expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git")
subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
@@ -243,7 +243,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
describe '#revert_renames', redis: true do
it 'renames the routes back to the previous values' do
- project = create(:project, path: 'a-project', namespace: namespace)
+ project = create(:project, :repository, path: 'a-project', namespace: namespace)
subject.rename_namespace(namespace)
expect(subject).to receive(:perform_rename)
@@ -261,7 +261,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
end
it 'moves the repositories back to their original place' do
- project = create(:project, path: 'a-project', namespace: namespace)
+ project = create(:project, :repository, path: 'a-project', namespace: namespace)
project.create_repository
subject.rename_namespace(namespace)
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index 0e240a5ccf1..19e23a32bda 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -104,7 +104,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr
describe '#move_repository' do
let(:known_parent) { create(:namespace, path: 'known-parent') }
- let(:project) { create(:project, path: 'the-path', namespace: known_parent) }
+ let(:project) { create(:project, :repository, path: 'the-path', namespace: known_parent) }
it 'moves the repository for a project' do
expected_path = File.join(TestEnv.repos_path, 'known-parent', 'new-repo.git')
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index c71568e2a65..8af49ed50ff 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Diff::Parser do
include RepoHelpers
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.raw_diffs.first }
let(:parser) { described_class.new }
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index f61dbc67ad1..89258d2eca7 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
describe Gitlab::Email::AttachmentUploader do
describe "#execute" do
- let(:project) { build(:project) }
+ let(:project) { build(:empty_project) }
let(:message_raw) { fixture_file("emails/attachment.eml") }
let(:message) { Mail::Message.new(message_raw) }
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index bd36d1d309d..6d0e715e889 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -69,7 +69,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
context "when project is private" do
- let(:project) { create(:project, :private, namespace: namespace) }
+ let(:project) { create(:empty_project, :private, namespace: namespace) }
it "raises a ProjectNotFound if the user is not a member" do
expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index d00a2deaf7b..d570f34985b 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -207,7 +207,7 @@ describe Gitlab::GithubImport::Importer do
end
end
- let(:project) { create(:project, :wiki_disabled, import_url: "#{repo_root}/octocat/Hello-World.git") }
+ let(:project) { create(:project, :repository, :wiki_disabled, import_url: "#{repo_root}/octocat/Hello-World.git") }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:credentials) { { user: 'joe' } }
diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
index fcd90fab547..119bf2b5662 100644
--- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::WikiFormatter do
let(:project) do
- create(:project,
+ create(:empty_project,
namespace: create(:namespace, path: 'gitlabhq'),
import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git')
end
diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb
index ac3558ab386..4e09020471b 100644
--- a/spec/lib/gitlab/gl_repository_spec.rb
+++ b/spec/lib/gitlab/gl_repository_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe ::Gitlab::GlRepository do
describe '.parse' do
- set(:project) { create(:project) }
+ set(:project) { create(:project, :repository) }
it 'parses a project gl_repository' do
expect(described_class.parse("project-#{project.id}")).to eq([project, false])
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 0ff64cbe880..230cc2d3006 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -2,11 +2,11 @@ require 'spec_helper'
describe 'forked project import' do
let(:user) { create(:user) }
- let!(:project_with_repo) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:empty_project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
- let(:forked_from_project) { create(:project) }
+ let(:forked_from_project) { create(:project, :repository) }
let(:fork_link) { create(:forked_project_link, forked_from_project: project_with_repo) }
let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index f2b66c4421c..4d87f27ce05 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::ImportExport::MergeRequestParser do
let(:user) { create(:user) }
- let!(:project) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') }
- let(:forked_from_project) { create(:project) }
+ let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let(:forked_from_project) { create(:project, :repository) }
let(:fork_link) { create(:forked_project_link, forked_from_project: project) }
let!(:merge_request) do
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 82935af2670..d57833c3fec 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::RepoRestorer do
describe 'bundle a project Git repo' do
let(:user) { create(:user) }
- let!(:project_with_repo) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:empty_project) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb
index aeb32ef96d6..c4138c063c8 100644
--- a/spec/lib/gitlab/issuable_sorter_spec.rb
+++ b/spec/lib/gitlab/issuable_sorter_spec.rb
@@ -1,25 +1,25 @@
require 'spec_helper'
describe Gitlab::IssuableSorter do
- let(:namespace1) { build(:namespace, id: 1) }
- let(:project1) { build(:project, id: 1, namespace: namespace1) }
+ let(:namespace1) { build_stubbed(:namespace, id: 1) }
+ let(:project1) { build_stubbed(:empty_project, id: 1, namespace: namespace1) }
- let(:project2) { build(:project, id: 2, path: "a", namespace: project1.namespace) }
- let(:project3) { build(:project, id: 3, path: "b", namespace: project1.namespace) }
+ let(:project2) { build_stubbed(:empty_project, id: 2, path: "a", namespace: project1.namespace) }
+ let(:project3) { build_stubbed(:empty_project, id: 3, path: "b", namespace: project1.namespace) }
- let(:namespace2) { build(:namespace, id: 2, path: "a") }
- let(:namespace3) { build(:namespace, id: 3, path: "b") }
- let(:project4) { build(:project, id: 4, path: "a", namespace: namespace2) }
- let(:project5) { build(:project, id: 5, path: "b", namespace: namespace2) }
- let(:project6) { build(:project, id: 6, path: "a", namespace: namespace3) }
+ let(:namespace2) { build_stubbed(:namespace, id: 2, path: "a") }
+ let(:namespace3) { build_stubbed(:namespace, id: 3, path: "b") }
+ let(:project4) { build_stubbed(:empty_project, id: 4, path: "a", namespace: namespace2) }
+ let(:project5) { build_stubbed(:empty_project, id: 5, path: "b", namespace: namespace2) }
+ let(:project6) { build_stubbed(:empty_project, id: 6, path: "a", namespace: namespace3) }
let(:unsorted) { [sorted[2], sorted[3], sorted[0], sorted[1]] }
let(:sorted) do
- [build(:issue, iid: 1, project: project1),
- build(:issue, iid: 2, project: project1),
- build(:issue, iid: 10, project: project1),
- build(:issue, iid: 20, project: project1)]
+ [build_stubbed(:issue, iid: 1, project: project1),
+ build_stubbed(:issue, iid: 2, project: project1),
+ build_stubbed(:issue, iid: 10, project: project1),
+ build_stubbed(:issue, iid: 20, project: project1)]
end
it 'sorts references by a given key' do
@@ -41,14 +41,14 @@ describe Gitlab::IssuableSorter do
context 'for references from multiple projects and namespaces' do
let(:sorted) do
- [build(:issue, iid: 1, project: project1),
- build(:issue, iid: 2, project: project1),
- build(:issue, iid: 10, project: project1),
- build(:issue, iid: 1, project: project2),
- build(:issue, iid: 1, project: project3),
- build(:issue, iid: 1, project: project4),
- build(:issue, iid: 1, project: project5),
- build(:issue, iid: 1, project: project6)]
+ [build_stubbed(:issue, iid: 1, project: project1),
+ build_stubbed(:issue, iid: 2, project: project1),
+ build_stubbed(:issue, iid: 10, project: project1),
+ build_stubbed(:issue, iid: 1, project: project2),
+ build_stubbed(:issue, iid: 1, project: project3),
+ build_stubbed(:issue, iid: 1, project: project4),
+ build_stubbed(:issue, iid: 1, project: project5),
+ build_stubbed(:issue, iid: 1, project: project6)]
end
let(:unsorted) do
[sorted[3], sorted[1], sorted[4], sorted[2],
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 6af1564da19..de4c8b68ffa 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Middleware::Go do
let(:current_user) { nil }
context 'with simple 2-segment project path' do
- let!(:project) { create(:project, :private) }
+ let!(:project) { create(:empty_project, :private) }
context 'with subpackages' do
let(:path) { "#{project.full_path}/subpackage" }
@@ -39,7 +39,7 @@ describe Gitlab::Middleware::Go do
context 'with a nested project path' do
let(:group) { create(:group, :nested) }
- let!(:project) { create(:project, :public, namespace: group) }
+ let!(:project) { create(:empty_project, :public, namespace: group) }
shared_examples 'a nested project' do
context 'when the project is public' do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index d17b436b910..7851beb956b 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -100,14 +100,14 @@ describe Gitlab::ProjectSearchResults do
end
describe 'wiki search' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:empty_project, :public) }
let(:wiki) { build(:project_wiki, project: project) }
let!(:wiki_page) { wiki.create_page('Title', 'Content') }
subject(:results) { described_class.new(user, project, 'Content').objects('wiki_blobs') }
context 'when wiki is disabled' do
- let(:project) { create(:project, :public, :wiki_disabled) }
+ let(:project) { create(:empty_project, :public, :wiki_disabled) }
it 'hides wiki blobs from members' do
project.add_reporter(user)
@@ -121,7 +121,7 @@ describe Gitlab::ProjectSearchResults do
end
context 'when wiki is internal' do
- let(:project) { create(:project, :public, :wiki_private) }
+ let(:project) { create(:empty_project, :public, :wiki_private) }
it 'finds wiki blobs for guest' do
project.add_guest(user)
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
index e42e034f4fb..c7169717fc1 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -1,19 +1,14 @@
require 'spec_helper'
describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
- include Prometheus::MetricBuilders
-
- let(:client) { double('prometheus_client') }
- let(:environment) { create(:environment, slug: 'environment-slug') }
- let(:deployment) { create(:deployment, environment: environment) }
-
- subject(:query_result) { described_class.new(client).query(deployment.id) }
-
around do |example|
Timecop.freeze(Time.local(2008, 9, 1, 12, 0, 0)) { example.run }
end
include_examples 'additional metrics query' do
+ let(:deployment) { create(:deployment, environment: environment) }
+ let(:query_params) { [deployment.id] }
+
it 'queries using specific time' do
expect(client).to receive(:query_range).with(anything,
start: (deployment.created_at - 30.minutes).to_f,
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index e9fd66d45fe..5a88b23aa82 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -1,20 +1,16 @@
require 'spec_helper'
describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
- include Prometheus::MetricBuilders
-
- let(:client) { double('prometheus_client') }
- let(:environment) { create(:environment, slug: 'environment-slug') }
-
- subject(:query_result) { described_class.new(client).query(environment.id) }
-
around do |example|
Timecop.freeze { example.run }
end
include_examples 'additional metrics query' do
+ let(:query_params) { [environment.id] }
+
it 'queries using specific time' do
expect(client).to receive(:query_range).with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f)
+
expect(query_result).not_to be_nil
end
end
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index efea4f429bf..6336f4a7125 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe ::Gitlab::RepoPath do
describe '.parse' do
- set(:project) { create(:project) }
+ set(:project) { create(:project, :repository) }
context 'a repository storage path' do
it 'parses a full repository path' do
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 5ebaf6c1507..cd97416bcc9 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::UserAccess do
let(:access) { described_class.new(user, project: project) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
describe '#can_push_to_branch?' do
diff --git a/spec/migrations/rename_system_namespaces_spec.rb b/spec/migrations/rename_system_namespaces_spec.rb
index 626a6005838..747694cbe33 100644
--- a/spec/migrations/rename_system_namespaces_spec.rb
+++ b/spec/migrations/rename_system_namespaces_spec.rb
@@ -58,7 +58,7 @@ describe RenameSystemNamespaces, truncate: true do
end
it "renames the route for projects of the namespace" do
- project = build(:project, path: "project-path", namespace: system_namespace)
+ project = build(:project, :repository, path: "project-path", namespace: system_namespace)
save_invalid_routable(project)
migration.up
@@ -68,7 +68,7 @@ describe RenameSystemNamespaces, truncate: true do
it "doesn't touch routes of namespaces that look like system" do
namespace = create(:group, path: 'systemlookalike')
- project = create(:project, namespace: namespace, path: 'the-project')
+ project = create(:project, :repository, namespace: namespace, path: 'the-project')
migration.up
@@ -77,7 +77,7 @@ describe RenameSystemNamespaces, truncate: true do
end
it "moves the the repository for a project in the namespace" do
- project = build(:project, namespace: system_namespace, path: "system-project")
+ project = build(:project, :repository, namespace: system_namespace, path: "system-project")
save_invalid_routable(project)
TestEnv.copy_repo(project,
bare_repo: TestEnv.factory_repo_path_bare,
@@ -105,7 +105,7 @@ describe RenameSystemNamespaces, truncate: true do
describe "clears the markdown cache for projects in the system namespace" do
let!(:project) do
- project = build(:project, namespace: system_namespace)
+ project = build(:project, :repository, namespace: system_namespace)
save_invalid_routable(project)
project
end
@@ -161,7 +161,7 @@ describe RenameSystemNamespaces, truncate: true do
it "updates the route of the project correctly" do
subgroup = build(:group, path: "subgroup", parent: system_namespace)
save_invalid_routable(subgroup)
- project = build(:project, path: "system0", namespace: subgroup)
+ project = build(:project, :repository, path: "system0", namespace: subgroup)
save_invalid_routable(project)
migration.up
@@ -174,7 +174,7 @@ describe RenameSystemNamespaces, truncate: true do
describe "#move_repositories" do
let(:namespace) { create(:group, name: "hello-group") }
it "moves a project for a namespace" do
- create(:project, namespace: namespace, path: "hello-project")
+ create(:project, :repository, namespace: namespace, path: "hello-project")
expected_path = File.join(TestEnv.repos_path, "bye-group", "hello-project.git")
migration.move_repositories(namespace, "hello-group", "bye-group")
@@ -184,7 +184,7 @@ describe RenameSystemNamespaces, truncate: true do
it "moves a namespace in a subdirectory correctly" do
child_namespace = create(:group, name: "sub-group", parent: namespace)
- create(:project, namespace: child_namespace, path: "hello-project")
+ create(:project, :repository, namespace: child_namespace, path: "hello-project")
expected_path = File.join(TestEnv.repos_path, "hello-group", "renamed-sub-group", "hello-project.git")
@@ -195,7 +195,7 @@ describe RenameSystemNamespaces, truncate: true do
it "moves a parent namespace with subdirectories" do
child_namespace = create(:group, name: "sub-group", parent: namespace)
- create(:project, namespace: child_namespace, path: "hello-project")
+ create(:project, :repository, namespace: child_namespace, path: "hello-project")
expected_path = File.join(TestEnv.repos_path, "renamed-group", "sub-group", "hello-project.git")
migration.move_repositories(child_namespace, "hello-group", "renamed-group")
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
index 82f6f7e5046..35e7e1805d4 100644
--- a/spec/models/blob_viewer/composer_json_spec.rb
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::ComposerJson do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) do
<<-SPEC.strip_heredoc
{
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
index 14cc5f3c0fd..5406ff552f8 100644
--- a/spec/models/blob_viewer/gemspec_spec.rb
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::Gemspec do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) do
<<-SPEC.strip_heredoc
Gem::Specification.new do |s|
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index 7a4f9866375..344fcdeb9b6 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::GitlabCiYml do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) }
subject { described_class.new(blob) }
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index 96fb1b08c99..976c2ed3639 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::PackageJson do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) do
<<-SPEC.strip_heredoc
{
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
index f510077a87b..b98360ea7b7 100644
--- a/spec/models/blob_viewer/podspec_json_spec.rb
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::PodspecJson do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) do
<<-SPEC.strip_heredoc
{
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
index 7c38083550c..ad2bbe034ea 100644
--- a/spec/models/blob_viewer/podspec_spec.rb
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::Podspec do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) do
<<-SPEC.strip_heredoc
Pod::Spec.new do |spec|
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
index 115731b4970..12b6519a344 100644
--- a/spec/models/blob_viewer/route_map_spec.rb
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe BlobViewer::RouteMap do
include FakeBlobHelpers
- let(:project) { build(:project) }
+ let(:project) { build_stubbed(:empty_project) }
let(:data) do
<<-MAP.strip_heredoc
# Team data
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 431f1482615..6c4b5a9e9d6 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -24,7 +24,7 @@ describe Participable do
user1 = build(:user)
user2 = build(:user)
user3 = build(:user)
- project = build(:project, :public)
+ project = build(:empty_project, :public)
instance = model.new
expect(instance).to receive(:foo).and_return(user2)
@@ -57,7 +57,7 @@ describe Participable do
other = other_model.new
user1 = build(:user)
user2 = build(:user)
- project = build(:project, :public)
+ project = build(:empty_project, :public)
expect(instance).to receive(:foo).and_return(other)
expect(other).to receive(:bar).and_return(user2)
@@ -69,7 +69,7 @@ describe Participable do
context 'when using a Proc as an attribute' do
it 'calls the supplied Proc' do
user1 = build(:user)
- project = build(:project, :public)
+ project = build(:empty_project, :public)
user_arg = nil
ext_arg = nil
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index 53eaa6f8461..d00faa4f8be 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Note, ResolvableNote do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
subject { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 36aedd2f701..866a835d62a 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -27,7 +27,7 @@ describe Group, 'Routable' do
it 'ensure route path uniqueness across different objects' do
create(:group, parent: group, path: 'xyz')
- duplicate = build(:project, namespace: group, path: 'xyz')
+ duplicate = build(:empty_project, namespace: group, path: 'xyz')
expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Route path has already been taken, Route is invalid')
end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index eff41d85972..bae88cb1d24 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe ContainerRepository do
let(:group) { create(:group, name: 'group') }
- let(:project) { create(:project, path: 'test', group: group) }
+ let(:project) { create(:project, :repository, path: 'test', group: group) }
let(:repository) do
create(:container_repository, name: 'my_image', project: project)
@@ -41,7 +41,7 @@ describe ContainerRepository do
end
context 'when path contains uppercase letters' do
- let(:project) { create(:project, path: 'MY_PROJECT', group: group) }
+ let(:project) { create(:project, :repository, path: 'MY_PROJECT', group: group) }
it 'returns a full path without capital letters' do
expect(repository.path).to eq('group/my_project/my_image')
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 6447095078b..c5708e70ef9 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -91,7 +91,7 @@ describe Deployment do
end
describe '#additional_metrics' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:deployment) { create(:deployment, project: project) }
subject { deployment.additional_metrics }
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index 2704698f6c9..fa02434b0fd 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -5,7 +5,7 @@ describe DiffDiscussion do
subject { described_class.new([diff_note]) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index ebf2c070116..9a35213c98d 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -21,7 +21,7 @@ describe Environment do
it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) }
describe '.order_by_last_deployed_at' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let!(:environment1) { create(:environment, project: project) }
let!(:environment2) { create(:environment, project: project) }
let!(:environment3) { create(:environment, project: project) }
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 38fbdd2536a..7dbeb4d2e74 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -41,7 +41,7 @@ describe ForkedProjectLink, "add link on fork" do
end
describe '#forked?' do
- let(:project_to) { create(:project, forked_project_link: forked_project_link) }
+ let(:project_to) { create(:project, :repository, forked_project_link: forked_project_link) }
let(:forked_project_link) { create(:forked_project_link) }
before do
diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb
index 9a9b1900aa5..c58fd46762a 100644
--- a/spec/models/gpg_signature_spec.rb
+++ b/spec/models/gpg_signature_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe GpgSignature do
describe '#commit' do
it 'fetches the commit through the project' do
commit_sha = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
- project = create :project
+ project = create :project, :repository
commit = create :commit, project: project
gpg_signature = create :gpg_signature, commit_sha: commit_sha
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index d72790eefe5..eaf06668327 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -724,7 +724,7 @@ describe Issue do
end
describe '#check_for_spam' do
- let(:project) { create :project, visibility_level: visibility_level }
+ let(:project) { create :empty_project, visibility_level: visibility_level }
let(:issue) { create :issue, project: project }
subject do
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index 07e296424ca..2a0d102d3fe 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -63,24 +63,20 @@ RSpec.describe NotificationSetting do
end
end
- describe 'event_enabled?' do
+ 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)
+ subject.update!(new_note: true)
- expect(subject.event_enabled?(:new_note)).to be(false)
+ expect(subject.event_enabled?(:new_note)).to be(true)
end
context 'when the column has a nil value' do
- it 'returns the value from the events hash' do
+ it 'returns false' do
expect(subject.event_enabled?(:new_note)).to be(false)
end
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index d68d8b719cd..0938a7b1b6d 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -9,7 +9,7 @@ describe ProjectGroupLink do
describe "Validation" do
let(:parent_group) { create(:group) }
let(:group) { create(:group, parent: parent_group) }
- let(:project) { create(:project, group: group) }
+ let(:project) { create(:empty_project, group: group) }
let!(:project_group_link) { create(:project_group_link, project: project) }
it { is_expected.to validate_presence_of(:project_id) }
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 413ceed73bf..ae80c4ae002 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -20,7 +20,7 @@ describe ChatNotificationService do
context 'with repository' do
it 'returns true' do
- subject.project = create(:project)
+ subject.project = create(:project, :repository)
expect(subject.can_test?).to be true
end
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index e6a1752576b..3ca123cb75d 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe IssueTrackerService do
describe 'Validations' do
- let(:project) { create :project }
+ let(:project) { create :empty_project }
describe 'only one issue tracker per project' do
let(:service) { RedmineService.new(project: project, active: true) }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index bc9374d6dbb..69bb8f1c725 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -190,7 +190,7 @@ describe JiraService do
describe '#test_settings' do
let(:jira_service) do
described_class.new(
- project: create(:project),
+ project: create(:empty_project),
url: 'http://jira.example.com',
username: 'jira_username',
password: 'jira_password'
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 29cc9d35fc9..3bc6fb09778 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -261,27 +261,27 @@ describe Project do
describe 'path validation' do
it 'allows paths reserved on the root namespace' do
- project = build(:project, path: 'api')
+ project = build(:empty_project, path: 'api')
expect(project).to be_valid
end
it 'rejects paths reserved on another level' do
- project = build(:project, path: 'tree')
+ project = build(:empty_project, path: 'tree')
expect(project).not_to be_valid
end
it 'rejects nested paths' do
parent = create(:group, :nested, path: 'environments')
- project = build(:project, path: 'folders', namespace: parent)
+ project = build(:empty_project, path: 'folders', namespace: parent)
expect(project).not_to be_valid
end
it 'allows a reserved group name' do
parent = create(:group)
- project = build(:project, path: 'avatar', namespace: parent)
+ project = build(:empty_project, path: 'avatar', namespace: parent)
expect(project).to be_valid
end
@@ -2045,7 +2045,7 @@ describe Project do
end
describe '#route_map_for' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:route_map) do
<<-MAP.strip_heredoc
- source: /source/(.*)/
@@ -2082,7 +2082,7 @@ describe Project do
end
describe '#public_path_for_source_path' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:route_map) do
Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
- source: /source/(.*)/
@@ -2196,7 +2196,7 @@ describe Project do
end
describe '#pipeline_status' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
it 'builds a pipeline status' do
expect(project.pipeline_status).to be_a(Gitlab::Cache::Ci::ProjectPipelineStatus)
end
@@ -2207,7 +2207,7 @@ describe Project do
end
describe '#append_or_update_attribute' do
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
it 'shows full error updating an invalid MR' do
error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 68228a038a8..9ccd366a48e 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -330,7 +330,7 @@ describe ProjectTeam do
end
shared_examples 'max member access for users' do
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
let(:group) { create(:group) }
let(:second_group) { create(:group) }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 0fd3a4d622a..3107037925b 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -300,7 +300,7 @@ describe Repository do
end
context "when committing to another project" do
- let(:forked_project) { create(:project) }
+ let(:forked_project) { create(:project, :repository) }
it "creates a fork and commit to the forked project" do
expect do
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 8b6b02916ae..8f05deb8b15 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -21,7 +21,7 @@ describe SentNotification do
end
context "when the noteable project and discussion project match" do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project) }
let(:discussion_id) { create(:note, project: project, noteable: issue).discussion_id }
subject { build(:sent_notification, project: project, noteable: issue, in_reply_to_discussion_id: discussion_id) }
@@ -128,7 +128,7 @@ describe SentNotification do
end
context 'for commit' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
subject { described_class.record(commit, project.creator.id) }
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index a83a83a7349..11a4c3c742e 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -97,7 +97,7 @@ describe Ci::BuildPolicy do
end
describe 'rules for protected ref' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, ref: 'some-ref', pipeline: pipeline) }
before do
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index b11b06d301f..48a8064c5fc 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -10,7 +10,7 @@ describe Ci::PipelinePolicy, :models do
describe 'rules' do
describe 'rules for protected ref' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
before do
project.add_developer(user)
diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb
index 035e20c7452..de4cb5b30c5 100644
--- a/spec/policies/environment_policy_spec.rb
+++ b/spec/policies/environment_policy_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe EnvironmentPolicy do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:environment) do
create(:environment, :with_review_app, project: project)
@@ -14,7 +14,7 @@ describe EnvironmentPolicy do
describe '#rules' do
context 'when user does not have access to the project' do
- let(:project) { create(:project, :private) }
+ let(:project) { create(:project, :private, :repository) }
it 'does not include ability to stop environment' do
expect(policy).to be_disallowed :stop_environment
@@ -22,7 +22,7 @@ describe EnvironmentPolicy do
end
context 'when anonymous user has access to the project' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
it 'does not include ability to stop environment' do
expect(policy).to be_disallowed :stop_environment
@@ -30,7 +30,7 @@ describe EnvironmentPolicy do
end
context 'when team member has access to the project' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
before do
project.add_developer(user)
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index c1a0313b13c..0dd4ede5538 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -105,7 +105,7 @@ describe MergeRequestPresenter do
end
context 'issues links' do
- let(:project) { create(:project, :private, creator: user, namespace: user.namespace) }
+ let(:project) { create(:project, :private, :repository, creator: user, namespace: user.namespace) }
let(:issue_a) { create(:issue, project: project) }
let(:issue_b) { create(:issue, project: project) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 9e268adf950..55c998b13b8 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -80,7 +80,7 @@ describe API::Files do
context 'when unauthenticated', 'and project is public' do
it_behaves_like 'repository files' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
let(:current_user) { nil }
end
end
@@ -153,7 +153,7 @@ describe API::Files do
context 'when unauthenticated', 'and project is public' do
it_behaves_like 'repository raw files' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
let(:current_user) { nil }
end
end
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
index f619b7e6eaf..d0e7a82e607 100644
--- a/spec/requests/api/notification_settings_spec.rb
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -72,8 +72,8 @@ describe API::NotificationSettings do
expect(response).to have_http_status(200)
expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level)
- expect(json_response['events']['new_note']).to eq(true)
- expect(json_response['events']['new_issue']).to eq(false)
+ expect(json_response['events']['new_note']).to be_truthy
+ expect(json_response['events']['new_issue']).to be_falsey
end
end
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index b34555d2815..9ff2b782b52 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::PipelineSchedules do
set(:developer) { create(:user) }
set(:user) { create(:user) }
- set(:project) { create(:project) }
+ set(:project) { create(:project, :repository) }
before do
project.add_developer(developer)
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 9fc73c6e092..f5413913c1e 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe API::Todos do
- let(:project_1) { create(:project) }
+ let(:project_1) { create(:project, :repository) }
let(:project_2) { create(:empty_project) }
let(:author_1) { create(:user) }
let(:author_2) { create(:user) }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 153596c2975..d5c53b703dd 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -13,7 +13,7 @@ describe API::Triggers do
let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
describe 'POST /projects/:project_id/trigger/pipeline' do
- let!(:project2) { create(:project) }
+ let!(:project2) { create(:project, :repository) }
let(:options) do
{
token: trigger_token
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
index 8b2d165c763..4ffa5d1784e 100644
--- a/spec/requests/api/v3/files_spec.rb
+++ b/spec/requests/api/v3/files_spec.rb
@@ -74,7 +74,7 @@ describe API::V3::Files do
context 'when unauthenticated', 'and project is public' do
it_behaves_like 'repository files' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
let(:current_user) { nil }
end
end
diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb
index b0eddbb5dd2..e7014458773 100644
--- a/spec/requests/api/v3/project_hooks_spec.rb
+++ b/spec/requests/api/v3/project_hooks_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::ProjectHooks, 'ProjectHooks' do
let(:user) { create(:user) }
let(:user3) { create(:user) }
- let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ let!(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
let!(:hook) do
create(:project_hook,
:all_events_enabled,
@@ -204,7 +204,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
it "returns a 404 if a user attempts to delete project hooks he/she does not own" do
test_user = create(:user)
- other_project = create(:project)
+ other_project = create(:empty_project)
other_project.team << [test_user, :master]
delete v3_api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user)
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index 60212660fb6..2a593dd8135 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -10,7 +10,7 @@ describe API::V3::Triggers do
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
describe 'POST /projects/:project_id/trigger' do
- let!(:project2) { create(:project) }
+ let!(:project2) { create(:empty_project) }
let(:options) do
{
token: trigger_token
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 4f1a90750b3..ff470a6cad0 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -600,7 +600,7 @@ describe 'Git LFS API and storage' do
context 'when user is not authenticated' do
describe 'is accessing public project' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:empty_project, :public) }
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
@@ -642,7 +642,7 @@ describe 'Git LFS API and storage' do
end
describe 'upload' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:empty_project, :public) }
let(:body) do
{
'operation' => 'upload',
@@ -1019,7 +1019,7 @@ describe 'Git LFS API and storage' do
end
describe 'to a forked project' do
- let(:upstream_project) { create(:project, :public) }
+ let(:upstream_project) { create(:empty_project, :public) }
let(:project_owner) { create(:user) }
let(:project) { fork_project(upstream_project, project_owner) }
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 9afeb2983b0..1325ba37fd8 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -13,7 +13,7 @@ describe 'Request Profiler' do
end
it 'creates a profile of the request' do
- project = create(:project, namespace: user.namespace)
+ project = create(:empty_project, namespace: user.namespace)
time = Time.now
path = "/#{project.full_path}"
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 4ec495f612e..730df4e0336 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -75,7 +75,7 @@ describe Ci::CreatePipelineService do
end
context 'when merge request target project is different from source project' do
- let!(:target_project) { create(:project) }
+ let!(:target_project) { create(:project, :repository) }
let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }
it 'updates head pipeline for merge request' do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index 1ced26ff98d..04f926ccb32 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -33,7 +33,7 @@ describe Ci::PlayBuildService, '#execute' do
end
context 'when project has repository' do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
it 'allows user with developer role to play a build' do
project.add_developer(user)
diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb
index ecb88653001..9ff3a5375b9 100644
--- a/spec/services/labels/create_service_spec.rb
+++ b/spec/services/labels/create_service_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
describe Labels::CreateService do
describe '#execute' do
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
let(:group) { create(:group) }
-
+
let(:hex_color) { '#FF0000' }
let(:named_color) { 'red' }
let(:upcase_color) { 'RED' }
diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb
index bb95fe20fbf..f0bec7e9f9c 100644
--- a/spec/services/labels/update_service_spec.rb
+++ b/spec/services/labels/update_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Labels::UpdateService do
describe '#execute' do
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
let(:hex_color) { '#FF0000' }
let(:named_color) { 'red' }
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
index 5739386dd0d..f7a6c3d323e 100644
--- a/spec/services/milestones/destroy_service_spec.rb
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Milestones::DestroyService do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) }
let(:issue) { create(:issue, project: project, milestone: milestone) }
let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) }
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 786335120fd..db30fe2c24e 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -40,7 +40,7 @@ describe Users::DestroyService do
end
context "a deleted user's issues" do
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
before do
project.add_developer(user)
@@ -66,7 +66,7 @@ describe Users::DestroyService do
end
context "a deleted user's merge_requests" do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
before do
project.add_developer(user)
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index a0030ce8809..ac3a8738cac 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Users::MigrateToGhostUserService do
let!(:user) { create(:user) }
- let!(:project) { create(:project) }
+ let!(:project) { create(:project, :repository) }
let(:service) { described_class.new(user) }
context "migrating a user's associated records to the ghost user" do
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index e79c12daa1c..0d710db812c 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -112,6 +112,23 @@ describe WebHookService do
end
end
+ context 'with unsafe response body' do
+ before do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "\xBB")
+ service_instance.execute
+ end
+
+ it 'log successful execution' do
+ expect(hook_log.trigger).to eq('push_hooks')
+ expect(hook_log.url).to eq(project_hook.url)
+ expect(hook_log.request_headers).to eq(headers)
+ expect(hook_log.response_body).to eq('')
+ expect(hook_log.response_status).to eq('200')
+ expect(hook_log.execution_duration).to be > 0
+ expect(hook_log.internal_error_message).to be_nil
+ end
+ end
+
context 'should not log ServiceHooks' do
let(:service_hook) { create(:service_hook) }
let(:service_instance) { described_class.new(service_hook, data, 'service_hook') }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 85335643921..609998d6e9c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -129,10 +129,14 @@ RSpec.configure do |config|
config.before(:example, :migration) do
ActiveRecord::Migrator
.migrate(migrations_paths, previous_migration.version)
+
+ ActiveRecord::Base.descendants.each(&:reset_column_information)
end
config.after(:example, :migration) do
ActiveRecord::Migrator.migrate(migrations_paths)
+
+ ActiveRecord::Base.descendants.each(&:reset_column_information)
end
config.around(:each, :nested_groups) do |example|
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 035428a7d9b..47a7fd0c2ea 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -5,7 +5,14 @@ shared_examples 'issuable record that supports quick actions in its description
include QuickActionsHelpers
let(:master) { create(:user) }
- let(:project) { create(:project, :public) }
+ let(:project) do
+ case issuable_type
+ when :merge_request
+ create(:project, :public, :repository)
+ when :issue
+ create(:empty_project, :public)
+ end
+ end
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let!(:label_bug) { create(:label, project: project, title: 'bug') }
let!(:label_feature) { create(:label, project: project, title: 'feature') }
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index 57b6abe12b7..2011408be93 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -6,7 +6,7 @@ module ExportFileHelper
ObjectWithParent = Struct.new(:object, :parent, :key_found)
def setup_project
- project = create(:project, :public)
+ project = create(:project, :public, :repository)
create(:release, project: project)
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index 3406e4c3161..1004c895bb4 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -11,10 +11,6 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
end
@issuable_ids << issuable.id
-
- issuable.id.times { create(:note, noteable: issuable, project: issuable.project) }
- (issuable.id + 1).times { create(:award_emoji, :downvote, awardable: issuable) }
- (issuable.id + 2).times { create(:award_emoji, :upvote, awardable: issuable) }
end
end
@@ -27,10 +23,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
meta_data = assigns(:issuable_meta_data)
- @issuable_ids.each do |id|
- expect(meta_data[id].notes_count).to eq(id)
- expect(meta_data[id].downvotes).to eq(id + 1)
- expect(meta_data[id].upvotes).to eq(id + 2)
+ aggregate_failures do
+ expect(meta_data.keys).to match_array(@issuable_ids)
+ expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta))
end
end
diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb
index 016e16fc8d4..620fa37d455 100644
--- a/spec/support/prometheus/additional_metrics_shared_examples.rb
+++ b/spec/support/prometheus/additional_metrics_shared_examples.rb
@@ -10,11 +10,61 @@ RSpec.shared_examples 'additional metrics query' do
[{ 'metric': {}, 'values': [[1488758662.506, '0.00002996364761904785'], [1488758722.506, '0.00003090239047619091']] }]
end
+ let(:client) { double('prometheus_client') }
+ let(:query_result) { described_class.new(client).query(*query_params) }
+ let(:environment) { create(:environment, slug: 'environment-slug') }
+
before do
allow(client).to receive(:label_values).and_return(metric_names)
allow(metric_group_class).to receive(:all).and_return([simple_metric_group(metrics: [simple_metric])])
end
+ context 'metrics query context' do
+ subject! { described_class.new(client) }
+
+ shared_examples 'query context containing environment slug and filter' do
+ it 'contains ci_environment_slug' do
+ expect(subject).to receive(:query_metrics).with(hash_including(ci_environment_slug: environment.slug))
+
+ subject.query(*query_params)
+ end
+
+ it 'contains environment filter' do
+ expect(subject).to receive(:query_metrics).with(
+ hash_including(
+ environment_filter: "container_name!=\"POD\",environment=\"#{environment.slug}\""
+ )
+ )
+
+ subject.query(*query_params)
+ end
+ end
+
+ describe 'project has Kubernetes service' do
+ let(:project) { create(:kubernetes_project) }
+ let(:environment) { create(:environment, slug: 'environment-slug', project: project) }
+ let(:kube_namespace) { project.kubernetes_service.actual_namespace }
+
+ it_behaves_like 'query context containing environment slug and filter'
+
+ it 'query context contains kube_namespace' do
+ expect(subject).to receive(:query_metrics).with(hash_including(kube_namespace: kube_namespace))
+
+ subject.query(*query_params)
+ end
+ end
+
+ describe 'project without Kubernetes service' do
+ it_behaves_like 'query context containing environment slug and filter'
+
+ it 'query context contains empty kube_namespace' do
+ expect(subject).to receive(:query_metrics).with(hash_including(kube_namespace: ''))
+
+ subject.query(*query_params)
+ end
+ end
+ end
+
context 'with one group where two metrics is found' do
before do
allow(metric_group_class).to receive(:all).and_return([simple_metric_group])
@@ -51,6 +101,7 @@ RSpec.shared_examples 'additional metrics query' do
context 'with two groups with one metric each' do
let(:metrics) { [simple_metric(queries: [simple_query])] }
+
before do
allow(metric_group_class).to receive(:all).and_return(
[
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 855051921f0..bbb63a08374 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -3,7 +3,14 @@ require "spec_helper"
shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields|
record_class_name = record_class.to_s.titleize.downcase
- let(:project) { create(:project) }
+ let(:project) do
+ case record_class
+ when MergeRequest
+ create(:project, :repository)
+ else
+ create(:empty_project)
+ end
+ end
before do
project.add_developer(user)
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 47e9365e13d..c2510a10286 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -5,7 +5,7 @@ describe FileUploader do
describe '.absolute_path' do
it 'returns the correct absolute path by building it dynamically' do
- project = build_stubbed(:project)
+ project = build_stubbed(:empty_project)
upload = double(model: project, path: 'secret/foo.jpg')
dynamic_segment = project.path_with_namespace
diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb
index 8bd5306ff98..dd90a836a78 100644
--- a/spec/validators/dynamic_path_validator_spec.rb
+++ b/spec/validators/dynamic_path_validator_spec.rb
@@ -28,7 +28,7 @@ describe DynamicPathValidator do
describe '#path_valid_for_record?' do
context 'for project' do
it 'calls valid_project_path?' do
- project = build(:project, path: 'activity')
+ project = build(:empty_project, path: 'activity')
expect(described_class).to receive(:valid_project_path?).with(project.full_path).and_call_original
diff --git a/spec/views/layouts/nav/_project.html.haml_spec.rb b/spec/views/layouts/nav/_project.html.haml_spec.rb
index fd1637ca91b..faea2505e40 100644
--- a/spec/views/layouts/nav/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/_project.html.haml_spec.rb
@@ -5,7 +5,7 @@ describe 'layouts/nav/_project' do
before do
stub_container_registry_config(enabled: true)
- assign(:project, create(:project))
+ assign(:project, create(:project, :repository))
allow(view).to receive(:current_ref).and_return('master')
allow(view).to receive(:can?).and_return(true)
diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
index f627f9165fb..d9d73f789c5 100644
--- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'notify/pipeline_failed_email.html.haml' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
let(:pipeline) do
diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
index ecd096ee579..a793b37e412 100644
--- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'notify/pipeline_success_email.html.haml' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
let(:pipeline) do
diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb
index c6a17d77d73..62ec011a3fe 100644
--- a/spec/workers/create_gpg_signature_worker_spec.rb
+++ b/spec/workers/create_gpg_signature_worker_spec.rb
@@ -4,7 +4,7 @@ describe CreateGpgSignatureWorker do
context 'when GpgKey is found' do
it 'calls Commit#signature' do
commit_sha = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
- project = create :project
+ project = create :empty_project
commit = instance_double(Commit)
allow(Project).to receive(:find_by).with(id: project.id).and_return(project)
@@ -18,7 +18,7 @@ describe CreateGpgSignatureWorker do
context 'when Commit is not found' do
let(:nonexisting_commit_sha) { 'bogus' }
- let(:project) { create :project }
+ let(:project) { create :empty_project }
it 'does not raise errors' do
expect { described_class.new.perform(nonexisting_commit_sha, project.id) }.not_to raise_error
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 6ebc94bb544..24f8ca67594 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -33,7 +33,7 @@ describe ProcessCommitWorker do
end
context 'when commit already exists in upstream project' do
- let(:forked) { create(:project, :public) }
+ let(:forked) { create(:project, :public, :repository) }
it 'does not process commit message' do
create(:forked_project_link, forked_to_project: forked, forked_from_project: project)
diff --git a/vendor/assets/javascripts/cropper.js b/vendor/assets/javascripts/cropper.js
deleted file mode 100644
index 805485904a5..00000000000
--- a/vendor/assets/javascripts/cropper.js
+++ /dev/null
@@ -1,2993 +0,0 @@
-/*!
- * Cropper v2.3.0
- * https://github.com/fengyuanchen/cropper
- *
- * Copyright (c) 2014-2016 Fengyuan Chen and contributors
- * Released under the MIT license
- *
- * Date: 2016-02-22T02:13:13.332Z
- */
-
-(function (factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as anonymous module.
- define(['jquery'], factory);
- } else if (typeof exports === 'object') {
- // Node / CommonJS
- factory(require('jquery'));
- } else {
- // Browser globals.
- factory(jQuery);
- }
-})(function ($) {
-
- 'use strict';
-
- // Globals
- var $window = $(window);
- var $document = $(document);
- var location = window.location;
- var navigator = window.navigator;
- var ArrayBuffer = window.ArrayBuffer;
- var Uint8Array = window.Uint8Array;
- var DataView = window.DataView;
- var btoa = window.btoa;
-
- // Constants
- var NAMESPACE = 'cropper';
-
- // Classes
- var CLASS_MODAL = 'cropper-modal';
- var CLASS_HIDE = 'cropper-hide';
- var CLASS_HIDDEN = 'cropper-hidden';
- var CLASS_INVISIBLE = 'cropper-invisible';
- var CLASS_MOVE = 'cropper-move';
- var CLASS_CROP = 'cropper-crop';
- var CLASS_DISABLED = 'cropper-disabled';
- var CLASS_BG = 'cropper-bg';
-
- // Events
- var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown';
- var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove';
- var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel';
- var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
- var EVENT_DBLCLICK = 'dblclick';
- var EVENT_LOAD = 'load.' + NAMESPACE;
- var EVENT_ERROR = 'error.' + NAMESPACE;
- var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace
- var EVENT_BUILD = 'build.' + NAMESPACE;
- var EVENT_BUILT = 'built.' + NAMESPACE;
- var EVENT_CROP_START = 'cropstart.' + NAMESPACE;
- var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE;
- var EVENT_CROP_END = 'cropend.' + NAMESPACE;
- var EVENT_CROP = 'crop.' + NAMESPACE;
- var EVENT_ZOOM = 'zoom.' + NAMESPACE;
-
- // RegExps
- var REGEXP_ACTIONS = /e|w|s|n|se|sw|ne|nw|all|crop|move|zoom/;
- var REGEXP_DATA_URL = /^data\:/;
- var REGEXP_DATA_URL_HEAD = /^data\:([^\;]+)\;base64,/;
- var REGEXP_DATA_URL_JPEG = /^data\:image\/jpeg.*;base64,/;
-
- // Data keys
- var DATA_PREVIEW = 'preview';
- var DATA_ACTION = 'action';
-
- // Actions
- var ACTION_EAST = 'e';
- var ACTION_WEST = 'w';
- var ACTION_SOUTH = 's';
- var ACTION_NORTH = 'n';
- var ACTION_SOUTH_EAST = 'se';
- var ACTION_SOUTH_WEST = 'sw';
- var ACTION_NORTH_EAST = 'ne';
- var ACTION_NORTH_WEST = 'nw';
- var ACTION_ALL = 'all';
- var ACTION_CROP = 'crop';
- var ACTION_MOVE = 'move';
- var ACTION_ZOOM = 'zoom';
- var ACTION_NONE = 'none';
-
- // Supports
- var SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext);
- var IS_SAFARI = navigator && /safari/i.test(navigator.userAgent) && /apple computer/i.test(navigator.vendor);
-
- // Maths
- var num = Number;
- var min = Math.min;
- var max = Math.max;
- var abs = Math.abs;
- var sin = Math.sin;
- var cos = Math.cos;
- var sqrt = Math.sqrt;
- var round = Math.round;
- var floor = Math.floor;
-
- // Utilities
- var fromCharCode = String.fromCharCode;
-
- function isNumber(n) {
- return typeof n === 'number' && !isNaN(n);
- }
-
- function isUndefined(n) {
- return typeof n === 'undefined';
- }
-
- function toArray(obj, offset) {
- var args = [];
-
- // This is necessary for IE8
- if (isNumber(offset)) {
- args.push(offset);
- }
-
- return args.slice.apply(obj, args);
- }
-
- // Custom proxy to avoid jQuery's guid
- function proxy(fn, context) {
- var args = toArray(arguments, 2);
-
- return function () {
- return fn.apply(context, args.concat(toArray(arguments)));
- };
- }
-
- function isCrossOriginURL(url) {
- var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
-
- return parts && (
- parts[1] !== location.protocol ||
- parts[2] !== location.hostname ||
- parts[3] !== location.port
- );
- }
-
- function addTimestamp(url) {
- var timestamp = 'timestamp=' + (new Date()).getTime();
-
- return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
- }
-
- function getCrossOrigin(crossOrigin) {
- return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : '';
- }
-
- function getImageSize(image, callback) {
- var newImage;
-
- // Modern browsers (ignore Safari, #120 & #509)
- if (image.naturalWidth && !IS_SAFARI) {
- return callback(image.naturalWidth, image.naturalHeight);
- }
-
- // IE8: Don't use `new Image()` here (#319)
- newImage = document.createElement('img');
-
- newImage.onload = function () {
- callback(this.width, this.height);
- };
-
- newImage.src = image.src;
- }
-
- function getTransform(options) {
- var transforms = [];
- var rotate = options.rotate;
- var scaleX = options.scaleX;
- var scaleY = options.scaleY;
-
- if (isNumber(rotate)) {
- transforms.push('rotate(' + rotate + 'deg)');
- }
-
- if (isNumber(scaleX) && isNumber(scaleY)) {
- transforms.push('scale(' + scaleX + ',' + scaleY + ')');
- }
-
- return transforms.length ? transforms.join(' ') : 'none';
- }
-
- function getRotatedSizes(data, isReversed) {
- var deg = abs(data.degree) % 180;
- var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180;
- var sinArc = sin(arc);
- var cosArc = cos(arc);
- var width = data.width;
- var height = data.height;
- var aspectRatio = data.aspectRatio;
- var newWidth;
- var newHeight;
-
- if (!isReversed) {
- newWidth = width * cosArc + height * sinArc;
- newHeight = width * sinArc + height * cosArc;
- } else {
- newWidth = width / (cosArc + sinArc / aspectRatio);
- newHeight = newWidth / aspectRatio;
- }
-
- return {
- width: newWidth,
- height: newHeight
- };
- }
-
- function getSourceCanvas(image, data) {
- var canvas = $('<canvas>')[0];
- var context = canvas.getContext('2d');
- var dstX = 0;
- var dstY = 0;
- var dstWidth = data.naturalWidth;
- var dstHeight = data.naturalHeight;
- var rotate = data.rotate;
- var scaleX = data.scaleX;
- var scaleY = data.scaleY;
- var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
- var rotatable = isNumber(rotate) && rotate !== 0;
- var advanced = rotatable || scalable;
- var canvasWidth = dstWidth * abs(scaleX || 1);
- var canvasHeight = dstHeight * abs(scaleY || 1);
- var translateX;
- var translateY;
- var rotated;
-
- if (scalable) {
- translateX = canvasWidth / 2;
- translateY = canvasHeight / 2;
- }
-
- if (rotatable) {
- rotated = getRotatedSizes({
- width: canvasWidth,
- height: canvasHeight,
- degree: rotate
- });
-
- canvasWidth = rotated.width;
- canvasHeight = rotated.height;
- translateX = canvasWidth / 2;
- translateY = canvasHeight / 2;
- }
-
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
-
- if (advanced) {
- dstX = -dstWidth / 2;
- dstY = -dstHeight / 2;
-
- context.save();
- context.translate(translateX, translateY);
- }
-
- if (rotatable) {
- context.rotate(rotate * Math.PI / 180);
- }
-
- // Should call `scale` after rotated
- if (scalable) {
- context.scale(scaleX, scaleY);
- }
-
- context.drawImage(image, floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
-
- if (advanced) {
- context.restore();
- }
-
- return canvas;
- }
-
- function getTouchesCenter(touches) {
- var length = touches.length;
- var pageX = 0;
- var pageY = 0;
-
- if (length) {
- $.each(touches, function (i, touch) {
- pageX += touch.pageX;
- pageY += touch.pageY;
- });
-
- pageX /= length;
- pageY /= length;
- }
-
- return {
- pageX: pageX,
- pageY: pageY
- };
- }
-
- function getStringFromCharCode(dataView, start, length) {
- var str = '';
- var i;
-
- for (i = start, length += start; i < length; i++) {
- str += fromCharCode(dataView.getUint8(i));
- }
-
- return str;
- }
-
- function getOrientation(arrayBuffer) {
- var dataView = new DataView(arrayBuffer);
- var length = dataView.byteLength;
- var orientation;
- var exifIDCode;
- var tiffOffset;
- var firstIFDOffset;
- var littleEndian;
- var endianness;
- var app1Start;
- var ifdStart;
- var offset;
- var i;
-
- // Only handle JPEG image (start by 0xFFD8)
- if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
- offset = 2;
-
- while (offset < length) {
- if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
- app1Start = offset;
- break;
- }
-
- offset++;
- }
- }
-
- if (app1Start) {
- exifIDCode = app1Start + 4;
- tiffOffset = app1Start + 10;
-
- if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
- endianness = dataView.getUint16(tiffOffset);
- littleEndian = endianness === 0x4949;
-
- if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
- if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
- firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
-
- if (firstIFDOffset >= 0x00000008) {
- ifdStart = tiffOffset + firstIFDOffset;
- }
- }
- }
- }
- }
-
- if (ifdStart) {
- length = dataView.getUint16(ifdStart, littleEndian);
-
- for (i = 0; i < length; i++) {
- offset = ifdStart + i * 12 + 2;
-
- if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
-
- // 8 is the offset of the current tag's value
- offset += 8;
-
- // Get the original orientation value
- orientation = dataView.getUint16(offset, littleEndian);
-
- // Override the orientation with its default value for Safari (#120)
- if (IS_SAFARI) {
- dataView.setUint16(offset, 1, littleEndian);
- }
-
- break;
- }
- }
- }
-
- return orientation;
- }
-
- function dataURLToArrayBuffer(dataURL) {
- var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
- var binary = atob(base64);
- var length = binary.length;
- var arrayBuffer = new ArrayBuffer(length);
- var dataView = new Uint8Array(arrayBuffer);
- var i;
-
- for (i = 0; i < length; i++) {
- dataView[i] = binary.charCodeAt(i);
- }
-
- return arrayBuffer;
- }
-
- // Only available for JPEG image
- function arrayBufferToDataURL(arrayBuffer) {
- var dataView = new Uint8Array(arrayBuffer);
- var length = dataView.length;
- var base64 = '';
- var i;
-
- for (i = 0; i < length; i++) {
- base64 += fromCharCode(dataView[i]);
- }
-
- return 'data:image/jpeg;base64,' + btoa(base64);
- }
-
- function Cropper(element, options) {
- this.$element = $(element);
- this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options);
- this.isLoaded = false;
- this.isBuilt = false;
- this.isCompleted = false;
- this.isRotated = false;
- this.isCropped = false;
- this.isDisabled = false;
- this.isReplaced = false;
- this.isLimited = false;
- this.wheeling = false;
- this.isImg = false;
- this.originalUrl = '';
- this.canvas = null;
- this.cropBox = null;
- this.init();
- }
-
- Cropper.prototype = {
- constructor: Cropper,
-
- init: function () {
- var $this = this.$element;
- var url;
-
- if ($this.is('img')) {
- this.isImg = true;
-
- // Should use `$.fn.attr` here. e.g.: "img/picture.jpg"
- this.originalUrl = url = $this.attr('src');
-
- // Stop when it's a blank image
- if (!url) {
- return;
- }
-
- // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg"
- url = $this.prop('src');
- } else if ($this.is('canvas') && SUPPORT_CANVAS) {
- url = $this[0].toDataURL();
- }
-
- this.load(url);
- },
-
- // A shortcut for triggering custom events
- trigger: function (type, data) {
- var e = $.Event(type, data);
-
- this.$element.trigger(e);
-
- return e;
- },
-
- load: function (url) {
- var options = this.options;
- var $this = this.$element;
- var read;
- var xhr;
-
- if (!url) {
- return;
- }
-
- // Trigger build event first
- $this.one(EVENT_BUILD, options.build);
-
- if (this.trigger(EVENT_BUILD).isDefaultPrevented()) {
- return;
- }
-
- this.url = url;
- this.image = {};
-
- if (!options.checkOrientation || !ArrayBuffer) {
- return this.clone();
- }
-
- read = $.proxy(this.read, this);
-
- // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
- if (REGEXP_DATA_URL.test(url)) {
- return REGEXP_DATA_URL_JPEG.test(url) ?
- read(dataURLToArrayBuffer(url)) :
- this.clone();
- }
-
- xhr = new XMLHttpRequest();
-
- xhr.onerror = xhr.onabort = $.proxy(function () {
- this.clone();
- }, this);
-
- xhr.onload = function () {
- read(this.response);
- };
-
- xhr.open('get', url);
- xhr.responseType = 'arraybuffer';
- xhr.send();
- },
-
- read: function (arrayBuffer) {
- var options = this.options;
- var orientation = getOrientation(arrayBuffer);
- var image = this.image;
- var rotate;
- var scaleX;
- var scaleY;
-
- if (orientation > 1) {
- this.url = arrayBufferToDataURL(arrayBuffer);
-
- switch (orientation) {
-
- // flip horizontal
- case 2:
- scaleX = -1;
- break;
-
- // rotate left 180°
- case 3:
- rotate = -180;
- break;
-
- // flip vertical
- case 4:
- scaleY = -1;
- break;
-
- // flip vertical + rotate right 90°
- case 5:
- rotate = 90;
- scaleY = -1;
- break;
-
- // rotate right 90°
- case 6:
- rotate = 90;
- break;
-
- // flip horizontal + rotate right 90°
- case 7:
- rotate = 90;
- scaleX = -1;
- break;
-
- // rotate left 90°
- case 8:
- rotate = -90;
- break;
- }
- }
-
- if (options.rotatable) {
- image.rotate = rotate;
- }
-
- if (options.scalable) {
- image.scaleX = scaleX;
- image.scaleY = scaleY;
- }
-
- this.clone();
- },
-
- clone: function () {
- var options = this.options;
- var $this = this.$element;
- var url = this.url;
- var crossOrigin = '';
- var crossOriginUrl;
- var $clone;
-
- if (options.checkCrossOrigin && isCrossOriginURL(url)) {
- crossOrigin = $this.prop('crossOrigin');
-
- if (crossOrigin) {
- crossOriginUrl = url;
- } else {
- crossOrigin = 'anonymous';
-
- // Bust cache (#148) when there is not a "crossOrigin" property
- crossOriginUrl = addTimestamp(url);
- }
- }
-
- this.crossOrigin = crossOrigin;
- this.crossOriginUrl = crossOriginUrl;
- this.$clone = $clone = $('<img' + getCrossOrigin(crossOrigin) + ' src="' + (crossOriginUrl || url) + '">');
-
- if (this.isImg) {
- if ($this[0].complete) {
- this.start();
- } else {
- $this.one(EVENT_LOAD, $.proxy(this.start, this));
- }
- } else {
- $clone.
- one(EVENT_LOAD, $.proxy(this.start, this)).
- one(EVENT_ERROR, $.proxy(this.stop, this)).
- addClass(CLASS_HIDE).
- insertAfter($this);
- }
- },
-
- start: function () {
- var $image = this.$element;
- var $clone = this.$clone;
-
- if (!this.isImg) {
- $clone.off(EVENT_ERROR, this.stop);
- $image = $clone;
- }
-
- getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) {
- $.extend(this.image, {
- naturalWidth: naturalWidth,
- naturalHeight: naturalHeight,
- aspectRatio: naturalWidth / naturalHeight
- });
-
- this.isLoaded = true;
- this.build();
- }, this));
- },
-
- stop: function () {
- this.$clone.remove();
- this.$clone = null;
- },
-
- build: function () {
- var options = this.options;
- var $this = this.$element;
- var $clone = this.$clone;
- var $cropper;
- var $cropBox;
- var $face;
-
- if (!this.isLoaded) {
- return;
- }
-
- // Unbuild first when replace
- if (this.isBuilt) {
- this.unbuild();
- }
-
- // Create cropper elements
- this.$container = $this.parent();
- this.$cropper = $cropper = $(Cropper.TEMPLATE);
- this.$canvas = $cropper.find('.cropper-canvas').append($clone);
- this.$dragBox = $cropper.find('.cropper-drag-box');
- this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box');
- this.$viewBox = $cropper.find('.cropper-view-box');
- this.$face = $face = $cropBox.find('.cropper-face');
-
- // Hide the original image
- $this.addClass(CLASS_HIDDEN).after($cropper);
-
- // Show the clone image if is hidden
- if (!this.isImg) {
- $clone.removeClass(CLASS_HIDE);
- }
-
- this.initPreview();
- this.bind();
-
- options.aspectRatio = max(0, options.aspectRatio) || NaN;
- options.viewMode = max(0, min(3, round(options.viewMode))) || 0;
-
- if (options.autoCrop) {
- this.isCropped = true;
-
- if (options.modal) {
- this.$dragBox.addClass(CLASS_MODAL);
- }
- } else {
- $cropBox.addClass(CLASS_HIDDEN);
- }
-
- if (!options.guides) {
- $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN);
- }
-
- if (!options.center) {
- $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN);
- }
-
- if (options.cropBoxMovable) {
- $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL);
- }
-
- if (!options.highlight) {
- $face.addClass(CLASS_INVISIBLE);
- }
-
- if (options.background) {
- $cropper.addClass(CLASS_BG);
- }
-
- if (!options.cropBoxResizable) {
- $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN);
- }
-
- this.setDragMode(options.dragMode);
- this.render();
- this.isBuilt = true;
- this.setData(options.data);
- $this.one(EVENT_BUILT, options.built);
-
- // Trigger the built event asynchronously to keep `data('cropper')` is defined
- setTimeout($.proxy(function () {
- this.trigger(EVENT_BUILT);
- this.isCompleted = true;
- }, this), 0);
- },
-
- unbuild: function () {
- if (!this.isBuilt) {
- return;
- }
-
- this.isBuilt = false;
- this.isCompleted = false;
- this.initialImage = null;
-
- // Clear `initialCanvas` is necessary when replace
- this.initialCanvas = null;
- this.initialCropBox = null;
- this.container = null;
- this.canvas = null;
-
- // Clear `cropBox` is necessary when replace
- this.cropBox = null;
- this.unbind();
-
- this.resetPreview();
- this.$preview = null;
-
- this.$viewBox = null;
- this.$cropBox = null;
- this.$dragBox = null;
- this.$canvas = null;
- this.$container = null;
-
- this.$cropper.remove();
- this.$cropper = null;
- },
-
- render: function () {
- this.initContainer();
- this.initCanvas();
- this.initCropBox();
-
- this.renderCanvas();
-
- if (this.isCropped) {
- this.renderCropBox();
- }
- },
-
- initContainer: function () {
- var options = this.options;
- var $this = this.$element;
- var $container = this.$container;
- var $cropper = this.$cropper;
-
- $cropper.addClass(CLASS_HIDDEN);
- $this.removeClass(CLASS_HIDDEN);
-
- $cropper.css((this.container = {
- width: max($container.width(), num(options.minContainerWidth) || 200),
- height: max($container.height(), num(options.minContainerHeight) || 100)
- }));
-
- $this.addClass(CLASS_HIDDEN);
- $cropper.removeClass(CLASS_HIDDEN);
- },
-
- // Canvas (image wrapper)
- initCanvas: function () {
- var viewMode = this.options.viewMode;
- var container = this.container;
- var containerWidth = container.width;
- var containerHeight = container.height;
- var image = this.image;
- var imageNaturalWidth = image.naturalWidth;
- var imageNaturalHeight = image.naturalHeight;
- var is90Degree = abs(image.rotate) === 90;
- var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth;
- var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight;
- var aspectRatio = naturalWidth / naturalHeight;
- var canvasWidth = containerWidth;
- var canvasHeight = containerHeight;
- var canvas;
-
- if (containerHeight * aspectRatio > containerWidth) {
- if (viewMode === 3) {
- canvasWidth = containerHeight * aspectRatio;
- } else {
- canvasHeight = containerWidth / aspectRatio;
- }
- } else {
- if (viewMode === 3) {
- canvasHeight = containerWidth / aspectRatio;
- } else {
- canvasWidth = containerHeight * aspectRatio;
- }
- }
-
- canvas = {
- naturalWidth: naturalWidth,
- naturalHeight: naturalHeight,
- aspectRatio: aspectRatio,
- width: canvasWidth,
- height: canvasHeight
- };
-
- canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2;
- canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2;
-
- this.canvas = canvas;
- this.isLimited = (viewMode === 1 || viewMode === 2);
- this.limitCanvas(true, true);
- this.initialImage = $.extend({}, image);
- this.initialCanvas = $.extend({}, canvas);
- },
-
- limitCanvas: function (isSizeLimited, isPositionLimited) {
- var options = this.options;
- var viewMode = options.viewMode;
- var container = this.container;
- var containerWidth = container.width;
- var containerHeight = container.height;
- var canvas = this.canvas;
- var aspectRatio = canvas.aspectRatio;
- var cropBox = this.cropBox;
- var isCropped = this.isCropped && cropBox;
- var minCanvasWidth;
- var minCanvasHeight;
- var newCanvasLeft;
- var newCanvasTop;
-
- if (isSizeLimited) {
- minCanvasWidth = num(options.minCanvasWidth) || 0;
- minCanvasHeight = num(options.minCanvasHeight) || 0;
-
- if (viewMode) {
- if (viewMode > 1) {
- minCanvasWidth = max(minCanvasWidth, containerWidth);
- minCanvasHeight = max(minCanvasHeight, containerHeight);
-
- if (viewMode === 3) {
- if (minCanvasHeight * aspectRatio > minCanvasWidth) {
- minCanvasWidth = minCanvasHeight * aspectRatio;
- } else {
- minCanvasHeight = minCanvasWidth / aspectRatio;
- }
- }
- } else {
- if (minCanvasWidth) {
- minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0);
- } else if (minCanvasHeight) {
- minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0);
- } else if (isCropped) {
- minCanvasWidth = cropBox.width;
- minCanvasHeight = cropBox.height;
-
- if (minCanvasHeight * aspectRatio > minCanvasWidth) {
- minCanvasWidth = minCanvasHeight * aspectRatio;
- } else {
- minCanvasHeight = minCanvasWidth / aspectRatio;
- }
- }
- }
- }
-
- if (minCanvasWidth && minCanvasHeight) {
- if (minCanvasHeight * aspectRatio > minCanvasWidth) {
- minCanvasHeight = minCanvasWidth / aspectRatio;
- } else {
- minCanvasWidth = minCanvasHeight * aspectRatio;
- }
- } else if (minCanvasWidth) {
- minCanvasHeight = minCanvasWidth / aspectRatio;
- } else if (minCanvasHeight) {
- minCanvasWidth = minCanvasHeight * aspectRatio;
- }
-
- canvas.minWidth = minCanvasWidth;
- canvas.minHeight = minCanvasHeight;
- canvas.maxWidth = Infinity;
- canvas.maxHeight = Infinity;
- }
-
- if (isPositionLimited) {
- if (viewMode) {
- newCanvasLeft = containerWidth - canvas.width;
- newCanvasTop = containerHeight - canvas.height;
-
- canvas.minLeft = min(0, newCanvasLeft);
- canvas.minTop = min(0, newCanvasTop);
- canvas.maxLeft = max(0, newCanvasLeft);
- canvas.maxTop = max(0, newCanvasTop);
-
- if (isCropped && this.isLimited) {
- canvas.minLeft = min(
- cropBox.left,
- cropBox.left + cropBox.width - canvas.width
- );
- canvas.minTop = min(
- cropBox.top,
- cropBox.top + cropBox.height - canvas.height
- );
- canvas.maxLeft = cropBox.left;
- canvas.maxTop = cropBox.top;
-
- if (viewMode === 2) {
- if (canvas.width >= containerWidth) {
- canvas.minLeft = min(0, newCanvasLeft);
- canvas.maxLeft = max(0, newCanvasLeft);
- }
-
- if (canvas.height >= containerHeight) {
- canvas.minTop = min(0, newCanvasTop);
- canvas.maxTop = max(0, newCanvasTop);
- }
- }
- }
- } else {
- canvas.minLeft = -canvas.width;
- canvas.minTop = -canvas.height;
- canvas.maxLeft = containerWidth;
- canvas.maxTop = containerHeight;
- }
- }
- },
-
- renderCanvas: function (isChanged) {
- var canvas = this.canvas;
- var image = this.image;
- var rotate = image.rotate;
- var naturalWidth = image.naturalWidth;
- var naturalHeight = image.naturalHeight;
- var aspectRatio;
- var rotated;
-
- if (this.isRotated) {
- this.isRotated = false;
-
- // Computes rotated sizes with image sizes
- rotated = getRotatedSizes({
- width: image.width,
- height: image.height,
- degree: rotate
- });
-
- aspectRatio = rotated.width / rotated.height;
-
- if (aspectRatio !== canvas.aspectRatio) {
- canvas.left -= (rotated.width - canvas.width) / 2;
- canvas.top -= (rotated.height - canvas.height) / 2;
- canvas.width = rotated.width;
- canvas.height = rotated.height;
- canvas.aspectRatio = aspectRatio;
- canvas.naturalWidth = naturalWidth;
- canvas.naturalHeight = naturalHeight;
-
- // Computes rotated sizes with natural image sizes
- if (rotate % 180) {
- rotated = getRotatedSizes({
- width: naturalWidth,
- height: naturalHeight,
- degree: rotate
- });
-
- canvas.naturalWidth = rotated.width;
- canvas.naturalHeight = rotated.height;
- }
-
- this.limitCanvas(true, false);
- }
- }
-
- if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) {
- canvas.left = canvas.oldLeft;
- }
-
- if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) {
- canvas.top = canvas.oldTop;
- }
-
- canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth);
- canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight);
-
- this.limitCanvas(false, true);
-
- canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft);
- canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop);
-
- this.$canvas.css({
- width: canvas.width,
- height: canvas.height,
- left: canvas.left,
- top: canvas.top
- });
-
- this.renderImage();
-
- if (this.isCropped && this.isLimited) {
- this.limitCropBox(true, true);
- }
-
- if (isChanged) {
- this.output();
- }
- },
-
- renderImage: function (isChanged) {
- var canvas = this.canvas;
- var image = this.image;
- var reversed;
-
- if (image.rotate) {
- reversed = getRotatedSizes({
- width: canvas.width,
- height: canvas.height,
- degree: image.rotate,
- aspectRatio: image.aspectRatio
- }, true);
- }
-
- $.extend(image, reversed ? {
- width: reversed.width,
- height: reversed.height,
- left: (canvas.width - reversed.width) / 2,
- top: (canvas.height - reversed.height) / 2
- } : {
- width: canvas.width,
- height: canvas.height,
- left: 0,
- top: 0
- });
-
- this.$clone.css({
- width: image.width,
- height: image.height,
- marginLeft: image.left,
- marginTop: image.top,
- transform: getTransform(image)
- });
-
- if (isChanged) {
- this.output();
- }
- },
-
- initCropBox: function () {
- var options = this.options;
- var canvas = this.canvas;
- var aspectRatio = options.aspectRatio;
- var autoCropArea = num(options.autoCropArea) || 0.8;
- var cropBox = {
- width: canvas.width,
- height: canvas.height
- };
-
- if (aspectRatio) {
- if (canvas.height * aspectRatio > canvas.width) {
- cropBox.height = cropBox.width / aspectRatio;
- } else {
- cropBox.width = cropBox.height * aspectRatio;
- }
- }
-
- this.cropBox = cropBox;
- this.limitCropBox(true, true);
-
- // Initialize auto crop area
- cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
- cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
-
- // The width of auto crop area must large than "minWidth", and the height too. (#164)
- cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea);
- cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea);
- cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2;
- cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2;
-
- this.initialCropBox = $.extend({}, cropBox);
- },
-
- limitCropBox: function (isSizeLimited, isPositionLimited) {
- var options = this.options;
- var aspectRatio = options.aspectRatio;
- var container = this.container;
- var containerWidth = container.width;
- var containerHeight = container.height;
- var canvas = this.canvas;
- var cropBox = this.cropBox;
- var isLimited = this.isLimited;
- var minCropBoxWidth;
- var minCropBoxHeight;
- var maxCropBoxWidth;
- var maxCropBoxHeight;
-
- if (isSizeLimited) {
- minCropBoxWidth = num(options.minCropBoxWidth) || 0;
- minCropBoxHeight = num(options.minCropBoxHeight) || 0;
-
- // The min/maxCropBoxWidth/Height must be less than containerWidth/Height
- minCropBoxWidth = min(minCropBoxWidth, containerWidth);
- minCropBoxHeight = min(minCropBoxHeight, containerHeight);
- maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth);
- maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight);
-
- if (aspectRatio) {
- if (minCropBoxWidth && minCropBoxHeight) {
- if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
- minCropBoxHeight = minCropBoxWidth / aspectRatio;
- } else {
- minCropBoxWidth = minCropBoxHeight * aspectRatio;
- }
- } else if (minCropBoxWidth) {
- minCropBoxHeight = minCropBoxWidth / aspectRatio;
- } else if (minCropBoxHeight) {
- minCropBoxWidth = minCropBoxHeight * aspectRatio;
- }
-
- if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
- maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
- } else {
- maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
- }
- }
-
- // The minWidth/Height must be less than maxWidth/Height
- cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth);
- cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight);
- cropBox.maxWidth = maxCropBoxWidth;
- cropBox.maxHeight = maxCropBoxHeight;
- }
-
- if (isPositionLimited) {
- if (isLimited) {
- cropBox.minLeft = max(0, canvas.left);
- cropBox.minTop = max(0, canvas.top);
- cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width;
- cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height;
- } else {
- cropBox.minLeft = 0;
- cropBox.minTop = 0;
- cropBox.maxLeft = containerWidth - cropBox.width;
- cropBox.maxTop = containerHeight - cropBox.height;
- }
- }
- },
-
- renderCropBox: function () {
- var options = this.options;
- var container = this.container;
- var containerWidth = container.width;
- var containerHeight = container.height;
- var cropBox = this.cropBox;
-
- if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) {
- cropBox.left = cropBox.oldLeft;
- }
-
- if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) {
- cropBox.top = cropBox.oldTop;
- }
-
- cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
- cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
-
- this.limitCropBox(false, true);
-
- cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft);
- cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop);
-
- if (options.movable && options.cropBoxMovable) {
-
- // Turn to move the canvas when the crop box is equal to the container
- this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL);
- }
-
- this.$cropBox.css({
- width: cropBox.width,
- height: cropBox.height,
- left: cropBox.left,
- top: cropBox.top
- });
-
- if (this.isCropped && this.isLimited) {
- this.limitCanvas(true, true);
- }
-
- if (!this.isDisabled) {
- this.output();
- }
- },
-
- output: function () {
- this.preview();
-
- if (this.isCompleted) {
- this.trigger(EVENT_CROP, this.getData());
- } else if (!this.isBuilt) {
-
- // Only trigger one crop event before complete
- this.$element.one(EVENT_BUILT, $.proxy(function () {
- this.trigger(EVENT_CROP, this.getData());
- }, this));
- }
- },
-
- initPreview: function () {
- var crossOrigin = getCrossOrigin(this.crossOrigin);
- var url = crossOrigin ? this.crossOriginUrl : this.url;
- var $clone2;
-
- this.$preview = $(this.options.preview);
- this.$clone2 = $clone2 = $('<img' + crossOrigin + ' src="' + url + '">');
- this.$viewBox.html($clone2);
- this.$preview.each(function () {
- var $this = $(this);
-
- // Save the original size for recover
- $this.data(DATA_PREVIEW, {
- width: $this.width(),
- height: $this.height(),
- html: $this.html()
- });
-
- /**
- * Override img element styles
- * Add `display:block` to avoid margin top issue
- * (Occur only when margin-top <= -height)
- */
- $this.html(
- '<img' + crossOrigin + ' src="' + url + '" style="' +
- 'display:block;width:100%;height:auto;' +
- 'min-width:0!important;min-height:0!important;' +
- 'max-width:none!important;max-height:none!important;' +
- 'image-orientation:0deg!important;">'
- );
- });
- },
-
- resetPreview: function () {
- this.$preview.each(function () {
- var $this = $(this);
- var data = $this.data(DATA_PREVIEW);
-
- $this.css({
- width: data.width,
- height: data.height
- }).html(data.html).removeData(DATA_PREVIEW);
- });
- },
-
- preview: function () {
- var image = this.image;
- var canvas = this.canvas;
- var cropBox = this.cropBox;
- var cropBoxWidth = cropBox.width;
- var cropBoxHeight = cropBox.height;
- var width = image.width;
- var height = image.height;
- var left = cropBox.left - canvas.left - image.left;
- var top = cropBox.top - canvas.top - image.top;
-
- if (!this.isCropped || this.isDisabled) {
- return;
- }
-
- this.$clone2.css({
- width: width,
- height: height,
- marginLeft: -left,
- marginTop: -top,
- transform: getTransform(image)
- });
-
- this.$preview.each(function () {
- var $this = $(this);
- var data = $this.data(DATA_PREVIEW);
- var originalWidth = data.width;
- var originalHeight = data.height;
- var newWidth = originalWidth;
- var newHeight = originalHeight;
- var ratio = 1;
-
- if (cropBoxWidth) {
- ratio = originalWidth / cropBoxWidth;
- newHeight = cropBoxHeight * ratio;
- }
-
- if (cropBoxHeight && newHeight > originalHeight) {
- ratio = originalHeight / cropBoxHeight;
- newWidth = cropBoxWidth * ratio;
- newHeight = originalHeight;
- }
-
- $this.css({
- width: newWidth,
- height: newHeight
- }).find('img').css({
- width: width * ratio,
- height: height * ratio,
- marginLeft: -left * ratio,
- marginTop: -top * ratio,
- transform: getTransform(image)
- });
- });
- },
-
- bind: function () {
- var options = this.options;
- var $this = this.$element;
- var $cropper = this.$cropper;
-
- if ($.isFunction(options.cropstart)) {
- $this.on(EVENT_CROP_START, options.cropstart);
- }
-
- if ($.isFunction(options.cropmove)) {
- $this.on(EVENT_CROP_MOVE, options.cropmove);
- }
-
- if ($.isFunction(options.cropend)) {
- $this.on(EVENT_CROP_END, options.cropend);
- }
-
- if ($.isFunction(options.crop)) {
- $this.on(EVENT_CROP, options.crop);
- }
-
- if ($.isFunction(options.zoom)) {
- $this.on(EVENT_ZOOM, options.zoom);
- }
-
- $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this));
-
- if (options.zoomable && options.zoomOnWheel) {
- $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
- }
-
- if (options.toggleDragModeOnDblclick) {
- $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));
- }
-
- $document.
- on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))).
- on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this)));
-
- if (options.responsive) {
- $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
- }
- },
-
- unbind: function () {
- var options = this.options;
- var $this = this.$element;
- var $cropper = this.$cropper;
-
- if ($.isFunction(options.cropstart)) {
- $this.off(EVENT_CROP_START, options.cropstart);
- }
-
- if ($.isFunction(options.cropmove)) {
- $this.off(EVENT_CROP_MOVE, options.cropmove);
- }
-
- if ($.isFunction(options.cropend)) {
- $this.off(EVENT_CROP_END, options.cropend);
- }
-
- if ($.isFunction(options.crop)) {
- $this.off(EVENT_CROP, options.crop);
- }
-
- if ($.isFunction(options.zoom)) {
- $this.off(EVENT_ZOOM, options.zoom);
- }
-
- $cropper.off(EVENT_MOUSE_DOWN, this.cropStart);
-
- if (options.zoomable && options.zoomOnWheel) {
- $cropper.off(EVENT_WHEEL, this.wheel);
- }
-
- if (options.toggleDragModeOnDblclick) {
- $cropper.off(EVENT_DBLCLICK, this.dblclick);
- }
-
- $document.
- off(EVENT_MOUSE_MOVE, this._cropMove).
- off(EVENT_MOUSE_UP, this._cropEnd);
-
- if (options.responsive) {
- $window.off(EVENT_RESIZE, this._resize);
- }
- },
-
- resize: function () {
- var restore = this.options.restore;
- var $container = this.$container;
- var container = this.container;
- var canvasData;
- var cropBoxData;
- var ratio;
-
- // Check `container` is necessary for IE8
- if (this.isDisabled || !container) {
- return;
- }
-
- ratio = $container.width() / container.width;
-
- // Resize when width changed or height changed
- if (ratio !== 1 || $container.height() !== container.height) {
- if (restore) {
- canvasData = this.getCanvasData();
- cropBoxData = this.getCropBoxData();
- }
-
- this.render();
-
- if (restore) {
- this.setCanvasData($.each(canvasData, function (i, n) {
- canvasData[i] = n * ratio;
- }));
- this.setCropBoxData($.each(cropBoxData, function (i, n) {
- cropBoxData[i] = n * ratio;
- }));
- }
- }
- },
-
- dblclick: function () {
- if (this.isDisabled) {
- return;
- }
-
- if (this.$dragBox.hasClass(CLASS_CROP)) {
- this.setDragMode(ACTION_MOVE);
- } else {
- this.setDragMode(ACTION_CROP);
- }
- },
-
- wheel: function (event) {
- var e = event.originalEvent || event;
- var ratio = num(this.options.wheelZoomRatio) || 0.1;
- var delta = 1;
-
- if (this.isDisabled) {
- return;
- }
-
- event.preventDefault();
-
- // Limit wheel speed to prevent zoom too fast
- if (this.wheeling) {
- return;
- }
-
- this.wheeling = true;
-
- setTimeout($.proxy(function () {
- this.wheeling = false;
- }, this), 50);
-
- if (e.deltaY) {
- delta = e.deltaY > 0 ? 1 : -1;
- } else if (e.wheelDelta) {
- delta = -e.wheelDelta / 120;
- } else if (e.detail) {
- delta = e.detail > 0 ? 1 : -1;
- }
-
- this.zoom(-delta * ratio, event);
- },
-
- cropStart: function (event) {
- var options = this.options;
- var originalEvent = event.originalEvent;
- var touches = originalEvent && originalEvent.touches;
- var e = event;
- var touchesLength;
- var action;
-
- if (this.isDisabled) {
- return;
- }
-
- if (touches) {
- touchesLength = touches.length;
-
- if (touchesLength > 1) {
- if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
- e = touches[1];
- this.startX2 = e.pageX;
- this.startY2 = e.pageY;
- action = ACTION_ZOOM;
- } else {
- return;
- }
- }
-
- e = touches[0];
- }
-
- action = action || $(e.target).data(DATA_ACTION);
-
- if (REGEXP_ACTIONS.test(action)) {
- if (this.trigger(EVENT_CROP_START, {
- originalEvent: originalEvent,
- action: action
- }).isDefaultPrevented()) {
- return;
- }
-
- event.preventDefault();
-
- this.action = action;
- this.cropping = false;
-
- // IE8 has `event.pageX/Y`, but not `event.originalEvent.pageX/Y`
- // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y`
- this.startX = e.pageX || originalEvent && originalEvent.pageX;
- this.startY = e.pageY || originalEvent && originalEvent.pageY;
-
- if (action === ACTION_CROP) {
- this.cropping = true;
- this.$dragBox.addClass(CLASS_MODAL);
- }
- }
- },
-
- cropMove: function (event) {
- var options = this.options;
- var originalEvent = event.originalEvent;
- var touches = originalEvent && originalEvent.touches;
- var e = event;
- var action = this.action;
- var touchesLength;
-
- if (this.isDisabled) {
- return;
- }
-
- if (touches) {
- touchesLength = touches.length;
-
- if (touchesLength > 1) {
- if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
- e = touches[1];
- this.endX2 = e.pageX;
- this.endY2 = e.pageY;
- } else {
- return;
- }
- }
-
- e = touches[0];
- }
-
- if (action) {
- if (this.trigger(EVENT_CROP_MOVE, {
- originalEvent: originalEvent,
- action: action
- }).isDefaultPrevented()) {
- return;
- }
-
- event.preventDefault();
-
- this.endX = e.pageX || originalEvent && originalEvent.pageX;
- this.endY = e.pageY || originalEvent && originalEvent.pageY;
-
- this.change(e.shiftKey, action === ACTION_ZOOM ? event : null);
- }
- },
-
- cropEnd: function (event) {
- var originalEvent = event.originalEvent;
- var action = this.action;
-
- if (this.isDisabled) {
- return;
- }
-
- if (action) {
- event.preventDefault();
-
- if (this.cropping) {
- this.cropping = false;
- this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal);
- }
-
- this.action = '';
-
- this.trigger(EVENT_CROP_END, {
- originalEvent: originalEvent,
- action: action
- });
- }
- },
-
- change: function (shiftKey, event) {
- var options = this.options;
- var aspectRatio = options.aspectRatio;
- var action = this.action;
- var container = this.container;
- var canvas = this.canvas;
- var cropBox = this.cropBox;
- var width = cropBox.width;
- var height = cropBox.height;
- var left = cropBox.left;
- var top = cropBox.top;
- var right = left + width;
- var bottom = top + height;
- var minLeft = 0;
- var minTop = 0;
- var maxWidth = container.width;
- var maxHeight = container.height;
- var renderable = true;
- var offset;
- var range;
-
- // Locking aspect ratio in "free mode" by holding shift key (#259)
- if (!aspectRatio && shiftKey) {
- aspectRatio = width && height ? width / height : 1;
- }
-
- if (this.limited) {
- minLeft = cropBox.minLeft;
- minTop = cropBox.minTop;
- maxWidth = minLeft + min(container.width, canvas.left + canvas.width);
- maxHeight = minTop + min(container.height, canvas.top + canvas.height);
- }
-
- range = {
- x: this.endX - this.startX,
- y: this.endY - this.startY
- };
-
- if (aspectRatio) {
- range.X = range.y * aspectRatio;
- range.Y = range.x / aspectRatio;
- }
-
- switch (action) {
- // Move crop box
- case ACTION_ALL:
- left += range.x;
- top += range.y;
- break;
-
- // Resize crop box
- case ACTION_EAST:
- if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
- (top <= minTop || bottom >= maxHeight))) {
-
- renderable = false;
- break;
- }
-
- width += range.x;
-
- if (aspectRatio) {
- height = width / aspectRatio;
- top -= range.Y / 2;
- }
-
- if (width < 0) {
- action = ACTION_WEST;
- width = 0;
- }
-
- break;
-
- case ACTION_NORTH:
- if (range.y <= 0 && (top <= minTop || aspectRatio &&
- (left <= minLeft || right >= maxWidth))) {
-
- renderable = false;
- break;
- }
-
- height -= range.y;
- top += range.y;
-
- if (aspectRatio) {
- width = height * aspectRatio;
- left += range.X / 2;
- }
-
- if (height < 0) {
- action = ACTION_SOUTH;
- height = 0;
- }
-
- break;
-
- case ACTION_WEST:
- if (range.x <= 0 && (left <= minLeft || aspectRatio &&
- (top <= minTop || bottom >= maxHeight))) {
-
- renderable = false;
- break;
- }
-
- width -= range.x;
- left += range.x;
-
- if (aspectRatio) {
- height = width / aspectRatio;
- top += range.Y / 2;
- }
-
- if (width < 0) {
- action = ACTION_EAST;
- width = 0;
- }
-
- break;
-
- case ACTION_SOUTH:
- if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
- (left <= minLeft || right >= maxWidth))) {
-
- renderable = false;
- break;
- }
-
- height += range.y;
-
- if (aspectRatio) {
- width = height * aspectRatio;
- left -= range.X / 2;
- }
-
- if (height < 0) {
- action = ACTION_NORTH;
- height = 0;
- }
-
- break;
-
- case ACTION_NORTH_EAST:
- if (aspectRatio) {
- if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
- renderable = false;
- break;
- }
-
- height -= range.y;
- top += range.y;
- width = height * aspectRatio;
- } else {
- if (range.x >= 0) {
- if (right < maxWidth) {
- width += range.x;
- } else if (range.y <= 0 && top <= minTop) {
- renderable = false;
- }
- } else {
- width += range.x;
- }
-
- if (range.y <= 0) {
- if (top > minTop) {
- height -= range.y;
- top += range.y;
- }
- } else {
- height -= range.y;
- top += range.y;
- }
- }
-
- if (width < 0 && height < 0) {
- action = ACTION_SOUTH_WEST;
- height = 0;
- width = 0;
- } else if (width < 0) {
- action = ACTION_NORTH_WEST;
- width = 0;
- } else if (height < 0) {
- action = ACTION_SOUTH_EAST;
- height = 0;
- }
-
- break;
-
- case ACTION_NORTH_WEST:
- if (aspectRatio) {
- if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
- renderable = false;
- break;
- }
-
- height -= range.y;
- top += range.y;
- width = height * aspectRatio;
- left += range.X;
- } else {
- if (range.x <= 0) {
- if (left > minLeft) {
- width -= range.x;
- left += range.x;
- } else if (range.y <= 0 && top <= minTop) {
- renderable = false;
- }
- } else {
- width -= range.x;
- left += range.x;
- }
-
- if (range.y <= 0) {
- if (top > minTop) {
- height -= range.y;
- top += range.y;
- }
- } else {
- height -= range.y;
- top += range.y;
- }
- }
-
- if (width < 0 && height < 0) {
- action = ACTION_SOUTH_EAST;
- height = 0;
- width = 0;
- } else if (width < 0) {
- action = ACTION_NORTH_EAST;
- width = 0;
- } else if (height < 0) {
- action = ACTION_SOUTH_WEST;
- height = 0;
- }
-
- break;
-
- case ACTION_SOUTH_WEST:
- if (aspectRatio) {
- if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
- renderable = false;
- break;
- }
-
- width -= range.x;
- left += range.x;
- height = width / aspectRatio;
- } else {
- if (range.x <= 0) {
- if (left > minLeft) {
- width -= range.x;
- left += range.x;
- } else if (range.y >= 0 && bottom >= maxHeight) {
- renderable = false;
- }
- } else {
- width -= range.x;
- left += range.x;
- }
-
- if (range.y >= 0) {
- if (bottom < maxHeight) {
- height += range.y;
- }
- } else {
- height += range.y;
- }
- }
-
- if (width < 0 && height < 0) {
- action = ACTION_NORTH_EAST;
- height = 0;
- width = 0;
- } else if (width < 0) {
- action = ACTION_SOUTH_EAST;
- width = 0;
- } else if (height < 0) {
- action = ACTION_NORTH_WEST;
- height = 0;
- }
-
- break;
-
- case ACTION_SOUTH_EAST:
- if (aspectRatio) {
- if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
- renderable = false;
- break;
- }
-
- width += range.x;
- height = width / aspectRatio;
- } else {
- if (range.x >= 0) {
- if (right < maxWidth) {
- width += range.x;
- } else if (range.y >= 0 && bottom >= maxHeight) {
- renderable = false;
- }
- } else {
- width += range.x;
- }
-
- if (range.y >= 0) {
- if (bottom < maxHeight) {
- height += range.y;
- }
- } else {
- height += range.y;
- }
- }
-
- if (width < 0 && height < 0) {
- action = ACTION_NORTH_WEST;
- height = 0;
- width = 0;
- } else if (width < 0) {
- action = ACTION_SOUTH_WEST;
- width = 0;
- } else if (height < 0) {
- action = ACTION_NORTH_EAST;
- height = 0;
- }
-
- break;
-
- // Move canvas
- case ACTION_MOVE:
- this.move(range.x, range.y);
- renderable = false;
- break;
-
- // Zoom canvas
- case ACTION_ZOOM:
- this.zoom((function (x1, y1, x2, y2) {
- var z1 = sqrt(x1 * x1 + y1 * y1);
- var z2 = sqrt(x2 * x2 + y2 * y2);
-
- return (z2 - z1) / z1;
- })(
- abs(this.startX - this.startX2),
- abs(this.startY - this.startY2),
- abs(this.endX - this.endX2),
- abs(this.endY - this.endY2)
- ), event);
- this.startX2 = this.endX2;
- this.startY2 = this.endY2;
- renderable = false;
- break;
-
- // Create crop box
- case ACTION_CROP:
- if (!range.x || !range.y) {
- renderable = false;
- break;
- }
-
- offset = this.$cropper.offset();
- left = this.startX - offset.left;
- top = this.startY - offset.top;
- width = cropBox.minWidth;
- height = cropBox.minHeight;
-
- if (range.x > 0) {
- action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
- } else if (range.x < 0) {
- left -= width;
- action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
- }
-
- if (range.y < 0) {
- top -= height;
- }
-
- // Show the crop box if is hidden
- if (!this.isCropped) {
- this.$cropBox.removeClass(CLASS_HIDDEN);
- this.isCropped = true;
-
- if (this.limited) {
- this.limitCropBox(true, true);
- }
- }
-
- break;
-
- // No default
- }
-
- if (renderable) {
- cropBox.width = width;
- cropBox.height = height;
- cropBox.left = left;
- cropBox.top = top;
- this.action = action;
-
- this.renderCropBox();
- }
-
- // Override
- this.startX = this.endX;
- this.startY = this.endY;
- },
-
- // Show the crop box manually
- crop: function () {
- if (!this.isBuilt || this.isDisabled) {
- return;
- }
-
- if (!this.isCropped) {
- this.isCropped = true;
- this.limitCropBox(true, true);
-
- if (this.options.modal) {
- this.$dragBox.addClass(CLASS_MODAL);
- }
-
- this.$cropBox.removeClass(CLASS_HIDDEN);
- }
-
- this.setCropBoxData(this.initialCropBox);
- },
-
- // Reset the image and crop box to their initial states
- reset: function () {
- if (!this.isBuilt || this.isDisabled) {
- return;
- }
-
- this.image = $.extend({}, this.initialImage);
- this.canvas = $.extend({}, this.initialCanvas);
- this.cropBox = $.extend({}, this.initialCropBox);
-
- this.renderCanvas();
-
- if (this.isCropped) {
- this.renderCropBox();
- }
- },
-
- // Clear the crop box
- clear: function () {
- if (!this.isCropped || this.isDisabled) {
- return;
- }
-
- $.extend(this.cropBox, {
- left: 0,
- top: 0,
- width: 0,
- height: 0
- });
-
- this.isCropped = false;
- this.renderCropBox();
-
- this.limitCanvas(true, true);
-
- // Render canvas after crop box rendered
- this.renderCanvas();
-
- this.$dragBox.removeClass(CLASS_MODAL);
- this.$cropBox.addClass(CLASS_HIDDEN);
- },
-
- /**
- * Replace the image's src and rebuild the cropper
- *
- * @param {String} url
- * @param {Boolean} onlyColorChanged (optional)
- */
- replace: function (url, onlyColorChanged) {
- if (!this.isDisabled && url) {
- if (this.isImg) {
- this.$element.attr('src', url);
- }
-
- if (onlyColorChanged) {
- this.url = url;
- this.$clone.attr('src', url);
-
- if (this.isBuilt) {
- this.$preview.find('img').add(this.$clone2).attr('src', url);
- }
- } else {
- if (this.isImg) {
- this.isReplaced = true;
- }
-
- // Clear previous data
- this.options.data = null;
- this.load(url);
- }
- }
- },
-
- // Enable (unfreeze) the cropper
- enable: function () {
- if (this.isBuilt) {
- this.isDisabled = false;
- this.$cropper.removeClass(CLASS_DISABLED);
- }
- },
-
- // Disable (freeze) the cropper
- disable: function () {
- if (this.isBuilt) {
- this.isDisabled = true;
- this.$cropper.addClass(CLASS_DISABLED);
- }
- },
-
- // Destroy the cropper and remove the instance from the image
- destroy: function () {
- var $this = this.$element;
-
- if (this.isLoaded) {
- if (this.isImg && this.isReplaced) {
- $this.attr('src', this.originalUrl);
- }
-
- this.unbuild();
- $this.removeClass(CLASS_HIDDEN);
- } else {
- if (this.isImg) {
- $this.off(EVENT_LOAD, this.start);
- } else if (this.$clone) {
- this.$clone.remove();
- }
- }
-
- $this.removeData(NAMESPACE);
- },
-
- /**
- * Move the canvas with relative offsets
- *
- * @param {Number} offsetX
- * @param {Number} offsetY (optional)
- */
- move: function (offsetX, offsetY) {
- var canvas = this.canvas;
-
- this.moveTo(
- isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX),
- isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY)
- );
- },
-
- /**
- * Move the canvas to an absolute point
- *
- * @param {Number} x
- * @param {Number} y (optional)
- */
- moveTo: function (x, y) {
- var canvas = this.canvas;
- var isChanged = false;
-
- // If "y" is not present, its default value is "x"
- if (isUndefined(y)) {
- y = x;
- }
-
- x = num(x);
- y = num(y);
-
- if (this.isBuilt && !this.isDisabled && this.options.movable) {
- if (isNumber(x)) {
- canvas.left = x;
- isChanged = true;
- }
-
- if (isNumber(y)) {
- canvas.top = y;
- isChanged = true;
- }
-
- if (isChanged) {
- this.renderCanvas(true);
- }
- }
- },
-
- /**
- * Zoom the canvas with a relative ratio
- *
- * @param {Number} ratio
- * @param {jQuery Event} _event (private)
- */
- zoom: function (ratio, _event) {
- var canvas = this.canvas;
-
- ratio = num(ratio);
-
- if (ratio < 0) {
- ratio = 1 / (1 - ratio);
- } else {
- ratio = 1 + ratio;
- }
-
- this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event);
- },
-
- /**
- * Zoom the canvas to an absolute ratio
- *
- * @param {Number} ratio
- * @param {jQuery Event} _event (private)
- */
- zoomTo: function (ratio, _event) {
- var options = this.options;
- var canvas = this.canvas;
- var width = canvas.width;
- var height = canvas.height;
- var naturalWidth = canvas.naturalWidth;
- var naturalHeight = canvas.naturalHeight;
- var originalEvent;
- var newWidth;
- var newHeight;
- var offset;
- var center;
-
- ratio = num(ratio);
-
- if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) {
- newWidth = naturalWidth * ratio;
- newHeight = naturalHeight * ratio;
-
- if (_event) {
- originalEvent = _event.originalEvent;
- }
-
- if (this.trigger(EVENT_ZOOM, {
- originalEvent: originalEvent,
- oldRatio: width / naturalWidth,
- ratio: newWidth / naturalWidth
- }).isDefaultPrevented()) {
- return;
- }
-
- if (originalEvent) {
- offset = this.$cropper.offset();
- center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : {
- pageX: _event.pageX || originalEvent.pageX || 0,
- pageY: _event.pageY || originalEvent.pageY || 0
- };
-
- // Zoom from the triggering point of the event
- canvas.left -= (newWidth - width) * (
- ((center.pageX - offset.left) - canvas.left) / width
- );
- canvas.top -= (newHeight - height) * (
- ((center.pageY - offset.top) - canvas.top) / height
- );
- } else {
-
- // Zoom from the center of the canvas
- canvas.left -= (newWidth - width) / 2;
- canvas.top -= (newHeight - height) / 2;
- }
-
- canvas.width = newWidth;
- canvas.height = newHeight;
- this.renderCanvas(true);
- }
- },
-
- /**
- * Rotate the canvas with a relative degree
- *
- * @param {Number} degree
- */
- rotate: function (degree) {
- this.rotateTo((this.image.rotate || 0) + num(degree));
- },
-
- /**
- * Rotate the canvas to an absolute degree
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate()
- *
- * @param {Number} degree
- */
- rotateTo: function (degree) {
- degree = num(degree);
-
- if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) {
- this.image.rotate = degree % 360;
- this.isRotated = true;
- this.renderCanvas(true);
- }
- },
-
- /**
- * Scale the image
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale()
- *
- * @param {Number} scaleX
- * @param {Number} scaleY (optional)
- */
- scale: function (scaleX, scaleY) {
- var image = this.image;
- var isChanged = false;
-
- // If "scaleY" is not present, its default value is "scaleX"
- if (isUndefined(scaleY)) {
- scaleY = scaleX;
- }
-
- scaleX = num(scaleX);
- scaleY = num(scaleY);
-
- if (this.isBuilt && !this.isDisabled && this.options.scalable) {
- if (isNumber(scaleX)) {
- image.scaleX = scaleX;
- isChanged = true;
- }
-
- if (isNumber(scaleY)) {
- image.scaleY = scaleY;
- isChanged = true;
- }
-
- if (isChanged) {
- this.renderImage(true);
- }
- }
- },
-
- /**
- * Scale the abscissa of the image
- *
- * @param {Number} scaleX
- */
- scaleX: function (scaleX) {
- var scaleY = this.image.scaleY;
-
- this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
- },
-
- /**
- * Scale the ordinate of the image
- *
- * @param {Number} scaleY
- */
- scaleY: function (scaleY) {
- var scaleX = this.image.scaleX;
-
- this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
- },
-
- /**
- * Get the cropped area position and size data (base on the original image)
- *
- * @param {Boolean} isRounded (optional)
- * @return {Object} data
- */
- getData: function (isRounded) {
- var options = this.options;
- var image = this.image;
- var canvas = this.canvas;
- var cropBox = this.cropBox;
- var ratio;
- var data;
-
- if (this.isBuilt && this.isCropped) {
- data = {
- x: cropBox.left - canvas.left,
- y: cropBox.top - canvas.top,
- width: cropBox.width,
- height: cropBox.height
- };
-
- ratio = image.width / image.naturalWidth;
-
- $.each(data, function (i, n) {
- n = n / ratio;
- data[i] = isRounded ? round(n) : n;
- });
-
- } else {
- data = {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
- }
-
- if (options.rotatable) {
- data.rotate = image.rotate || 0;
- }
-
- if (options.scalable) {
- data.scaleX = image.scaleX || 1;
- data.scaleY = image.scaleY || 1;
- }
-
- return data;
- },
-
- /**
- * Set the cropped area position and size with new data
- *
- * @param {Object} data
- */
- setData: function (data) {
- var options = this.options;
- var image = this.image;
- var canvas = this.canvas;
- var cropBoxData = {};
- var isRotated;
- var isScaled;
- var ratio;
-
- if ($.isFunction(data)) {
- data = data.call(this.element);
- }
-
- if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
- if (options.rotatable) {
- if (isNumber(data.rotate) && data.rotate !== image.rotate) {
- image.rotate = data.rotate;
- this.isRotated = isRotated = true;
- }
- }
-
- if (options.scalable) {
- if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) {
- image.scaleX = data.scaleX;
- isScaled = true;
- }
-
- if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) {
- image.scaleY = data.scaleY;
- isScaled = true;
- }
- }
-
- if (isRotated) {
- this.renderCanvas();
- } else if (isScaled) {
- this.renderImage();
- }
-
- ratio = image.width / image.naturalWidth;
-
- if (isNumber(data.x)) {
- cropBoxData.left = data.x * ratio + canvas.left;
- }
-
- if (isNumber(data.y)) {
- cropBoxData.top = data.y * ratio + canvas.top;
- }
-
- if (isNumber(data.width)) {
- cropBoxData.width = data.width * ratio;
- }
-
- if (isNumber(data.height)) {
- cropBoxData.height = data.height * ratio;
- }
-
- this.setCropBoxData(cropBoxData);
- }
- },
-
- /**
- * Get the container size data
- *
- * @return {Object} data
- */
- getContainerData: function () {
- return this.isBuilt ? this.container : {};
- },
-
- /**
- * Get the image position and size data
- *
- * @return {Object} data
- */
- getImageData: function () {
- return this.isLoaded ? this.image : {};
- },
-
- /**
- * Get the canvas position and size data
- *
- * @return {Object} data
- */
- getCanvasData: function () {
- var canvas = this.canvas;
- var data = {};
-
- if (this.isBuilt) {
- $.each([
- 'left',
- 'top',
- 'width',
- 'height',
- 'naturalWidth',
- 'naturalHeight'
- ], function (i, n) {
- data[n] = canvas[n];
- });
- }
-
- return data;
- },
-
- /**
- * Set the canvas position and size with new data
- *
- * @param {Object} data
- */
- setCanvasData: function (data) {
- var canvas = this.canvas;
- var aspectRatio = canvas.aspectRatio;
-
- if ($.isFunction(data)) {
- data = data.call(this.$element);
- }
-
- if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
- if (isNumber(data.left)) {
- canvas.left = data.left;
- }
-
- if (isNumber(data.top)) {
- canvas.top = data.top;
- }
-
- if (isNumber(data.width)) {
- canvas.width = data.width;
- canvas.height = data.width / aspectRatio;
- } else if (isNumber(data.height)) {
- canvas.height = data.height;
- canvas.width = data.height * aspectRatio;
- }
-
- this.renderCanvas(true);
- }
- },
-
- /**
- * Get the crop box position and size data
- *
- * @return {Object} data
- */
- getCropBoxData: function () {
- var cropBox = this.cropBox;
- var data;
-
- if (this.isBuilt && this.isCropped) {
- data = {
- left: cropBox.left,
- top: cropBox.top,
- width: cropBox.width,
- height: cropBox.height
- };
- }
-
- return data || {};
- },
-
- /**
- * Set the crop box position and size with new data
- *
- * @param {Object} data
- */
- setCropBoxData: function (data) {
- var cropBox = this.cropBox;
- var aspectRatio = this.options.aspectRatio;
- var isWidthChanged;
- var isHeightChanged;
-
- if ($.isFunction(data)) {
- data = data.call(this.$element);
- }
-
- if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) {
-
- if (isNumber(data.left)) {
- cropBox.left = data.left;
- }
-
- if (isNumber(data.top)) {
- cropBox.top = data.top;
- }
-
- if (isNumber(data.width)) {
- isWidthChanged = true;
- cropBox.width = data.width;
- }
-
- if (isNumber(data.height)) {
- isHeightChanged = true;
- cropBox.height = data.height;
- }
-
- if (aspectRatio) {
- if (isWidthChanged) {
- cropBox.height = cropBox.width / aspectRatio;
- } else if (isHeightChanged) {
- cropBox.width = cropBox.height * aspectRatio;
- }
- }
-
- this.renderCropBox();
- }
- },
-
- /**
- * Get a canvas drawn the cropped image
- *
- * @param {Object} options (optional)
- * @return {HTMLCanvasElement} canvas
- */
- getCroppedCanvas: function (options) {
- var originalWidth;
- var originalHeight;
- var canvasWidth;
- var canvasHeight;
- var scaledWidth;
- var scaledHeight;
- var scaledRatio;
- var aspectRatio;
- var canvas;
- var context;
- var data;
-
- if (!this.isBuilt || !this.isCropped || !SUPPORT_CANVAS) {
- return;
- }
-
- if (!$.isPlainObject(options)) {
- options = {};
- }
-
- data = this.getData();
- originalWidth = data.width;
- originalHeight = data.height;
- aspectRatio = originalWidth / originalHeight;
-
- if ($.isPlainObject(options)) {
- scaledWidth = options.width;
- scaledHeight = options.height;
-
- if (scaledWidth) {
- scaledHeight = scaledWidth / aspectRatio;
- scaledRatio = scaledWidth / originalWidth;
- } else if (scaledHeight) {
- scaledWidth = scaledHeight * aspectRatio;
- scaledRatio = scaledHeight / originalHeight;
- }
- }
-
- // The canvas element will use `Math.floor` on a float number, so floor first
- canvasWidth = floor(scaledWidth || originalWidth);
- canvasHeight = floor(scaledHeight || originalHeight);
-
- canvas = $('<canvas>')[0];
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
- context = canvas.getContext('2d');
-
- if (options.fillColor) {
- context.fillStyle = options.fillColor;
- context.fillRect(0, 0, canvasWidth, canvasHeight);
- }
-
- // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
- context.drawImage.apply(context, (function () {
- var source = getSourceCanvas(this.$clone[0], this.image);
- var sourceWidth = source.width;
- var sourceHeight = source.height;
- var canvas = this.canvas;
- var params = [source];
-
- // Source canvas
- var srcX = data.x + canvas.naturalWidth * (abs(data.scaleX || 1) - 1) / 2;
- var srcY = data.y + canvas.naturalHeight * (abs(data.scaleY || 1) - 1) / 2;
- var srcWidth;
- var srcHeight;
-
- // Destination canvas
- var dstX;
- var dstY;
- var dstWidth;
- var dstHeight;
-
- if (srcX <= -originalWidth || srcX > sourceWidth) {
- srcX = srcWidth = dstX = dstWidth = 0;
- } else if (srcX <= 0) {
- dstX = -srcX;
- srcX = 0;
- srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX);
- } else if (srcX <= sourceWidth) {
- dstX = 0;
- srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX);
- }
-
- if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) {
- srcY = srcHeight = dstY = dstHeight = 0;
- } else if (srcY <= 0) {
- dstY = -srcY;
- srcY = 0;
- srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY);
- } else if (srcY <= sourceHeight) {
- dstY = 0;
- srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY);
- }
-
- // All the numerical parameters should be integer for `drawImage` (#476)
- params.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight));
-
- // Scale destination sizes
- if (scaledRatio) {
- dstX *= scaledRatio;
- dstY *= scaledRatio;
- dstWidth *= scaledRatio;
- dstHeight *= scaledRatio;
- }
-
- // Avoid "IndexSizeError" in IE and Firefox
- if (dstWidth > 0 && dstHeight > 0) {
- params.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
- }
-
- return params;
- }).call(this));
-
- return canvas;
- },
-
- /**
- * Change the aspect ratio of the crop box
- *
- * @param {Number} aspectRatio
- */
- setAspectRatio: function (aspectRatio) {
- var options = this.options;
-
- if (!this.isDisabled && !isUndefined(aspectRatio)) {
-
- // 0 -> NaN
- options.aspectRatio = max(0, aspectRatio) || NaN;
-
- if (this.isBuilt) {
- this.initCropBox();
-
- if (this.isCropped) {
- this.renderCropBox();
- }
- }
- }
- },
-
- /**
- * Change the drag mode
- *
- * @param {String} mode (optional)
- */
- setDragMode: function (mode) {
- var options = this.options;
- var croppable;
- var movable;
-
- if (this.isLoaded && !this.isDisabled) {
- croppable = mode === ACTION_CROP;
- movable = options.movable && mode === ACTION_MOVE;
- mode = (croppable || movable) ? mode : ACTION_NONE;
-
- this.$dragBox.
- data(DATA_ACTION, mode).
- toggleClass(CLASS_CROP, croppable).
- toggleClass(CLASS_MOVE, movable);
-
- if (!options.cropBoxMovable) {
-
- // Sync drag mode to crop box when it is not movable(#300)
- this.$face.
- data(DATA_ACTION, mode).
- toggleClass(CLASS_CROP, croppable).
- toggleClass(CLASS_MOVE, movable);
- }
- }
- }
- };
-
- Cropper.DEFAULTS = {
-
- // Define the view mode of the cropper
- viewMode: 0, // 0, 1, 2, 3
-
- // Define the dragging mode of the cropper
- dragMode: 'crop', // 'crop', 'move' or 'none'
-
- // Define the aspect ratio of the crop box
- aspectRatio: NaN,
-
- // An object with the previous cropping result data
- data: null,
-
- // A jQuery selector for adding extra containers to preview
- preview: '',
-
- // Re-render the cropper when resize the window
- responsive: true,
-
- // Restore the cropped area after resize the window
- restore: true,
-
- // Check if the current image is a cross-origin image
- checkCrossOrigin: true,
-
- // Check the current image's Exif Orientation information
- checkOrientation: true,
-
- // Show the black modal
- modal: true,
-
- // Show the dashed lines for guiding
- guides: true,
-
- // Show the center indicator for guiding
- center: true,
-
- // Show the white modal to highlight the crop box
- highlight: true,
-
- // Show the grid background
- background: true,
-
- // Enable to crop the image automatically when initialize
- autoCrop: true,
-
- // Define the percentage of automatic cropping area when initializes
- autoCropArea: 0.8,
-
- // Enable to move the image
- movable: true,
-
- // Enable to rotate the image
- rotatable: true,
-
- // Enable to scale the image
- scalable: true,
-
- // Enable to zoom the image
- zoomable: true,
-
- // Enable to zoom the image by dragging touch
- zoomOnTouch: true,
-
- // Enable to zoom the image by wheeling mouse
- zoomOnWheel: true,
-
- // Define zoom ratio when zoom the image by wheeling mouse
- wheelZoomRatio: 0.1,
-
- // Enable to move the crop box
- cropBoxMovable: true,
-
- // Enable to resize the crop box
- cropBoxResizable: true,
-
- // Toggle drag mode between "crop" and "move" when click twice on the cropper
- toggleDragModeOnDblclick: true,
-
- // Size limitation
- minCanvasWidth: 0,
- minCanvasHeight: 0,
- minCropBoxWidth: 0,
- minCropBoxHeight: 0,
- minContainerWidth: 200,
- minContainerHeight: 100,
-
- // Shortcuts of events
- build: null,
- built: null,
- cropstart: null,
- cropmove: null,
- cropend: null,
- crop: null,
- zoom: null
- };
-
- Cropper.setDefaults = function (options) {
- $.extend(Cropper.DEFAULTS, options);
- };
-
- Cropper.TEMPLATE = (
- '<div class="cropper-container">' +
- '<div class="cropper-wrap-box">' +
- '<div class="cropper-canvas"></div>' +
- '</div>' +
- '<div class="cropper-drag-box"></div>' +
- '<div class="cropper-crop-box">' +
- '<span class="cropper-view-box"></span>' +
- '<span class="cropper-dashed dashed-h"></span>' +
- '<span class="cropper-dashed dashed-v"></span>' +
- '<span class="cropper-center"></span>' +
- '<span class="cropper-face"></span>' +
- '<span class="cropper-line line-e" data-action="e"></span>' +
- '<span class="cropper-line line-n" data-action="n"></span>' +
- '<span class="cropper-line line-w" data-action="w"></span>' +
- '<span class="cropper-line line-s" data-action="s"></span>' +
- '<span class="cropper-point point-e" data-action="e"></span>' +
- '<span class="cropper-point point-n" data-action="n"></span>' +
- '<span class="cropper-point point-w" data-action="w"></span>' +
- '<span class="cropper-point point-s" data-action="s"></span>' +
- '<span class="cropper-point point-ne" data-action="ne"></span>' +
- '<span class="cropper-point point-nw" data-action="nw"></span>' +
- '<span class="cropper-point point-sw" data-action="sw"></span>' +
- '<span class="cropper-point point-se" data-action="se"></span>' +
- '</div>' +
- '</div>'
- );
-
- // Save the other cropper
- Cropper.other = $.fn.cropper;
-
- // Register as jQuery plugin
- $.fn.cropper = function (option) {
- var args = toArray(arguments, 1);
- var result;
-
- this.each(function () {
- var $this = $(this);
- var data = $this.data(NAMESPACE);
- var options;
- var fn;
-
- if (!data) {
- if (/destroy/.test(option)) {
- return;
- }
-
- options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
- $this.data(NAMESPACE, (data = new Cropper(this, options)));
- }
-
- if (typeof option === 'string' && $.isFunction(fn = data[option])) {
- result = fn.apply(data, args);
- }
- });
-
- return isUndefined(result) ? this : result;
- };
-
- $.fn.cropper.Constructor = Cropper;
- $.fn.cropper.setDefaults = Cropper.setDefaults;
-
- // No conflict
- $.fn.cropper.noConflict = function () {
- $.fn.cropper = Cropper.other;
- return this;
- };
-
-});
diff --git a/yarn.lock b/yarn.lock
index 98da6a984d1..d56d7272fe2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1409,6 +1409,12 @@ create-hmac@^1.1.0, create-hmac@^1.1.2:
create-hash "^1.1.0"
inherits "^2.0.1"
+cropper@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/cropper/-/cropper-2.3.0.tgz#607461d4e7aa7a7fe15a26834b14b7f0c2801562"
+ dependencies:
+ jquery ">= 1.9.1"
+
cryptiles@2.x.x:
version "2.0.5"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
@@ -3169,7 +3175,7 @@ jquery-ujs@^1.2.1:
dependencies:
jquery ">=1.8.0"
-jquery@>=1.8.0, jquery@^2.2.1:
+"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.1.tgz#3c3e16854ad3d2ac44ac65021b17426d22ad803f"