diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-30 21:08:47 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-30 21:08:47 +0000 |
commit | c8f773a8593926f4f2dec6f446a3b3e59e9c9909 (patch) | |
tree | 4e5ea1d3b861ff99015f6112da567de7873868aa /app | |
parent | 929b887e5391dea7cb53b88b77b9a35351c87d99 (diff) | |
download | gitlab-ce-c8f773a8593926f4f2dec6f446a3b3e59e9c9909.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
25 files changed, 229 insertions, 120 deletions
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue index 8cf939254c1..2ffecce0a56 100644 --- a/app/assets/javascripts/frequent_items/components/app.vue +++ b/app/assets/javascripts/frequent_items/components/app.vue @@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; import eventHub from '../event_hub'; import store from '../store/'; import { FREQUENT_ITEMS, STORAGE_KEY } from '../constants'; -import { isMobile, updateExistingFrequentItem } from '../utils'; +import { isMobile, updateExistingFrequentItem, sanitizeItem } from '../utils'; import FrequentItemsSearchInput from './frequent_items_search_input.vue'; import FrequentItemsList from './frequent_items_list.vue'; import frequentItemsMixin from './frequent_items_mixin'; @@ -64,7 +64,9 @@ export default { this.fetchFrequentItems(); } }, - logItemAccess(storageKey, item) { + logItemAccess(storageKey, unsanitizedItem) { + const item = sanitizeItem(unsanitizedItem); + if (!AccessorUtilities.isLocalStorageAccessSafe()) { return false; } diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue index 67ffa97a046..0ece64692ae 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue @@ -1,6 +1,7 @@ <script> import FrequentItemsListItem from './frequent_items_list_item.vue'; import frequentItemsMixin from './frequent_items_mixin'; +import { sanitizeItem } from '../utils'; export default { components: { @@ -48,6 +49,9 @@ export default { ? this.translations.itemListErrorMessage : this.translations.itemListEmptyMessage; }, + sanitizedItems() { + return this.items.map(sanitizeItem); + }, }, }; </script> @@ -59,7 +63,7 @@ export default { {{ listEmptyMessage }} </li> <frequent-items-list-item - v-for="item in items" + v-for="item in sanitizedItems" v-else :key="item.id" :item-id="item.id" diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js index cc1668b1a0d..5188d6118ac 100644 --- a/app/assets/javascripts/frequent_items/utils.js +++ b/app/assets/javascripts/frequent_items/utils.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; +import sanitize from 'sanitize-html'; import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants'; export const isMobile = () => ['md', 'sm', 'xs'].includes(bp.getBreakpointSize()); @@ -43,3 +44,9 @@ export const updateExistingFrequentItem = (frequentItem, item) => { lastAccessedOn: accessedOverHourAgo ? Date.now() : frequentItem.lastAccessedOn, }; }; + +export const sanitizeItem = item => ({ + ...item, + name: sanitize(item.name.toString(), { allowedTags: [] }), + namespace: sanitize(item.namespace.toString(), { allowedTags: [] }), +}); diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index a5e38022b8d..4daa8c60e58 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; import Api from './api'; +import { escape } from 'lodash'; import { normalizeHeaders } from './lib/utils/common_utils'; import { __ } from '~/locale'; @@ -75,10 +76,12 @@ const groupsSelect = () => { } }, formatResult(object) { - return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`; + return `<div class='group-result'> <div class='group-name'>${escape( + object.full_name, + )}</div> <div class='group-path'>${object.full_path}</div> </div>`; }, formatSelection(object) { - return object.full_name; + return escape(object.full_name); }, dropdownCssClass: 'ajax-groups-dropdown select2-infinite', // we do not want to escape markup since we are displaying html in results diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index be2adb07526..762228dd138 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -14,7 +14,7 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; import { __ } from '~/locale'; -import initUserPopovers from '../../user_popovers'; +import initUserPopovers from '~/user_popovers'; export default { name: 'NotesApp', diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js index 157d89a3a40..5b9e3817f3a 100644 --- a/app/assets/javascripts/user_popovers.js +++ b/app/assets/javascripts/user_popovers.js @@ -3,108 +3,92 @@ import Vue from 'vue'; import UsersCache from './lib/utils/users_cache'; import UserPopover from './vue_shared/components/user_popover/user_popover.vue'; -let renderedPopover; -let renderFn; - -const handleUserPopoverMouseOut = event => { - const { target } = event; - target.removeEventListener('mouseleave', handleUserPopoverMouseOut); - - if (renderFn) { - clearTimeout(renderFn); - } - if (renderedPopover) { - renderedPopover.$destroy(); - renderedPopover = null; - } - target.removeAttribute('aria-describedby'); +const removeTitle = el => { + // Removing titles so its not showing tooltips also + + el.dataset.originalTitle = ''; + el.setAttribute('title', ''); +}; + +const getPreloadedUserInfo = dataset => { + const userId = dataset.user || dataset.userId; + const { username, name, avatarUrl } = dataset; + + return { + userId, + username, + name, + avatarUrl, + }; }; /** * Adds a UserPopover component to the body, hands over as much data as the target element has in data attributes. * loads based on data-user-id more data about a user from the API and sets it on the popover */ -const handleUserPopoverMouseOver = event => { - const { target } = event; - // Add listener to actually remove it again - target.addEventListener('mouseleave', handleUserPopoverMouseOut); - - renderFn = setTimeout(() => { - // Helps us to use current markdown setup without maybe breaking or duplicating for now - if (target.dataset.user) { - target.dataset.userId = target.dataset.user; - // Removing titles so its not showing tooltips also - target.dataset.originalTitle = ''; - target.setAttribute('title', ''); - } - - const { userId, username, name, avatarUrl } = target.dataset; +const populateUserInfo = user => { + const { userId } = user; + + return Promise.all([UsersCache.retrieveById(userId), UsersCache.retrieveStatusById(userId)]).then( + ([userData, status]) => { + if (userData) { + Object.assign(user, { + avatarUrl: userData.avatar_url, + username: userData.username, + name: userData.name, + location: userData.location, + bio: userData.bio, + organization: userData.organization, + loaded: true, + }); + } + + if (status) { + Object.assign(user, { + status, + }); + } + + return user; + }, + ); +}; + +export default (elements = document.querySelectorAll('.js-user-link')) => { + const userLinks = Array.from(elements); + + return userLinks.map(el => { + const UserPopoverComponent = Vue.extend(UserPopover); const user = { - userId, - username, - name, - avatarUrl, location: null, bio: null, organization: null, status: null, loaded: false, }; - if (userId || username) { - const UserPopoverComponent = Vue.extend(UserPopover); - renderedPopover = new UserPopoverComponent({ - propsData: { - target, - user, - }, - }); - - renderedPopover.$mount(); - - UsersCache.retrieveById(userId) - .then(userData => { - if (!userData) { - return undefined; - } - - Object.assign(user, { - avatarUrl: userData.avatar_url, - username: userData.username, - name: userData.name, - location: userData.location, - bio: userData.bio, - organization: userData.organization, - status: userData.status, - loaded: true, - }); - - if (userData.status) { - return Promise.resolve(); - } - - return UsersCache.retrieveStatusById(userId); - }) - .then(status => { - if (!status) { - return; - } - - Object.assign(user, { - status, - }); - }) - .catch(() => { - renderedPopover.$destroy(); - renderedPopover = null; - }); - } - }, 200); // 200ms delay so not every mouseover triggers Popover + API Call -}; + const renderedPopover = new UserPopoverComponent({ + propsData: { + target: el, + user, + }, + }); + + renderedPopover.$mount(); + + el.addEventListener('mouseenter', ({ target }) => { + removeTitle(target); + const preloadedUserInfo = getPreloadedUserInfo(target.dataset); + + Object.assign(user, preloadedUserInfo); -export default elements => { - const userLinks = elements || [...document.querySelectorAll('.js-user-link')]; + if (preloadedUserInfo.userId) { + populateUserInfo(user); + } + }); + el.addEventListener('mouseleave', ({ target }) => { + target.removeAttribute('aria-describedby'); + }); - userLinks.forEach(el => { - el.addEventListener('mouseenter', handleUserPopoverMouseOver); + return renderedPopover; }); }; diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 37e3643bf6c..ca25d9ee738 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -56,19 +56,16 @@ export default { </script> <template> - <gl-popover :target="target" boundary="viewport" placement="top" offset="0, 1" show> + <!-- 200ms delay so not every mouseover triggers Popover --> + <gl-popover :target="target" :delay="200" boundary="viewport" triggers="hover" placement="top"> <div class="user-popover d-flex"> <div class="p-1 flex-shrink-1"> <user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="mr-2" /> </div> <div class="p-1 w-100"> <h5 class="m-0"> - {{ user.name }} - <gl-skeleton-loading - v-if="nameIsLoading" - :lines="1" - class="animation-container-small mb-1" - /> + <span v-if="user.name">{{ user.name }}</span> + <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" /> </h5> <div class="text-secondary mb-2"> <span v-if="user.username">@{{ user.username }}</span> diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 789bccf268a..fa88ca91170 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,6 +5,7 @@ require 'fogbugz' class ApplicationController < ActionController::Base include Gitlab::GonHelper + include Gitlab::NoCacheHeaders include GitlabRoutingHelper include PageLayoutHelper include SafeParamsHelper @@ -55,7 +56,6 @@ class ApplicationController < ActionController::Base # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security # concerns due to caching private data. DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store" - DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache" rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -247,9 +247,9 @@ class ApplicationController < ActionController::Base end def no_cache_headers - headers['Cache-Control'] = DEFAULT_GITLAB_CONTROL_NO_CACHE - headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility - headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' + DEFAULT_GITLAB_NO_CACHE_HEADERS.each do |k, v| + headers[k] = v + end end def default_headers diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 1a97b39d3ae..1668cf004f8 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -19,7 +19,7 @@ class DashboardController < Dashboard::ApplicationController format.json do load_events - pager_json("events/_events", @events.count) + pager_json('events/_events', @events.count { |event| event.visible_to_user?(current_user) }) end end end @@ -37,6 +37,7 @@ class DashboardController < Dashboard::ApplicationController @events = EventCollection .new(projects, offset: params[:offset].to_i, filter: event_filter) .to_a + .map(&:present) Events::RenderService.new(current_user).execute(@events) end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 0953ca96317..958dc27984f 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -91,7 +91,7 @@ class GroupsController < Groups::ApplicationController format.json do load_events - pager_json("events/_events", @events.count) + pager_json("events/_events", @events.count { |event| event.visible_to_user?(current_user) }) end end end @@ -209,8 +209,9 @@ class GroupsController < Groups::ApplicationController .includes(:namespace) @events = EventCollection - .new(projects, offset: params[:offset].to_i, filter: event_filter, groups: groups) - .to_a + .new(projects, offset: params[:offset].to_i, filter: event_filter, groups: groups) + .to_a + .map(&:present) Events::RenderService .new(current_user) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f4f2a16b82b..d39a4c373ff 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -118,7 +118,7 @@ class ProjectsController < Projects::ApplicationController format.html format.json do load_events - pager_json('events/_events', @events.count) + pager_json('events/_events', @events.count { |event| event.visible_to_user?(current_user) }) end end end @@ -343,6 +343,7 @@ class ProjectsController < Projects::ApplicationController @events = EventCollection .new(projects, offset: params[:offset].to_i, filter: event_filter) .to_a + .map(&:present) Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?) end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index efeee4a7a4d..3ade1300c2d 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -10,6 +10,8 @@ module Types @calls_gitaly = !!kwargs.delete(:calls_gitaly) @constant_complexity = !!kwargs[:complexity] kwargs[:complexity] ||= field_complexity(kwargs[:resolver_class]) + @feature_flag = kwargs[:feature_flag] + kwargs = check_feature_flag(kwargs) super(*args, **kwargs, &block) end @@ -28,8 +30,27 @@ module Types @constant_complexity end + def visible?(context) + return false if feature_flag.present? && !Feature.enabled?(feature_flag) + + super + end + private + attr_reader :feature_flag + + def feature_documentation_message(key, description) + "#{description}. Available only when feature flag #{key} is enabled." + end + + def check_feature_flag(args) + args[:description] = feature_documentation_message(args[:feature_flag], args[:description]) if args[:feature_flag].present? + args.delete(:feature_flag) + + args + end + def field_complexity(resolver_class) if resolver_class field_resolver_complexity diff --git a/app/graphql/types/grafana_integration_type.rb b/app/graphql/types/grafana_integration_type.rb index e6c865fea53..f234008ee0d 100644 --- a/app/graphql/types/grafana_integration_type.rb +++ b/app/graphql/types/grafana_integration_type.rb @@ -10,14 +10,19 @@ module Types description: 'Internal ID of the Grafana integration' field :grafana_url, GraphQL::STRING_TYPE, null: false, description: 'Url for the Grafana host for the Grafana integration' - field :token, GraphQL::STRING_TYPE, null: false, - description: 'API token for the Grafana integration' field :enabled, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates whether Grafana integration is enabled' - field :created_at, Types::TimeType, null: false, description: 'Timestamp of the issue\'s creation' field :updated_at, Types::TimeType, null: false, description: 'Timestamp of the issue\'s last activity' + + field :token, GraphQL::STRING_TYPE, null: false, + deprecation_reason: 'Plain text token has been masked for security reasons', + description: 'API token for the Grafana integration. Field is permanently masked.' + + def token + object.masked_token + end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b8d7685c2cf..011871f373f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -368,8 +368,8 @@ module ProjectsHelper @project.grafana_integration&.grafana_url end - def grafana_integration_token - @project.grafana_integration&.token + def grafana_integration_masked_token + @project.grafana_integration&.masked_token end def grafana_integration_enabled? diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index 8a768b3a2c0..6c8bfc35334 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true class GenericCommitStatus < CommitStatus + EXTERNAL_STAGE_IDX = 1_000_000 + before_validation :set_default_values validates :target_url, addressable_url: true, length: { maximum: 255 }, allow_nil: true + validate :name_uniqueness_across_types, unless: :importing? # GitHub compatible API alias_attribute :context, :name @@ -13,7 +16,7 @@ class GenericCommitStatus < CommitStatus def set_default_values self.context ||= 'default' self.stage ||= 'external' - self.stage_idx ||= 1000000 + self.stage_idx ||= EXTERNAL_STAGE_IDX end def tags @@ -25,4 +28,14 @@ class GenericCommitStatus < CommitStatus .new(self, current_user) .fabricate! end + + private + + def name_uniqueness_across_types + return if !pipeline || name.blank? + + if pipeline.statuses.by_name(name).where.not(type: type).exists? + errors.add(:name, :taken) + end + end end diff --git a/app/models/grafana_integration.rb b/app/models/grafana_integration.rb index ed4c279965a..00213732fee 100644 --- a/app/models/grafana_integration.rb +++ b/app/models/grafana_integration.rb @@ -8,11 +8,13 @@ class GrafanaIntegration < ApplicationRecord algorithm: 'aes-256-gcm', key: Settings.attr_encrypted_db_key_base_32 + before_validation :check_token_changes + validates :grafana_url, length: { maximum: 1024 }, addressable_url: { enforce_sanitization: true, ascii_only: true } - validates :token, :project, presence: true + validates :encrypted_token, :project, presence: true validates :enabled, inclusion: { in: [true, false] } @@ -23,4 +25,28 @@ class GrafanaIntegration < ApplicationRecord @client ||= ::Grafana::Client.new(api_url: grafana_url.chomp('/'), token: token) end + + def masked_token + mask(encrypted_token) + end + + def masked_token_was + mask(encrypted_token_was) + end + + private + + def token + decrypt(:token, encrypted_token) + end + + def check_token_changes + return unless [encrypted_token_was, masked_token_was].include?(token) + + clear_attribute_changes [:token, :encrypted_token, :encrypted_token_iv] + end + + def mask(token) + token&.squish&.gsub(/./, '*') + end end diff --git a/app/models/note.rb b/app/models/note.rb index 0434f0963d3..8af650e27aa 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -545,7 +545,8 @@ class Note < ApplicationRecord # if they are not equal, then there are private/confidential references as well user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count else - referenced_mentionables(user).any? + refs = all_references(user) + refs.all.any? && refs.stateful_not_visible_counter == 0 end end diff --git a/app/models/project.rb b/app/models/project.rb index 8cb35904d92..064c647ac59 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2366,6 +2366,10 @@ class Project < ApplicationRecord end end + def template_source? + false + end + private def closest_namespace_setting(name) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 3a16f7dc239..c93a19bdc3d 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -21,6 +21,14 @@ class BasePolicy < DeclarativePolicy::Base with_options scope: :user, score: 0 condition(:deactivated) { @user&.deactivated? } + desc "User email is unconfirmed or user account is locked" + with_options scope: :user, score: 0 + condition(:inactive) do + Feature.enabled?(:inactive_policy_condition, default_enabled: true) && + @user && + !@user&.active_for_authentication? + end + with_options scope: :user, score: 0 condition(:external_user) { @user.nil? || @user.external? } diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 764d61a9e22..2bde7bcca08 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -36,6 +36,13 @@ class GlobalPolicy < BasePolicy enable :use_slash_commands end + rule { inactive }.policy do + prevent :log_in + prevent :access_api + prevent :access_git + prevent :use_slash_commands + end + rule { blocked | internal }.policy do prevent :log_in prevent :access_api diff --git a/app/presenters/event_presenter.rb b/app/presenters/event_presenter.rb index f31d362d5fa..5657e0b96bc 100644 --- a/app/presenters/event_presenter.rb +++ b/app/presenters/event_presenter.rb @@ -3,6 +3,18 @@ class EventPresenter < Gitlab::View::Presenter::Delegated presents :event + def initialize(subject, **attributes) + super + + @visible_to_user_cache = ActiveSupport::Cache::MemoryStore.new + end + + # Caching `visible_to_user?` method in the presenter beause it might be called multiple times. + def visible_to_user?(user = nil) + @visible_to_user_cache.fetch(user&.id) { super(user) } + end + + # implement cache here def resource_parent_name resource_parent&.full_name || '' end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 3f0aedfbfb2..569b91de73e 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -18,7 +18,7 @@ class CompareService return unless raw_compare && raw_compare.base && raw_compare.head Compare.new(raw_compare, - target_project, + start_project, base_sha: base_sha, straight: straight) end diff --git a/app/services/projects/group_links/destroy_service.rb b/app/services/projects/group_links/destroy_service.rb index c96dcaae8d5..ea7d05551fd 100644 --- a/app/services/projects/group_links/destroy_service.rb +++ b/app/services/projects/group_links/destroy_service.rb @@ -6,6 +6,12 @@ module Projects def execute(group_link) return false unless group_link + if group_link.project.private? + TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) + else + TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id) + end + group_link.destroy end end diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 8344397f67d..38859c1efa4 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -4,6 +4,12 @@ module Projects module ImportExport class ExportService < BaseService def execute(after_export_strategy = nil, options = {}) + unless project.template_source? || can?(current_user, :admin_project, project) + raise ::Gitlab::ImportExport::Error.new( + "User with ID: %s does not have permission to Project %s with ID: %s." % + [current_user.id, project.name, project.id]) + end + @shared = project.import_export_shared save_all! diff --git a/app/views/projects/settings/operations/_grafana_integration.html.haml b/app/views/projects/settings/operations/_grafana_integration.html.haml index cd5b5abd9ce..69e42a6c4fb 100644 --- a/app/views/projects/settings/operations/_grafana_integration.html.haml +++ b/app/views/projects/settings/operations/_grafana_integration.html.haml @@ -1,2 +1,2 @@ .js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project), - grafana_integration: { url: grafana_integration_url, token: grafana_integration_token, enabled: grafana_integration_enabled?.to_s } } } + grafana_integration: { url: grafana_integration_url, token: grafana_integration_masked_token, enabled: grafana_integration_enabled?.to_s } } } |