diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-16 18:12:52 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-16 18:12:52 +0000 |
commit | 8a9790b0db723db32f8dff511ee032e5e8e3b583 (patch) | |
tree | 8173501b91ea0ada6a68d656786867b2abcc97f9 /app | |
parent | 7212129029f4e7e68614066cc43802faba42c554 (diff) | |
download | gitlab-ce-8a9790b0db723db32f8dff511ee032e5e8e3b583.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
47 files changed, 380 insertions, 63 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue b/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue new file mode 100644 index 00000000000..83bad9eb518 --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue @@ -0,0 +1,101 @@ +<script> +import createFlash from '~/flash'; +import getAdminVariables from '../graphql/queries/variables.query.graphql'; +import { + ADD_MUTATION_ACTION, + DELETE_MUTATION_ACTION, + UPDATE_MUTATION_ACTION, + genericMutationErrorText, + variableFetchErrorText, +} from '../constants'; +import addAdminVariable from '../graphql/mutations/admin_add_variable.mutation.graphql'; +import deleteAdminVariable from '../graphql/mutations/admin_delete_variable.mutation.graphql'; +import updateAdminVariable from '../graphql/mutations/admin_update_variable.mutation.graphql'; +import ciVariableSettings from './ci_variable_settings.vue'; + +export default { + components: { + ciVariableSettings, + }, + inject: ['endpoint'], + data() { + return { + adminVariables: [], + isInitialLoading: true, + }; + }, + apollo: { + adminVariables: { + query: getAdminVariables, + update(data) { + return data?.ciVariables?.nodes || []; + }, + error() { + createFlash({ message: variableFetchErrorText }); + }, + watchLoading(flag) { + if (!flag) { + this.isInitialLoading = false; + } + }, + }, + }, + computed: { + isLoading() { + return this.$apollo.queries.adminVariables.loading && this.isInitialLoading; + }, + }, + methods: { + addVariable(variable) { + this.variableMutation(ADD_MUTATION_ACTION, variable); + }, + deleteVariable(variable) { + this.variableMutation(DELETE_MUTATION_ACTION, variable); + }, + updateVariable(variable) { + this.variableMutation(UPDATE_MUTATION_ACTION, variable); + }, + async variableMutation(mutationAction, variable) { + try { + const currentMutation = this.$options.mutationData[mutationAction]; + const { data } = await this.$apollo.mutate({ + mutation: currentMutation.action, + variables: { + endpoint: this.endpoint, + variable, + }, + }); + + const { errors } = data[currentMutation.name]; + + if (errors.length > 0) { + createFlash({ message: errors[0] }); + } else { + // The writing to cache for admin variable is not working + // because there is no ID in the cache at the top level. + // We therefore need to manually refetch. + this.$apollo.queries.adminVariables.refetch(); + } + } catch { + createFlash({ message: genericMutationErrorText }); + } + }, + }, + mutationData: { + [ADD_MUTATION_ACTION]: { action: addAdminVariable, name: 'addAdminVariable' }, + [UPDATE_MUTATION_ACTION]: { action: updateAdminVariable, name: 'updateAdminVariable' }, + [DELETE_MUTATION_ACTION]: { action: deleteAdminVariable, name: 'deleteAdminVariable' }, + }, +}; +</script> + +<template> + <ci-variable-settings + :are-scoped-variables-available="false" + :is-loading="isLoading" + :variables="adminVariables" + @add-variable="addVariable" + @delete-variable="deleteVariable" + @update-variable="updateVariable" + /> +</template> diff --git a/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue b/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue index 8ee7132bb25..c9002edc1ab 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue @@ -33,9 +33,9 @@ export default { }, filteredEnvironments() { const lowerCasedSearchTerm = this.searchTerm.toLowerCase(); - return this.environments.filter((resultString) => - resultString.toLowerCase().includes(lowerCasedSearchTerm), - ); + return this.environments.filter((environment) => { + return environment.toLowerCase().includes(lowerCasedSearchTerm); + }); }, shouldRenderCreateButton() { return this.searchTerm && !this.environments.includes(this.searchTerm); diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue index dc57f3fe4ce..5ba63de8c96 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue @@ -33,7 +33,7 @@ import { VARIABLE_ACTIONS, variableOptions, } from '../constants'; - +import { createJoinedEnvironments } from '../utils'; import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens'; @@ -98,9 +98,15 @@ export default { required: false, default: () => {}, }, + variables: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { + newEnvironments: [], isTipDismissed: getCookie(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true', typeOptions: variableOptions, validationErrorEventProperty: '', @@ -128,6 +134,9 @@ export default { isTipVisible() { return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key); }, + joinedEnvironments() { + return createJoinedEnvironments(this.variables, this.environments, this.newEnvironments); + }, maskedFeedback() { return this.displayMaskedError ? __('This variable can not be masked.') : ''; }, @@ -176,7 +185,7 @@ export default { this.$emit('add-variable', this.variable); }, createEnvironmentScope(env) { - this.$emit('create-environment-scope', env); + this.newEnvironments.push(env); }, deleteVariable() { this.$emit('delete-variable', this.variable); @@ -314,7 +323,7 @@ export default { v-if="areScopedVariablesAvailable" class="gl-w-full" :selected-environment-scope="variable.environmentScope" - :environments="environments" + :environments="joinedEnvironments" @select-environment="setEnvironmentScope" @create-environment-scope="createEnvironmentScope" /> diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue index 29578c6f710..81e3a983ea3 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue @@ -1,6 +1,5 @@ <script> import { ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION, VARIABLE_ACTIONS } from '../constants'; -import { createJoinedEnvironments } from '../utils'; import CiVariableTable from './ci_variable_table.vue'; import CiVariableModal from './ci_variable_modal.vue'; @@ -17,7 +16,8 @@ export default { }, environments: { type: Array, - required: true, + required: false, + default: () => [], }, isLoading: { type: Boolean, @@ -36,9 +36,6 @@ export default { }; }, computed: { - joinedEnvironments() { - return createJoinedEnvironments(this.variables, this.environments); - }, showModal() { return VARIABLE_ACTIONS.includes(this.mode); }, @@ -80,7 +77,8 @@ export default { <ci-variable-modal v-if="showModal" :are-scoped-variables-available="areScopedVariablesAvailable" - :environments="joinedEnvironments" + :environments="environments" + :variables="variables" :mode="mode" :selected-variable="selectedVariable" @add-variable="addVariable" diff --git a/app/assets/javascripts/ci_variable_list/constants.js b/app/assets/javascripts/ci_variable_list/constants.js index e42a728a44e..5d22974ffbb 100644 --- a/app/assets/javascripts/ci_variable_list/constants.js +++ b/app/assets/javascripts/ci_variable_list/constants.js @@ -47,6 +47,13 @@ export const defaultVariableState = { variableType: types.variableType, }; +// eslint-disable-next-line @gitlab/require-i18n-strings +export const groupString = 'Group'; +// eslint-disable-next-line @gitlab/require-i18n-strings +export const instanceString = 'Instance'; +// eslint-disable-next-line @gitlab/require-i18n-strings +export const projectString = 'Instance'; + export const AWS_TIP_DISMISSED_COOKIE_NAME = 'ci_variable_list_constants_aws_tip_dismissed'; export const AWS_TIP_MESSAGE = __( '%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}.', diff --git a/app/assets/javascripts/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql b/app/assets/javascripts/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql new file mode 100644 index 00000000000..a28ca4eebc9 --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql @@ -0,0 +1,7 @@ +fragment BaseCiVariable on CiVariable { + __typename + id + key + value + variableType +} diff --git a/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_add_variable.mutation.graphql b/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_add_variable.mutation.graphql new file mode 100644 index 00000000000..eba4b0c32f8 --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_add_variable.mutation.graphql @@ -0,0 +1,16 @@ +#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql" + +mutation addAdminVariable($variable: CiVariable!, $endpoint: String!) { + addAdminVariable(variable: $variable, endpoint: $endpoint) @client { + ciVariables { + nodes { + ...BaseCiVariable + ... on CiInstanceVariable { + protected + masked + } + } + } + errors + } +} diff --git a/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_delete_variable.mutation.graphql b/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_delete_variable.mutation.graphql new file mode 100644 index 00000000000..96eb8c794bc --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_delete_variable.mutation.graphql @@ -0,0 +1,16 @@ +#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql" + +mutation deleteAdminVariable($variable: CiVariable!, $endpoint: String!) { + deleteAdminVariable(variable: $variable, endpoint: $endpoint) @client { + ciVariables { + nodes { + ...BaseCiVariable + ... on CiInstanceVariable { + protected + masked + } + } + } + errors + } +} diff --git a/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_update_variable.mutation.graphql b/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_update_variable.mutation.graphql new file mode 100644 index 00000000000..c0388507bb8 --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/graphql/mutations/admin_update_variable.mutation.graphql @@ -0,0 +1,16 @@ +#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql" + +mutation updateAdminVariable($variable: CiVariable!, $endpoint: String!) { + updateAdminVariable(variable: $variable, endpoint: $endpoint) @client { + ciVariables { + nodes { + ...BaseCiVariable + ... on CiInstanceVariable { + protected + masked + } + } + } + errors + } +} diff --git a/app/assets/javascripts/ci_variable_list/graphql/queries/variables.query.graphql b/app/assets/javascripts/ci_variable_list/graphql/queries/variables.query.graphql new file mode 100644 index 00000000000..95056842b49 --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/graphql/queries/variables.query.graphql @@ -0,0 +1,13 @@ +#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql" + +query getVariables { + ciVariables { + nodes { + ...BaseCiVariable + ... on CiInstanceVariable { + masked + protected + } + } + } +} diff --git a/app/assets/javascripts/ci_variable_list/graphql/resolvers.js b/app/assets/javascripts/ci_variable_list/graphql/resolvers.js new file mode 100644 index 00000000000..7b57e97a4b8 --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/graphql/resolvers.js @@ -0,0 +1,67 @@ +import axios from 'axios'; +import { + convertObjectPropsToCamelCase, + convertObjectPropsToSnakeCase, +} from '../../lib/utils/common_utils'; +import { getIdFromGraphQLId } from '../../graphql_shared/utils'; +import { instanceString } from '../constants'; +import getAdminVariables from './queries/variables.query.graphql'; + +const prepareVariableForApi = ({ variable, destroy = false }) => { + return { + ...convertObjectPropsToSnakeCase(variable), + id: getIdFromGraphQLId(variable?.id), + variable_type: variable.variableType.toLowerCase(), + secret_value: variable.value, + _destroy: destroy, + }; +}; + +const mapVariableTypes = (variables = [], kind) => { + return variables.map((ciVar) => { + return { + __typename: `Ci${kind}Variable`, + ...convertObjectPropsToCamelCase(ciVar), + variableType: ciVar.variable_type ? ciVar.variable_type.toUpperCase() : ciVar.variableType, + }; + }); +}; + +const prepareAdminGraphQLResponse = ({ data, errors = [] }) => { + return { + errors, + ciVariables: { + __typename: `Ci${instanceString}VariableConnection`, + nodes: mapVariableTypes(data.variables, instanceString), + }, + }; +}; + +const callAdminEndpoint = async ({ endpoint, variable, cache, destroy = false }) => { + try { + const { data } = await axios.patch(endpoint, { + variables_attributes: [prepareVariableForApi({ variable, destroy })], + }); + + return prepareAdminGraphQLResponse({ data }); + } catch (e) { + return prepareAdminGraphQLResponse({ + data: cache.readQuery({ query: getAdminVariables }), + errors: [...e.response.data], + }); + } +}; + +export const resolvers = { + Mutation: { + addAdminVariable: async (_, { endpoint, variable }, { cache }) => { + return callAdminEndpoint({ endpoint, variable, cache }); + }, + updateAdminVariable: async (_, { endpoint, variable }, { cache }) => { + return callAdminEndpoint({ endpoint, variable, cache }); + }, + deleteAdminVariable: async (_, { endpoint, variable }, { cache }) => { + return callAdminEndpoint({ endpoint, variable, cache, destroy: true }); + }, + }, +}; diff --git a/app/assets/javascripts/ci_variable_list/index.js b/app/assets/javascripts/ci_variable_list/index.js index 2b54af6a2a4..713a453561e 100644 --- a/app/assets/javascripts/ci_variable_list/index.js +++ b/app/assets/javascripts/ci_variable_list/index.js @@ -2,8 +2,9 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import { parseBoolean } from '~/lib/utils/common_utils'; -import CiVariableSettings from './components/ci_variable_settings.vue'; +import CiAdminVariables from './components/ci_admin_variables.vue'; import LegacyCiVariableSettings from './components/legacy_ci_variable_settings.vue'; +import { resolvers } from './graphql/resolvers'; import createStore from './store'; const mountCiVariableListApp = (containerEl) => { @@ -13,8 +14,12 @@ const mountCiVariableListApp = (containerEl) => { awsTipDeployLink, awsTipLearnLink, containsVariableReferenceLink, + endpoint, environmentScopeLink, - group, + groupId, + groupPath, + isGroup, + isProject, maskedEnvironmentVariablesLink, maskableRegex, projectFullPath, @@ -23,13 +28,16 @@ const mountCiVariableListApp = (containerEl) => { protectedEnvironmentVariablesLink, } = containerEl.dataset; - const isGroup = parseBoolean(group); + const parsedIsProject = parseBoolean(isProject); + const parsedIsGroup = parseBoolean(isGroup); const isProtectedByDefault = parseBoolean(protectedByDefault); + const component = CiAdminVariables; + Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient(resolvers), }); return new Vue({ @@ -41,8 +49,12 @@ const mountCiVariableListApp = (containerEl) => { awsTipDeployLink, awsTipLearnLink, containsVariableReferenceLink, + endpoint, environmentScopeLink, - isGroup, + groupId, + groupPath, + isGroup: parsedIsGroup, + isProject: parsedIsProject, isProtectedByDefault, maskedEnvironmentVariablesLink, maskableRegex, @@ -51,7 +63,7 @@ const mountCiVariableListApp = (containerEl) => { protectedEnvironmentVariablesLink, }, render(createElement) { - return createElement(CiVariableSettings); + return createElement(component); }, }); }; diff --git a/app/assets/javascripts/ci_variable_list/utils.js b/app/assets/javascripts/ci_variable_list/utils.js index 009e56469d1..1faa97a5f73 100644 --- a/app/assets/javascripts/ci_variable_list/utils.js +++ b/app/assets/javascripts/ci_variable_list/utils.js @@ -2,20 +2,25 @@ import { uniq } from 'lodash'; import { allEnvironments } from './constants'; /** - * This function takes aa list of variable and environments + * This function takes a list of variable, environments and + * new environments added through the scope dropdown * and create a new Array that concatenate the environment list * with the environment scopes find in the variable list. This is * useful for variable settings so that we can render a list of all - * environment scopes available based on both the list of envs and what - * is found under each variable. + * environment scopes available based on the list of envs, the ones the user + * added explictly and what is found under each variable. * @param {Array} variables * @param {Array} environments * @returns {Array} - Array of environments */ -export const createJoinedEnvironments = (variables = [], environments = []) => { +export const createJoinedEnvironments = ( + variables = [], + environments = [], + newEnvironments = [], +) => { const scopesFromVariables = variables.map((variable) => variable.environmentScope); - return uniq(environments.concat(scopesFromVariables)).sort(); + return uniq([...environments, ...newEnvironments, ...scopesFromVariables]).sort(); }; /** diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js index 7c424088c8b..9cea89f4990 100644 --- a/app/assets/javascripts/persistent_user_callout.js +++ b/app/assets/javascripts/persistent_user_callout.js @@ -7,11 +7,12 @@ const DEFERRED_LINK_CLASS = 'deferred-link'; export default class PersistentUserCallout { constructor(container, options = container.dataset) { - const { dismissEndpoint, featureId, groupId, deferLinks } = options; + const { dismissEndpoint, featureId, groupId, namespaceId, deferLinks } = options; this.container = container; this.dismissEndpoint = dismissEndpoint; this.featureId = featureId; this.groupId = groupId; + this.namespaceId = namespaceId; this.deferLinks = parseBoolean(deferLinks); this.closeButtons = this.container.querySelectorAll('.js-close'); @@ -56,6 +57,7 @@ export default class PersistentUserCallout { .post(this.dismissEndpoint, { feature_name: this.featureId, group_id: this.groupId, + namespace_id: this.namespaceId, }) .then(() => { this.container.remove(); diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index e05e87ffd89..6f21b123eb0 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -13,6 +13,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController before_action :disable_query_limiting, only: [:usage_data] + before_action do + push_frontend_feature_flag(:ci_variable_settings_graphql) + end + feature_category :not_owned, [ # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned :general, :reporting, :metrics_and_profiling, :network, :preferences, :update, :reset_health_check_token diff --git a/app/controllers/users/namespace_callouts_controller.rb b/app/controllers/users/namespace_callouts_controller.rb new file mode 100644 index 00000000000..d4876382dfe --- /dev/null +++ b/app/controllers/users/namespace_callouts_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Users + class NamespaceCalloutsController < Users::CalloutsController + private + + def callout + Users::DismissNamespaceCalloutService.new( + container: nil, current_user: current_user, params: callout_params + ).execute + end + + def callout_params + params.permit(:namespace_id).merge(feature_name: feature_name) + end + end +end diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb index c1c28c569d1..68b55386daa 100644 --- a/app/graphql/types/work_item_type.rb +++ b/app/graphql/types/work_item_type.rb @@ -6,8 +6,12 @@ module Types authorize :read_work_item + field :closed_at, Types::TimeType, null: true, + description: 'Timestamp of when the work item was closed.' field :confidential, GraphQL::Types::Boolean, null: false, description: 'Indicates the work item is confidential.' + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of when the work item was created.' field :description, GraphQL::Types::String, null: true, description: 'Description of the work item.' field :id, Types::GlobalIDType[::WorkItem], null: false, @@ -22,6 +26,8 @@ module Types description: 'State of the work item.' field :title, GraphQL::Types::String, null: false, description: 'Title of the work item.' + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of when the work item was last updated.' field :widgets, [Types::WorkItems::WidgetInterface], null: true, diff --git a/app/models/event.rb b/app/models/event.rb index 5349ad029ec..a20ca0dc423 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -216,6 +216,10 @@ class Event < ApplicationRecord target_type == 'DesignManagement::Design' end + def work_item? + target_type == 'WorkItem' + end + def milestone target if milestone? end @@ -399,7 +403,8 @@ class Event < ApplicationRecord read_milestone: %i[milestone?], read_wiki: %i[wiki_page?], read_design: %i[design_note? design?], - read_note: %i[note?] + read_note: %i[note?], + read_work_item: %i[work_item?] } end diff --git a/app/services/users/dismiss_namespace_callout_service.rb b/app/services/users/dismiss_namespace_callout_service.rb new file mode 100644 index 00000000000..51261a93e20 --- /dev/null +++ b/app/services/users/dismiss_namespace_callout_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Users + class DismissNamespaceCalloutService < DismissCalloutService + private + + def callout + current_user.find_or_initialize_namespace_callout(params[:feature_name], params[:namespace_id]) + end + end +end diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index 9ef2599a2a6..02c468cebd7 100644 --- a/app/views/ci/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -6,10 +6,15 @@ = s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } - is_group = !@group.nil? +- is_project = !@project.nil? #js-ci-project-variables{ data: { endpoint: save_endpoint, + is_project: is_project.to_s, project_id: @project&.id || '', - group: is_group.to_s, + project_full_path: @project&.full_path || '', + is_group: is_group.to_s, + group_id: @group&.id || '', + group_path: @group&.full_path, maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s, aws_logo_svg_path: image_path('aws_logo.svg'), diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index f0f1413831a..813c1cdbfe4 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -3,7 +3,7 @@ - if current_user.can_create_group? .page-title-controls - = link_to _("New group"), new_group_path, class: "gl-button btn btn-confirm", data: { testid: "new-group-button" } + = link_to _("New group"), new_group_path, class: "gl-button btn btn-confirm", data: { qa_selector: "new_group_button", testid: "new-group-button" } .top-area = gl_tabs_nav({ class: 'gl-flex-grow-1 gl-border-0' }) do diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 820d5ccb4f9..1400ac9ca72 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -4,7 +4,7 @@ .devise-errors = render "devise/shared/error_messages", resource: resource .form-group.gl-px-5.gl-pt-5 - = f.label :email, class: "gl-mb-1" if Feature.enabled?(:restyle_login_page, @project) + = f.label :email, class: ("gl-mb-1" if Feature.enabled?(:restyle_login_page)) = f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.') .form-text.text-muted = _('Requires your primary GitLab email address.') diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index 3b117022d1e..88352ea351c 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -32,7 +32,7 @@ = expanded ? _('Collapse') : _('Expand') %p = _("Runners are processes that pick up and execute CI/CD jobs for GitLab.") - = link_to s_('How do I configure runners?'), help_page_path('ci/runners/index'), target: '_blank', rel: 'noopener noreferrer' + = link_to s_('What is GitLab Runner?'), 'https://docs.gitlab.com/runner/', target: '_blank', rel: 'noopener noreferrer' .settings-content = render 'groups/runners/settings' diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 09f9ca60b3e..dd9cc296d52 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -41,7 +41,7 @@ = expanded ? _('Collapse') : _('Expand') %p = _("Runners are processes that pick up and execute CI/CD jobs for GitLab.") - = link_to s_('How do I configure runners?'), help_page_path('ci/runners/index'), target: '_blank', rel: 'noopener noreferrer' + = link_to s_('What is GitLab Runner?'), 'https://docs.gitlab.com/runner/', target: '_blank', rel: 'noopener noreferrer' .settings-content = render 'projects/runners/settings' diff --git a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml index 1f37e33a037..ac7d56520f7 100644 --- a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml +++ b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml @@ -1,5 +1,5 @@ - if show_auto_devops_implicitly_enabled_banner?(project, current_user) - = render Pajamas::AlertComponent.new(alert_options: { class: 'qa-auto-devops-banner auto-devops-implicitly-enabled-banner' }, + = render Pajamas::AlertComponent.new(alert_options: { class: 'auto-devops-implicitly-enabled-banner', data: { qa_selector: 'auto_devops_banner_content' } }, close_button_options: { class: 'hide-auto-devops-implicitly-enabled-banner', data: { project_id: project.id }}) do |c| = c.body do diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 80b50f7a3de..6b502ee928e 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -5,7 +5,7 @@ %span.js-clone-dropdown-label = enabled_protocol_button(container, enabled_protocol) - else - %a#clone-dropdown.input-group-text.gl-button.btn.btn-default.btn-icon.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } + %a#clone-dropdown.input-group-text.gl-button.btn.btn-default.btn-icon.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown', qa_selector: 'clone_dropdown' } } %span.js-clone-dropdown-label = default_clone_protocol.upcase = sprite_icon('chevron-down', css_class: 'gl-icon') diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 821f1ede422..0bd5d1795d0 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,6 +1,6 @@ - if any_projects?(@projects) .dropdown.b-dropdown.gl-new-dropdown.btn-group.project-item-select-holder{ class: 'gl-display-inline-flex!' } - %a.btn.gl-button.btn-confirm.split-content-button.js-new-project-item-link.block-truncated.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } } + %a.btn.gl-button.btn-confirm.split-content-button.js-new-project-item-link.block-truncated{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } } = gl_loading_icon(inline: true, color: 'light') = project_select_tag :project_path, class: "project-item-select gl-absolute! gl-visibility-hidden", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path], with_shared: local_assigns[:with_shared], include_projects_in_subgroups: local_assigns[:include_projects_in_subgroups] }, with_feature_enabled: local_assigns[:with_feature_enabled] - %button.btn.dropdown-toggle.btn-confirm.btn-md.gl-button.gl-dropdown-toggle.dropdown-toggle-split.new-project-item-select-button.qa-new-project-item-select-button{ 'aria-label': _('Toggle project select') } + %button.btn.dropdown-toggle.btn-confirm.btn-md.gl-button.gl-dropdown-toggle.dropdown-toggle-split.new-project-item-select-button{ 'aria-label': _('Toggle project select') } diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index 74e0a088656..20bf2141cc3 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -13,8 +13,8 @@ - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" } - .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-right" if local_assigns[:align_right]) } + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true, qa_selector: "branches_dropdown", testid: "branches-select" }, { toggle_class: "js-project-refs-dropdown" } + .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-right" if local_assigns[:align_right]), data: { qa_selector: "branches_dropdown_content" } } .dropdown-page-one = dropdown_title _("Switch branch/tag") = dropdown_filter _("Search branches and tags") diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml index f3942aa5dc2..770d335a88b 100644 --- a/app/views/shared/_remote_mirror_update_button.html.haml +++ b/app/views/shared/_remote_mirror_update_button.html.haml @@ -3,5 +3,5 @@ button_options: { class: 'disabled', title: _('Updating'), data: { toggle: 'tooltip', container: 'body', qa_selector: 'updating_button' } }, icon_classes: 'spin') - elsif remote_mirror.enabled? - = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn btn-icon gl-button qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do + = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn btn-icon gl-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body', qa_selector: 'update_now_button' }, title: _('Update now') do = sprite_icon("retry") diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml index 0a74e47fa4c..4cdf1340d64 100644 --- a/app/views/shared/_sidebar_toggle_button.html.haml +++ b/app/views/shared/_sidebar_toggle_button.html.haml @@ -1,4 +1,4 @@ -%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar.rspec-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" } +%a.toggle-sidebar-button.js-toggle-sidebar.rspec-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" } = sprite_icon('chevron-double-lg-left', size: 12, css_class: 'icon-chevron-double-lg-left') %span.collapse-text.gl-ml-3= _("Collapse sidebar") diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml index 5ca9cf8d9a4..53c6800f93d 100644 --- a/app/views/shared/access_tokens/_table.html.haml +++ b/app/views/shared/access_tokens/_table.html.haml @@ -45,7 +45,7 @@ %span.token-never-expires-label= _('Never') - if resource %td= resource.member(token.user).human_access - %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right qa-revoke-button #{'btn-danger-secondary' unless token.expires?}", aria: { label: _('Revoke') }, data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type }, 'confirm-btn-variant': 'danger' } + %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right #{'btn-danger-secondary' unless token.expires?}", aria: { label: _('Revoke') }, data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type }, 'confirm-btn-variant': 'danger', qa_selector: 'revoke_button' } - else .settings-message.text-center = no_active_tokens_message diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml index 4e5e04ba4d4..e96fcd11cef 100644 --- a/app/views/shared/empty_states/_labels.html.haml +++ b/app/views/shared/empty_states/_labels.html.haml @@ -1,6 +1,6 @@ .row.empty-state.labels .col-12 - .svg-content.qa-label-svg + .svg-content{ data: { qa_selector: 'label_svg_content' } } = image_tag 'illustrations/labels.svg' .col-12 .text-content @@ -8,7 +8,7 @@ %p= _("You can also star a label to make it a priority label.") .text-center - if can?(current_user, :admin_label, @project) - = link_to _('New label'), new_project_label_path(@project), class: 'btn gl-button btn-confirm qa-label-create-new', title: _('New label'), id: 'new_label_link' + = link_to _('New label'), new_project_label_path(@project), class: 'btn gl-button btn-confirm', title: _('New label'), id: 'new_label_link' = link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn gl-button btn-confirm-secondary', title: _('Generate a default set of labels'), id: 'generate_labels_link' - if can?(current_user, :admin_label, @group) = link_to _('New label'), new_group_label_path(@group), class: 'btn gl-button btn-confirm', title: _('New label'), id: 'new_label_link' diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml index a93f6e4c795..3381c5f0c67 100644 --- a/app/views/shared/empty_states/_priority_labels.html.haml +++ b/app/views/shared/empty_states/_priority_labels.html.haml @@ -1,5 +1,5 @@ .text-center - .svg-content.qa-label-svg + .svg-content{ data: { qa_selector: 'label_svg_content' } } = image_tag 'illustrations/priority_labels.svg' - if can?(current_user, :admin_label, @project) %p diff --git a/app/views/shared/empty_states/_topics.html.haml b/app/views/shared/empty_states/_topics.html.haml index fd82a853037..0283e852c7d 100644 --- a/app/views/shared/empty_states/_topics.html.haml +++ b/app/views/shared/empty_states/_topics.html.haml @@ -1,7 +1,7 @@ .row.empty-state .col-12 .svg-content - = image_tag 'illustrations/labels.svg', data: { qa_selector: 'svg_content' } + = image_tag 'illustrations/labels.svg' .text-content.gl-text-center.gl-pt-0! %h4= _('There are no topics to show.') %p= _('Add topics to projects to help users find them.') diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml index 552b100d5dd..8304a2f18a0 100644 --- a/app/views/shared/empty_states/_wikis.html.haml +++ b/app/views/shared/empty_states/_wikis.html.haml @@ -3,7 +3,7 @@ - if can?(current_user, :create_wiki, @wiki.container) - create_path = wiki_page_path(@wiki, params[:id], view: 'create') - - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn gl-button btn-confirm qa-create-first-page-link', title: s_('WikiEmpty|Create your first page') + - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn gl-button btn-confirm', title: s_('WikiEmpty|Create your first page'), data: { qa_selector: 'create_first_page_link' } = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do %h4.text-left diff --git a/app/views/shared/empty_states/_wikis_layout.html.haml b/app/views/shared/empty_states/_wikis_layout.html.haml index 3b100f832b2..0b7034838ed 100644 --- a/app/views/shared/empty_states/_wikis_layout.html.haml +++ b/app/views/shared/empty_states/_wikis_layout.html.haml @@ -1,6 +1,6 @@ .row.empty-state.empty-state-wiki .col-12 - .svg-content.qa-svg-content + .svg-content{ data: { qa_selector: 'svg_content' } } = image_tag image_path .col-12 .text-content.text-center diff --git a/app/views/shared/groups/_search_form.html.haml b/app/views/shared/groups/_search_form.html.haml index a574394694d..2afac0ad733 100644 --- a/app/views/shared/groups/_search_form.html.haml +++ b/app/views/shared/groups/_search_form.html.haml @@ -1,2 +1,2 @@ = form_tag request.path, method: :get, class: "group-filter-form js-group-filter-form", id: 'group-filter-form' do |f| - = search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter qa-groups-filter', spellcheck: false, id: 'group-filter-form-field' + = search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter', data: { qa_selector: 'groups_filter_field' }, spellcheck: false, id: 'group-filter-form-field' diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 08883bb3372..af63839d7c1 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -11,7 +11,7 @@ - dropdown_title = local_assigns.fetch(:dropdown_title, _('Filter by label')) - dropdown_data = label_dropdown_data(edit_context, labels: labels_filter_path_with_defaults(only_group_labels: edit_context.is_a?(Group)), default_label: _('Labels')) -- dropdown_data.merge!(data_options) +- dropdown_data.merge!(data_options, qa_selector: "issuable_label_dropdown") - label_name = local_assigns.fetch(:label_name, _('Labels')) - no_default_styles = local_assigns.fetch(:no_default_styles, false) - classes << 'js-extra-options' if extra_options @@ -22,7 +22,7 @@ = hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : label.try(:title), id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.qa-issuable-label{ class: classes.join(' '), type: "button", data: dropdown_data } + %button.dropdown-menu-toggle.js-label-select.js-multiselect{ class: classes.join(' '), type: "button", data: dropdown_data } - apply_is_default_styles = (selected.nil? || selected.empty?) && !no_default_styles %span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) } = multi_label_name(selected, label_name) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index dc713337747..ef539029272 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -7,8 +7,8 @@ - dropdown_title = local_assigns.fetch(:dropdown_title, _('Filter by milestone')) - if selected.present? || params[:milestone_title].present? = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id) -= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "qa-issuable-milestone-dropdown js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "qa-issuable-dropdown-menu-milestone dropdown-menu-selectable dropdown-menu-milestone", - placeholder: _('Search milestones'), footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), default_label: _('Milestone') } }) do += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", dropdown_qa_selector: "issuable_milestone_dropdown_content", + placeholder: _('Search milestones'), footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), default_label: _('Milestone'), qa_selector: "issuable_milestone_dropdown", testid: "issuable-milestone-dropdown" } }) do - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 321311e1d96..6da094924a0 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -26,14 +26,14 @@ = _('To-Do') .js-issuable-todo{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } - .block.assignee.qa-assignee-block{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}" } + .block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { qa_selector: 'assignee_block_container' } } = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in - if issuable_sidebar[:supports_severity] #js-severity - if reviewers - .block.reviewer.qa-reviewer-block + .block.reviewer = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in - if issuable_sidebar[:supports_escalation] diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index ca84bb6d987..76469b34832 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -35,7 +35,7 @@ = form.label :milestone_id, _('Milestone'), class: "col-12" .col-12 .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: _('Select milestone') + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: _('Select milestone') .form-group.row = form.label :label_ids, _('Labels'), class: "col-12" diff --git a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml index f9c3c11eed8..efecffbcc2e 100644 --- a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml +++ b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml @@ -8,4 +8,4 @@ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' } = dropdown_tag(users_dropdown_label(issuable.assignees), options: assignees_dropdown_options(issuable.to_ability_name)) - = link_to _('Assign to me'), '#', class: "assign-to-me-link gl-white-space-nowrap gl-pl-4 qa-assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}" + = link_to _('Assign to me'), '#', class: "assign-to-me-link gl-white-space-nowrap gl-pl-4 #{'hide' if issuable.assignees.include?(current_user)}", data: { qa_selector: 'assign_to_me_link' } diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml index e7c0833de0f..51f49c7ca8e 100644 --- a/app/views/shared/issuable/form/_title.html.haml +++ b/app/views/shared/issuable/form/_title.html.haml @@ -9,7 +9,7 @@ %div{ data: { testid: 'issue-title-input-field' } } = form.text_field :title, required: true, aria: { required: true }, maxlength: 255, autofocus: true, - autocomplete: 'off', class: 'form-control pad qa-issuable-form-title', dir: 'auto' + autocomplete: 'off', class: 'form-control pad', dir: 'auto', data: { qa_selector: 'issuable_form_title_field' } - if issuable.respond_to?(:draft?) .form-text.text-muted diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml index 1df02d57030..cf8bd23b153 100644 --- a/app/views/shared/labels/_form.html.haml +++ b/app/views/shared/labels/_form.html.haml @@ -4,20 +4,20 @@ .form-group.row .col-12 = f.label :title - = f.text_field :title, class: "gl-form-input form-control js-label-title qa-label-title", required: true, autofocus: true + = f.text_field :title, class: "gl-form-input form-control js-label-title", required: true, autofocus: true, data: { qa_selector: 'label_title_field' } = render_if_exists 'shared/labels/create_label_help_text' .form-group.row .col-12 = f.label :description - = f.text_field :description, class: "gl-form-input form-control js-quick-submit qa-label-description" + = f.text_field :description, class: "gl-form-input form-control js-quick-submit", data: { qa_selector: 'label_description_field' } .form-group.row .col-12 = f.label :color, _("Background color") .input-group .input-group-prepend .input-group-text.label-color-preview - = f.text_field :color, class: "gl-form-input form-control qa-label-color" + = f.text_field :color, class: "gl-form-input form-control", data: { qa_selector: 'label_color_field' } .form-text.text-muted = _('Choose any color.') %br @@ -28,7 +28,7 @@ - if @label.persisted? = f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button gl-mr-2' - else - = f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button qa-label-create-button gl-mr-2' + = f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button gl-mr-2', data: { qa_selector: 'label_create_button' } = link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel gl-mr-2' - if @label.persisted? - presented_label = @label.present diff --git a/app/views/shared/labels/_nav.html.haml b/app/views/shared/labels/_nav.html.haml index 622ad9db425..c82a22c73b8 100644 --- a/app/views/shared/labels/_nav.html.haml +++ b/app/views/shared/labels/_nav.html.haml @@ -14,8 +14,8 @@ = render Pajamas::ButtonComponent.new(icon: 'search', button_options: { type: "submit", "aria-label" => _('Submit search') }) = render 'shared/labels/sort_dropdown' - if labels_or_filters && can_admin_label && @project - = render Pajamas::ButtonComponent.new(variant: :confirm, href: new_project_label_path(@project), button_options: { class: 'qa-label-create-new' }) do + = render Pajamas::ButtonComponent.new(variant: :confirm, href: new_project_label_path(@project), button_options: { data: { qa_selector: 'create_new_label_button' } }) do = _('New label') - if labels_or_filters && can_admin_label && @group - = render Pajamas::ButtonComponent.new(variant: :confirm, href: new_group_label_path(@group), button_options: { class: 'qa-label-create-new' }) do + = render Pajamas::ButtonComponent.new(variant: :confirm, href: new_group_label_path(@group), button_options: { data: { qa_selector: 'create_new_label_button' } }) do = _('New label') diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml index e96a9152c80..51a5c9dd38f 100644 --- a/app/views/shared/projects/_search_form.html.haml +++ b/app/views/shared/projects/_search_form.html.haml @@ -1,7 +1,7 @@ - form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : '' - placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...' -= form_tag filter_projects_path, method: :get, class: 'project-filter-form qa-project-filter-form', id: 'project-filter-form' do |f| += form_tag filter_projects_path, method: :get, class: 'project-filter-form', data: { qa_selector: 'project_filter_form_container' }, id: 'project-filter-form' do |f| = search_field_tag :name, params[:name], placeholder: placeholder, class: "project-filter-form-field form-control #{form_field_classes}", diff --git a/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml b/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml index 32b9044c551..d10196a83cc 100644 --- a/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml +++ b/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml @@ -10,7 +10,7 @@ %td.merge_access_levels-container = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", merge_access_levels.first&.access_level = dropdown_tag( (merge_access_levels.first&.humanize || 'Select') , - options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header', + options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header', data: { field_name: "allowed_to_merge_#{protected_branch.id}", preselected_items: access_levels_data(merge_access_levels) }}) - if user_merge_access_levels.any? %p.small |