diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-17 21:08:29 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-17 21:08:29 +0000 |
commit | 40254b9ace2a74a3c9f1cc51a1b1d5e3e78c1ae9 (patch) | |
tree | 9b735ef933178be36d35088f3acab2d9b75dbbad /app | |
parent | 22a0d312ae82e7dda3073d5d1a5a766d7641738d (diff) | |
download | gitlab-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.vue | 37 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/text_utility.js | 28 | ||||
-rw-r--r-- | app/assets/javascripts/projects/project_import_gitlab_project.js | 42 | ||||
-rw-r--r-- | app/assets/javascripts/projects/project_new.js | 58 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/common.scss | 1 | ||||
-rw-r--r-- | app/finders/deployments_finder.rb | 20 | ||||
-rw-r--r-- | app/models/deployment.rb | 5 | ||||
-rw-r--r-- | app/models/group.rb | 2 | ||||
-rw-r--r-- | app/models/import_failure.rb | 4 |
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 |