summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-17 21:08:29 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-17 21:08:29 +0000
commit40254b9ace2a74a3c9f1cc51a1b1d5e3e78c1ae9 (patch)
tree9b735ef933178be36d35088f3acab2d9b75dbbad /app
parent22a0d312ae82e7dda3073d5d1a5a766d7641738d (diff)
downloadgitlab-ce-40254b9ace2a74a3c9f1cc51a1b1d5e3e78c1ae9.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue37
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js28
-rw-r--r--app/assets/javascripts/projects/project_import_gitlab_project.js42
-rw-r--r--app/assets/javascripts/projects/project_new.js58
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/finders/deployments_finder.rb20
-rw-r--r--app/models/deployment.rb5
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/import_failure.rb4
9 files changed, 155 insertions, 42 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index eb4150b010d..3280ff48129 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -28,29 +28,38 @@ export default {
{
key: 'error',
label: __('Error'),
- thClass: 'w-70p',
- tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
+ thClass: 'w-60p',
+ tdClass: 'table-col d-flex d-sm-table-cell px-3',
},
{
key: 'events',
label: __('Events'),
- tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
+ thClass: 'text-right',
+ tdClass: 'table-col d-flex d-sm-table-cell',
},
{
key: 'users',
label: __('Users'),
- tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
+ thClass: 'text-right',
+ tdClass: 'table-col d-flex d-sm-table-cell',
},
{
key: 'lastSeen',
label: __('Last seen'),
- thClass: 'w-15p',
- tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
+ thClass: '',
+ tdClass: 'table-col d-flex d-sm-table-cell',
},
{
key: 'ignore',
label: '',
- tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
+ thClass: 'w-3rem',
+ tdClass: 'table-col d-flex pl-0 d-sm-table-cell',
+ },
+ {
+ key: 'resolved',
+ label: '',
+ thClass: 'w-3rem',
+ tdClass: 'table-col d-flex pl-0 d-sm-table-cell',
},
{
key: 'details',
@@ -197,9 +206,7 @@ export default {
<template>
<div class="error-list">
<div v-if="errorTrackingEnabled">
- <div
- class="row flex-column flex-sm-row align-items-sm-center row-top m-0 mt-sm-2 mx-sm-1 p-0 p-sm-3"
- >
+ <div class="row flex-column flex-sm-row align-items-sm-center row-top m-0 mt-sm-2 p-0 p-sm-3">
<div class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0">
<div class="filtered-search-box mb-0">
<gl-dropdown
@@ -333,6 +340,16 @@ export default {
<gl-icon name="eye-slash" :size="12" />
</gl-button>
</template>
+ <template v-slot:resolved="errors">
+ <gl-button
+ ref="resolveError"
+ v-gl-tooltip
+ :title="__('Resolve')"
+ @click="updateIssueStatus(errors.item.id, 'resolved')"
+ >
+ <gl-icon name="check-circle" :size="12" />
+ </gl-button>
+ </template>
<template v-slot:details="errors">
<gl-button
:href="getDetailsLink(errors.item.id)"
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 6bbf118d7d1..a03fedcd7e7 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -21,12 +21,17 @@ export const addDelimiter = text =>
export const highCountTrim = count => (count > 99 ? '99+' : count);
/**
- * Converts first char to uppercase and replaces undercores with spaces
- * @param {String} string
+ * Converts first char to uppercase and replaces the given separator with spaces
+ * @param {String} string - The string to humanize
+ * @param {String} separator - The separator used to separate words (defaults to "_")
* @requires {String}
+ * @returns {String}
*/
-export const humanize = string =>
- string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
+export const humanize = (string, separator = '_') => {
+ const replaceRegex = new RegExp(separator, 'g');
+
+ return string.charAt(0).toUpperCase() + string.replace(replaceRegex, ' ').slice(1);
+};
/**
* Replaces underscores with dashes
@@ -45,7 +50,11 @@ export const slugify = (str, separator = '-') => {
const slug = str
.trim()
.toLowerCase()
- .replace(/[^a-zA-Z0-9_.-]+/g, separator);
+ .replace(/[^a-zA-Z0-9_.-]+/g, separator)
+ // Remove any duplicate separators or separator prefixes/suffixes
+ .split(separator)
+ .filter(Boolean)
+ .join(separator);
return slug === separator ? '' : slug;
};
@@ -160,6 +169,15 @@ export const convertToSentenceCase = string => {
};
/**
+ * Converts a sentence to title case
+ * e.g. Hello world => Hello World
+ *
+ * @param {String} string
+ * @returns {String}
+ */
+export const convertToTitleCase = string => string.replace(/\b[a-z]/g, s => s.toUpperCase());
+
+/**
* Splits camelCase or PascalCase words
* e.g. HelloWorld => Hello World
*
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
index fbef3a0b059..4f222438500 100644
--- a/app/assets/javascripts/projects/project_import_gitlab_project.js
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -1,19 +1,45 @@
import $ from 'jquery';
+import { convertToTitleCase, humanize, slugify } from '../lib/utils/text_utility';
import { getParameterValues } from '../lib/utils/url_utility';
import projectNew from './project_new';
+const prepareParameters = () => {
+ const name = getParameterValues('name')[0];
+ const path = getParameterValues('path')[0];
+
+ // If the name param exists but the path doesn't then generate it from the name
+ if (name && !path) {
+ return { name, path: slugify(name) };
+ }
+
+ // If the path param exists but the name doesn't then generate it from the path
+ if (path && !name) {
+ return { name: convertToTitleCase(humanize(path, '-')), path };
+ }
+
+ return { name, path };
+};
+
export default () => {
- const pathParam = getParameterValues('path')[0];
- const nameParam = getParameterValues('name')[0];
- const $projectPath = $('.js-path-name');
+ let hasUserDefinedProjectName = false;
const $projectName = $('.js-project-name');
-
- // get the path url and append it in the input
- $projectPath.val(pathParam);
+ const $projectPath = $('.js-path-name');
+ const { name, path } = prepareParameters();
// get the project name from the URL and set it as input value
- $projectName.val(nameParam);
+ $projectName.val(name);
+
+ // get the path url and append it in the input
+ $projectPath.val(path);
// generate slug when project name changes
- $projectName.keyup(() => projectNew.onProjectNameChange($projectName, $projectPath));
+ $projectName.on('keyup', () => {
+ projectNew.onProjectNameChange($projectName, $projectPath);
+ hasUserDefinedProjectName = $projectName.val().trim().length > 0;
+ });
+
+ // generate project name from the slug if one isn't set
+ $projectPath.on('keyup', () =>
+ projectNew.onProjectPathChange($projectName, $projectPath, hasUserDefinedProjectName),
+ );
};
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 92c4c05bd87..2aa5f6ec626 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,14 +1,45 @@
import $ from 'jquery';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
-import { slugify } from '../lib/utils/text_utility';
+import { convertToTitleCase, humanize, slugify } from '../lib/utils/text_utility';
import { s__ } from '~/locale';
let hasUserDefinedProjectPath = false;
+let hasUserDefinedProjectName = false;
+
+const onProjectNameChange = ($projectNameInput, $projectPathInput) => {
+ const slug = slugify($projectNameInput.val());
+ $projectPathInput.val(slug);
+};
+
+const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingProjectName) => {
+ const slug = $projectPathInput.val();
+
+ if (!hasExistingProjectName) {
+ $projectNameInput.val(convertToTitleCase(humanize(slug, '[-_]')));
+ }
+};
+
+const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
+ $projectNameInput.off('keyup change').on('keyup change', () => {
+ onProjectNameChange($projectNameInput, $projectPathInput);
+ hasUserDefinedProjectName = $projectNameInput.val().trim().length > 0;
+ hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
+ });
+
+ $projectPathInput.off('keyup change').on('keyup change', () => {
+ onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
+ hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
+ });
+};
const deriveProjectPathFromUrl = $projectImportUrl => {
+ const $currentProjectName = $projectImportUrl
+ .parents('.toggle-import-form')
+ .find('#project_name');
const $currentProjectPath = $projectImportUrl
.parents('.toggle-import-form')
.find('#project_path');
+
if (hasUserDefinedProjectPath) {
return;
}
@@ -30,14 +61,10 @@ const deriveProjectPathFromUrl = $projectImportUrl => {
const pathMatch = /\/([^/]+)$/.exec(importUrl);
if (pathMatch) {
$currentProjectPath.val(pathMatch[1]);
+ onProjectPathChange($currentProjectName, $currentProjectPath, false);
}
};
-const onProjectNameChange = ($projectNameInput, $projectPathInput) => {
- const slug = slugify($projectNameInput.val());
- $projectPathInput.val(slug);
-};
-
const bindEvents = () => {
const $newProjectForm = $('#new_project');
const $projectImportUrl = $('#project_import_url');
@@ -202,10 +229,7 @@ const bindEvents = () => {
const $activeTabProjectName = $('.tab-pane.active #project_name');
const $activeTabProjectPath = $('.tab-pane.active #project_path');
$activeTabProjectName.focus();
- $activeTabProjectName.keyup(() => {
- onProjectNameChange($activeTabProjectName, $activeTabProjectPath);
- hasUserDefinedProjectPath = $activeTabProjectPath.val().trim().length > 0;
- });
+ setProjectNamePathHandlers($activeTabProjectName, $activeTabProjectPath);
}
$useTemplateBtn.on('change', chooseTemplate);
@@ -220,26 +244,24 @@ const bindEvents = () => {
$projectPath.val($projectPath.val().trim());
});
- $projectPath.on('keyup', () => {
- hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
- });
-
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
$('.js-import-git-toggle-button').on('click', () => {
const $projectMirror = $('#project_mirror');
$projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
+ setProjectNamePathHandlers(
+ $('.tab-pane.active #project_name'),
+ $('.tab-pane.active #project_path'),
+ );
});
- $projectName.on('keyup change', () => {
- onProjectNameChange($projectName, $projectPath);
- hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
- });
+ setProjectNamePathHandlers($projectName, $projectPath);
};
export default {
bindEvents,
deriveProjectPathFromUrl,
onProjectNameChange,
+ onProjectPathChange,
};
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 7521a6491af..dc119b52f4e 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -461,6 +461,7 @@ img.emoji {
.w-3rem { width: 3rem; }
.w-15p { width: 15%; }
.w-30p { width: 30%; }
+.w-60p { width: 60%; }
.w-70p { width: 70%; }
.h-12em { height: 12em; }
diff --git a/app/finders/deployments_finder.rb b/app/finders/deployments_finder.rb
index b718b55dd68..0bb8ce6b4da 100644
--- a/app/finders/deployments_finder.rb
+++ b/app/finders/deployments_finder.rb
@@ -17,6 +17,8 @@ class DeploymentsFinder
def execute
items = init_collection
items = by_updated_at(items)
+ items = by_environment(items)
+ items = by_status(items)
sort(items)
end
@@ -58,6 +60,24 @@ class DeploymentsFinder
items
end
+ def by_environment(items)
+ if params[:environment].present?
+ items.for_environment_name(params[:environment])
+ else
+ items
+ end
+ end
+
+ def by_status(items)
+ return items unless params[:status].present?
+
+ unless Deployment.statuses.key?(params[:status])
+ raise ArgumentError, "The deployment status #{params[:status]} is invalid"
+ end
+
+ items.for_status(params[:status])
+ end
+
def sort_params
order_by = ALLOWED_SORT_VALUES.include?(params[:order_by]) ? params[:order_by] : DEFAULT_SORT_VALUE
order_direction = ALLOWED_SORT_DIRECTIONS.include?(params[:sort]) ? params[:sort] : DEFAULT_SORT_DIRECTION
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 74cc7f93580..e0daf692665 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -30,6 +30,11 @@ class Deployment < ApplicationRecord
delegate :name, to: :environment, prefix: true
scope :for_environment, -> (environment) { where(environment_id: environment) }
+ scope :for_environment_name, -> (name) do
+ joins(:environment).where(environments: { name: name })
+ end
+
+ scope :for_status, -> (status) { where(status: status) }
scope :visible, -> { where(status: %i[running success failed canceled]) }
diff --git a/app/models/group.rb b/app/models/group.rb
index 51b4fe4c1ce..b642b177df1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -57,6 +57,8 @@ class Group < Namespace
has_one :import_export_upload
+ has_many :import_failures, inverse_of: :group
+
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
diff --git a/app/models/import_failure.rb b/app/models/import_failure.rb
index 998572853d3..a1e03218640 100644
--- a/app/models/import_failure.rb
+++ b/app/models/import_failure.rb
@@ -2,6 +2,8 @@
class ImportFailure < ApplicationRecord
belongs_to :project
+ belongs_to :group
- validates :project, presence: true
+ validates :project, presence: true, unless: :group
+ validates :group, presence: true, unless: :project
end