Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-07-10 21:08:26 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-10 21:08:26 +0300
commit8731954e18e2c5969d4a5f67d187892312c463c4 (patch)
treeb8ce4b5012aa5239d3eb3f5168916e0597996742 /app
parent69e6424b738ea9ac3c6eed0263fe5a6951df7195 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue2
-rw-r--r--app/assets/javascripts/error_tracking/components/timeline_chart.vue4
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue1
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue68
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/feedback_banner.vue57
-rw-r--r--app/assets/javascripts/mr_more_dropdown.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/page.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/show/index.js4
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue7
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue12
-rw-r--r--app/assets/stylesheets/page_bundles/jira_connect.scss8
-rw-r--r--app/graphql/types/ci/job_type.rb10
-rw-r--r--app/graphql/types/ci/stage_type.rb2
-rw-r--r--app/models/user.rb3
-rw-r--r--app/models/user_preference.rb2
-rw-r--r--app/views/admin/application_settings/_help_page.html.haml2
17 files changed, 145 insertions, 49 deletions
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue
index 794763e0cd8..76db9613dc1 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue
@@ -26,7 +26,7 @@ export default {
return [
{
key: 'artifacts.paths',
- title: i18n.ARTIFACTS_AND_CACHE,
+ title: i18n.ARTIFACTS_PATHS,
paths: this.job.artifacts.paths,
generateInputDataTestId: (index) => `artifacts-paths-input-${index}`,
generateDeleteButtonDataTestId: (index) => `delete-artifacts-paths-button-${index}`,
diff --git a/app/assets/javascripts/error_tracking/components/timeline_chart.vue b/app/assets/javascripts/error_tracking/components/timeline_chart.vue
index 51e0c900e4b..907a6c8557f 100644
--- a/app/assets/javascripts/error_tracking/components/timeline_chart.vue
+++ b/app/assets/javascripts/error_tracking/components/timeline_chart.vue
@@ -1,6 +1,6 @@
<script>
import { GlChart } from '@gitlab/ui/dist/charts';
-import { dataVizBlue500 } from '@gitlab/ui/scss_to_js/scss_variables';
+import { DATA_VIZ_BLUE_500 } from '@gitlab/ui/dist/tokens/js/tokens';
import { hexToRgba } from '@gitlab/ui/dist/utils/utils';
import { isNumber } from 'lodash';
import { formatDate } from '~/lib/utils/datetime/date_format_utility';
@@ -109,7 +109,7 @@ export default {
{
data: yData,
type: 'bar',
- itemStyle: { color: hexToRgba(dataVizBlue500, 0.5) },
+ itemStyle: { color: hexToRgba(DATA_VIZ_BLUE_500, 0.5) },
},
],
tooltip: {
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 8962bb76926..edc6cc3dcdc 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -105,6 +105,7 @@ export default {
:title="lastCommit.message"
:href="getCommitPath(lastCommit.short_id)"
class="commit-sha"
+ data-testid="commit-sha-content"
data-qa-selector="commit_sha_content"
>{{ lastCommit.short_id }}</a
>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 7e79572f76d..c5f6f736626 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -10,6 +10,7 @@ import SignInPage from '../pages/sign_in/sign_in_page.vue';
import SubscriptionsPage from '../pages/subscriptions_page.vue';
import UserLink from './user_link.vue';
import BrowserSupportAlert from './browser_support_alert.vue';
+import FeedbackBanner from './feedback_banner.vue';
export default {
name: 'JiraConnectApp',
@@ -18,6 +19,7 @@ export default {
GlLink,
GlSprintf,
BrowserSupportAlert,
+ FeedbackBanner,
SignInPage,
SubscriptionsPage,
UserLink,
@@ -103,39 +105,47 @@ export default {
<user-link v-if="userSignedIn" :user="currentUser" class="gl-fixed gl-right-4" />
</header>
- <main class="jira-connect-app gl-px-5 gl-pt-7 gl-mx-auto">
- <browser-support-alert v-if="!isBrowserSupported" class="gl-mb-7" />
- <div v-else data-testid="jira-connect-app">
- <gl-alert
- v-if="shouldShowAlert"
- :variant="alert.variant"
- :title="alert.title"
- class="gl-mb-5"
- data-testid="jira-connect-persisted-alert"
- @dismiss="setAlert"
- >
- <gl-sprintf v-if="alert.linkUrl" :message="alert.message">
- <template #link="{ content }">
- <gl-link :href="alert.linkUrl" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
+ <main
+ class="jira-connect-app gl-px-5 gl-pt-7 gl-pb-7 gl-mx-auto gl-display-flex gl-flex-direction-column gl-gap-7"
+ >
+ <div class="gl-flex-grow-1">
+ <browser-support-alert v-if="!isBrowserSupported" class="gl-mb-7" />
+ <div v-else data-testid="jira-connect-app">
+ <gl-alert
+ v-if="shouldShowAlert"
+ :variant="alert.variant"
+ :title="alert.title"
+ class="gl-mb-5"
+ data-testid="jira-connect-persisted-alert"
+ @dismiss="setAlert"
+ >
+ <gl-sprintf v-if="alert.linkUrl" :message="alert.message">
+ <template #link="{ content }">
+ <gl-link :href="alert.linkUrl" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
- <template v-else>
- {{ alert.message }}
- </template>
- </gl-alert>
+ <template v-else>
+ {{ alert.message }}
+ </template>
+ </gl-alert>
- <div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
- <sign-in-page
- v-show="!userSignedIn"
- :has-subscriptions="hasSubscriptions"
- :public-key-storage-enabled="publicKeyStorageEnabled"
- @sign-in-oauth="onSignInOauth"
- @error="onSignInError"
- />
- <subscriptions-page v-if="userSignedIn" :has-subscriptions="hasSubscriptions" />
+ <div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
+ <sign-in-page
+ v-show="!userSignedIn"
+ :has-subscriptions="hasSubscriptions"
+ :public-key-storage-enabled="publicKeyStorageEnabled"
+ @sign-in-oauth="onSignInOauth"
+ @error="onSignInError"
+ />
+ <subscriptions-page v-if="userSignedIn" :has-subscriptions="hasSubscriptions" />
+ </div>
</div>
</div>
+
+ <div class="gl-flex-grow-2">
+ <feedback-banner class="gl-max-w-80 gl-mx-auto" />
+ </div>
</main>
</div>
</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/feedback_banner.vue b/app/assets/javascripts/jira_connect/subscriptions/components/feedback_banner.vue
new file mode 100644
index 00000000000..5d6117b836d
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/feedback_banner.vue
@@ -0,0 +1,57 @@
+<script>
+import { GlBanner } from '@gitlab/ui';
+import ChatBubbleSvg from '@gitlab/svgs/dist/illustrations/chat-bubble-sm.svg?url';
+import { s__, __ } from '~/locale';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+
+export default {
+ components: {
+ GlBanner,
+ LocalStorageSync,
+ },
+
+ data() {
+ return {
+ feedbackBannerDismissed: false,
+ };
+ },
+
+ methods: {
+ handleBannerClose() {
+ this.feedbackBannerDismissed = true;
+ },
+ },
+
+ i18n: {
+ title: s__('JiraConnect|Tell us what you think!'),
+ body: s__(
+ 'JiraConnect|We would love to learn more about your experience with the GitLab for Jira Cloud App.',
+ ),
+ dismissLabel: __('Dismiss'),
+ buttonText: __('Give feedback'),
+ },
+ feedbackBannerKey: 'jira_connect_feedback_banner',
+ feedbackIssueUrl: 'https://gitlab.com/gitlab-org/gitlab/-/issues/413652',
+ buttonAttributes: {
+ target: '_blank',
+ },
+ ChatBubbleSvg,
+};
+</script>
+
+<template>
+ <local-storage-sync v-model="feedbackBannerDismissed" :storage-key="$options.feedbackBannerKey">
+ <gl-banner
+ v-if="!feedbackBannerDismissed"
+ :title="$options.i18n.title"
+ :button-attributes="$options.buttonAttributes"
+ :button-text="$options.i18n.buttonText"
+ :button-link="$options.feedbackIssueUrl"
+ :dismiss-label="$options.i18n.dismissLabel"
+ :svg-path="$options.ChatBubbleSvg"
+ @close="handleBannerClose"
+ >
+ <p>{{ $options.i18n.body }}</p>
+ </gl-banner>
+ </local-storage-sync>
+</template>
diff --git a/app/assets/javascripts/mr_more_dropdown.js b/app/assets/javascripts/mr_more_dropdown.js
index ca50028a93a..4a9e10be5ad 100644
--- a/app/assets/javascripts/mr_more_dropdown.js
+++ b/app/assets/javascripts/mr_more_dropdown.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { initReportAbuse } from '~/projects/report_abuse';
import MrMoreDropdown from '~/vue_shared/components/mr_more_dropdown.vue';
export const initMrMoreDropdown = () => {
@@ -37,6 +38,9 @@ export const initMrMoreDropdown = () => {
reportAbusePath: el.dataset.reportAbusePath,
showSummaryNotesToggle: Boolean(document.querySelector('#js-summary-notes')),
},
+ beforeCreate() {
+ initReportAbuse();
+ },
render: (createElement) =>
createElement(MrMoreDropdown, {
props: {
diff --git a/app/assets/javascripts/pages/projects/merge_requests/page.js b/app/assets/javascripts/pages/projects/merge_requests/page.js
index 552e75da9b8..75e308e706f 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/page.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/page.js
@@ -9,6 +9,7 @@ import store from '~/mr_notes/stores';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import { apolloProvider } from '~/graphql_shared/issuable_client';
import { parseBoolean } from '~/lib/utils/common_utils';
+import { initMrMoreDropdown } from '~/mr_more_dropdown';
import initShow from './init_merge_request_show';
import getStateQuery from './queries/get_state.query.graphql';
@@ -17,6 +18,7 @@ Vue.use(VueApollo);
export function initMrPage() {
initMrNotes();
initShow();
+ initMrMoreDropdown();
startCodeReviewMessaging({ signalBus: diffsEventHub });
}
diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
index 1cc7e892ca0..9eaf490abb2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
@@ -1,9 +1,5 @@
import mountNotesApp from 'ee_else_ce/mr_notes/mount_app';
-import { initReportAbuse } from '~/projects/report_abuse';
-import { initMrMoreDropdown } from '~/mr_more_dropdown';
import { initMrPage } from 'ee_else_ce/pages/projects/merge_requests/page';
initMrPage();
mountNotesApp();
-initReportAbuse();
-initMrMoreDropdown();
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
index 1e31a1cbbee..a1d0e400b5f 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
@@ -59,6 +59,13 @@ export default {
case COMMAND_HANDLE:
this.getCommandsAndPages();
break;
+ /* TODO: Search for recent issues initiated by #(ISSUE_HANDLE) from the command palette scope
+ was removed as using the # in command palette conflicted
+ with the existing global search functionality to search for issue by its id.
+ The code that performs the Recent issues search was not removed from the code base
+ as it would be nice to bring it back when we decide how to combine both search by id and text.
+ In scope of https://gitlab.com/gitlab-org/gitlab/-/issues/417434
+ we either bring back the search by #issue_text or remove the related code completely */
case USER_HANDLE:
case PROJECT_HANDLE:
case ISSUE_HANDLE:
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
index 83b262cf0d7..a43e621da44 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
@@ -2,14 +2,14 @@ import { s__, sprintf } from '~/locale';
export const COMMAND_HANDLE = '>';
export const USER_HANDLE = '@';
-export const PROJECT_HANDLE = '&';
+export const PROJECT_HANDLE = ':';
export const ISSUE_HANDLE = '#';
export const PATH_HANDLE = '/';
-export const COMMON_HANDLES = [COMMAND_HANDLE, USER_HANDLE, PROJECT_HANDLE, ISSUE_HANDLE];
+export const COMMON_HANDLES = [COMMAND_HANDLE, USER_HANDLE, PROJECT_HANDLE];
export const SEARCH_OR_COMMAND_MODE_PLACEHOLDER = sprintf(
s__(
- 'CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue, %{pathHandle} for project file or perform generic search...',
+ 'CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{pathHandle} for project file, or perform generic search...',
),
{
commandHandle: COMMAND_HANDLE,
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index 0d7547d88a1..6670b931416 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -36,6 +36,15 @@ export default {
status: {
type: Object,
required: true,
+ validator(status) {
+ const { group, icon } = status;
+ return (
+ typeof group === 'string' &&
+ group.length &&
+ typeof icon === 'string' &&
+ icon.startsWith('status_')
+ );
+ },
},
size: {
type: Number,
@@ -69,7 +78,7 @@ export default {
computed: {
wrapperStyleClasses() {
const status = this.status.group;
- return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status} gl-rounded-full gl-justify-content-center gl-line-height-0`;
+ return `ci-status-icon ci-status-icon-${status} gl-rounded-full gl-justify-content-center gl-line-height-0`;
},
icon() {
return this.isBorderless ? `${this.status.icon}_borderless` : this.status.icon;
@@ -84,7 +93,6 @@ export default {
{ interactive: isInteractive, active: isActive, borderless: isBorderless },
]"
:style="{ height: `${size}px`, width: `${size}px` }"
- data-testid="ci-icon-wrapper"
>
<gl-icon :name="icon" :size="size" :class="cssClasses" :aria-label="status.icon" />
</span>
diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss
index 2c54c819543..6972e98b0bf 100644
--- a/app/assets/stylesheets/page_bundles/jira_connect.scss
+++ b/app/assets/stylesheets/page_bundles/jira_connect.scss
@@ -9,6 +9,8 @@
@import '@gitlab/ui/src/components/base/alert/alert';
@import '@gitlab/ui/src/components/base/avatar/avatar';
@import '@gitlab/ui/src/components/base/button/button';
+@import '@gitlab/ui/src/components/base/banner/banner';
+@import '@gitlab/ui/src/components/base/card/card';
@import '@gitlab/ui/src/components/base/icon/icon';
@import '@gitlab/ui/src/components/base/link/link';
@import '@gitlab/ui/src/components/base/loading_icon/loading_icon';
@@ -23,7 +25,7 @@
@import '@gitlab/ui/src/components/base/form/form_group/form_group';
@import '@gitlab/ui/src/components/base/search_box_by_type/search_box_by_type';
-$header-height: 40px;
+$header-height: $gl-spacing-scale-8;
.jira-connect-header {
min-height: $header-height;
@@ -35,6 +37,6 @@ $header-height: 40px;
.jira-connect-app {
margin-top: $header-height;
- height: calc(100% - #{$header-height});
- max-width: 1000px;
+ height: 100%;
+ max-height: calc(100% - #{$header-height + $gl-spacing-scale-7 * 2});
}
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index a779ceb2e2a..02b10f3e4bd 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -87,8 +87,10 @@ module Types
description: 'Play path of the job.'
field :playable, GraphQL::Types::Boolean, null: false, method: :playable?,
description: 'Indicates the job can be played.'
- field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type, null: true,
- description: 'Jobs that must complete before the job runs. Returns `BuildNeed`, which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.'
+ field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type,
+ null: true,
+ description: 'Jobs that must complete before the job runs. Returns `BuildNeed`, ' \
+ 'which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.'
field :ref_name, GraphQL::Types::String, null: true,
description: 'Ref name of the job.'
field :ref_path, GraphQL::Types::String, null: true,
@@ -179,7 +181,9 @@ module Types
stages = pipeline.stages.by_position(positions)
stages.each do |stage|
- loader.call([pipeline, stage.position], stage.latest_statuses)
+ # Without `.to_a`, the memoization will only preserve the activerecord relation object. And when there is
+ # a call, the SQL query will be executed again.
+ loader.call([pipeline, stage.position], stage.latest_statuses.to_a)
end
end
end
diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb
index c0f3d1db57b..a9d8075329d 100644
--- a/app/graphql/types/ci/stage_type.rb
+++ b/app/graphql/types/ci/stage_type.rb
@@ -33,7 +33,7 @@ module Types
by_pipeline = keys.group_by(&:pipeline)
include_needs = keys.any? do |k|
k.requires?(%i[nodes jobs nodes needs]) ||
- k.requires?(%i[nodes jobs nodes previousStageJobsAndNeeds])
+ k.requires?(%i[nodes jobs nodes previousStageJobsOrNeeds])
end
by_pipeline.each do |pl, key_group|
diff --git a/app/models/user.rb b/app/models/user.rb
index 7fd5d25d7e0..93c15229e03 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -317,6 +317,9 @@ class User < ApplicationRecord
validates :color_scheme_id, allow_nil: true, inclusion: { in: Gitlab::ColorSchemes.valid_ids,
message: ->(*) { _("%{placeholder} is not a valid color scheme") % { placeholder: '%{value}' } } }
+ validates :hide_no_ssh_key, allow_nil: false, inclusion: { in: [true, false] }
+ validates :hide_no_password, allow_nil: false, inclusion: { in: [true, false] }
+ validates :notified_of_own_activity, allow_nil: false, inclusion: { in: [true, false] }
after_initialize :set_projects_limit
before_validation :sanitize_attrs
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index e527542e357..78ccce2aaae 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -23,6 +23,8 @@ class UserPreference < ApplicationRecord
format: { with: ColorsHelper::HEX_COLOR_PATTERN },
allow_blank: true
+ validates :time_display_relative, allow_nil: false, inclusion: { in: [true, false] }
+ validates :render_whitespace_in_code, allow_nil: false, inclusion: { in: [true, false] }
validates :pass_user_identities_to_ci_jwt, allow_nil: false, inclusion: { in: [true, false] }
validates :pinned_nav_items, json_schema: { filename: 'pinned_nav_items' }
diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml
index e76a83662af..9509806fc41 100644
--- a/app/views/admin/application_settings/_help_page.html.haml
+++ b/app/views/admin/application_settings/_help_page.html.haml
@@ -18,7 +18,7 @@
.form-group
= f.label :help_page_documentation_base_url, _('Documentation pages URL'), class: 'gl-font-weight-bold'
= f.text_field :help_page_documentation_base_url, class: 'form-control gl-form-input', placeholder: 'https://docs.gitlab.com'
- - docs_link_url = help_page_path('user/admin_area/settings/help_page', anchor: 'destination-requirements')
+ - docs_link_url = help_page_path('administration/settings/help_page', anchor: 'destination-requirements')
- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
%span.form-text.text-muted#support_help_block= html_escape(_('Requests for pages at %{code_start}%{help_text_url}%{code_end} redirect to the URL. The destination must meet certain requirements. %{docs_link_start}Learn more.%{docs_link_end}')) % { code_start: '<code>'.html_safe, help_text_url: help_url, code_end: '</code>'.html_safe, docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
= f.submit _('Save changes'), pajamas_button: true