Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2025-02-18 06:10:57 +00:00
parent 77be3075a5
commit 8e254665e0
13 changed files with 300 additions and 242 deletions

View File

@ -6,6 +6,7 @@ export default {
i18n: {
buttonLabel: __('Add request manually'),
inputLabel: __('URL or request ID'),
submitLabel: __('Add'),
},
components: {
GlForm,
@ -38,7 +39,7 @@ export default {
</script>
<template>
<div id="peek-view-add-request" class="view gl-flex">
<gl-form class="gl-flex gl-items-center" @submit.prevent>
<gl-form class="gl-flex gl-items-center" @submit.prevent="addRequest">
<gl-button
v-gl-tooltip.viewport
class="gl-mr-2"
@ -50,16 +51,28 @@ export default {
:aria-label="$options.i18n.buttonLabel"
@click="toggleInput"
/>
<gl-form-input
v-if="inputEnabled"
v-model="urlOrRequestId"
type="text"
:placeholder="$options.i18n.inputLabel"
:aria-label="$options.i18n.inputLabel"
class="gl-ml-2 !gl-px-3 !gl-py-2"
@keyup.enter="addRequest"
@keyup.esc="clearForm"
/>
<template v-if="inputEnabled">
<gl-form-input
v-model="urlOrRequestId"
type="text"
:placeholder="$options.i18n.inputLabel"
:aria-label="$options.i18n.inputLabel"
class="gl-ml-2 !gl-px-3 !gl-py-2"
@keyup.esc="clearForm"
/>
<gl-button
v-gl-tooltip.viewport
class="gl-ml-2"
category="tertiary"
type="submit"
variant="link"
icon="file-addition-solid"
size="small"
:aria-label="$options.i18n.submitLabel"
>
{{ $options.i18n.submitLabel }}
</gl-button>
</template>
</gl-form>
</div>
</template>

View File

@ -0,0 +1,134 @@
# frozen_string_literal: true
module Integrations
module Base
module Telegram
extend ActiveSupport::Concern
include HasAvatar
include Base::ChatNotification
TELEGRAM_HOSTNAME = "%{hostname}/bot%{token}/sendMessage"
class_methods do
def title
'Telegram'
end
def description
s_("TelegramIntegration|Send notifications about project events to Telegram.")
end
def to_param
'telegram'
end
def help
build_help_page_url(
'user/project/integrations/telegram.md',
s_("TelegramIntegration|Send notifications about project events to Telegram.")
)
end
def supported_events
super - ['deployment']
end
end
included do
field :hostname,
title: -> { _('Hostname') },
section: Integrations::Base::Integration::SECTION_TYPE_CONNECTION,
help: -> { _('Custom hostname of the Telegram API. The default value is `https://api.telegram.org`.') },
placeholder: 'https://api.telegram.org',
exposes_secrets: true,
required: false
field :token,
title: -> { _('Token') },
section: Integrations::Base::Integration::SECTION_TYPE_CONNECTION,
help: -> { s_('TelegramIntegration|Unique authentication token.') },
non_empty_password_title: -> { s_('TelegramIntegration|New token') },
non_empty_password_help: -> { s_('TelegramIntegration|Leave blank to use your current token.') },
placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
description: -> { _('The Telegram bot token (for example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`).') },
exposes_secrets: true,
is_secret: true,
required: true
field :room,
title: -> { _('Channel identifier') },
section: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
help: -> {
_("Unique identifier for the target chat or the username of the target channel " \
"(in the format `@channelusername`).")
},
placeholder: '@channelusername',
required: true
field :thread,
title: -> { _('Message thread ID') },
section: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
help: -> { _('Unique identifier for the target message thread (topic in a forum supergroup).') },
placeholder: '123',
required: false
field :notify_only_broken_pipelines,
title: -> { _('Notify only broken pipelines') },
type: :checkbox,
section: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
description: -> { _('Send notifications for broken pipelines.') },
help: -> { _('If selected, successful pipelines do not trigger a notification event.') }
field :branches_to_be_notified,
type: :select,
section: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
description: -> {
_('Branches to send notifications for. Valid options are `all`, `default`, `protected`, ' \
'and `default_and_protected`. The default value is `default`.')
},
choices: -> { branch_choices }
with_options if: :activated? do
validates :token, :room, presence: true
validates :thread, numericality: { only_integer: true }, allow_blank: true
end
before_validation :set_webhook
private
def set_webhook
hostname = self.hostname.presence || 'https://api.telegram.org'
self.webhook = format(TELEGRAM_HOSTNAME, hostname: hostname, token: token) if token.present?
end
def notify(message, _opts)
body = {
text: message.summary,
chat_id: room,
message_thread_id: thread,
parse_mode: 'markdown'
}.compact_blank
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
# We're retrying the request with a different format to ensure accurate formatting and
# avoid receiving a 400 response due to invalid markdown.
if response.bad_request?
body.except!(:parse_mode)
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
end
response if response.success?
end
def custom_data(data)
super.merge(markdown: true)
end
end
end
end
end

View File

@ -3,7 +3,7 @@
module Integrations
module Instance
class Telegram < Integration
# To be updated as part of https://gitlab.com/gitlab-org/gitlab/-/issues/474809
include Integrations::Base::Telegram
end
end
end

View File

@ -2,125 +2,6 @@
module Integrations
class Telegram < Integration
include HasAvatar
include Base::ChatNotification
TELEGRAM_HOSTNAME = "%{hostname}/bot%{token}/sendMessage"
field :hostname,
title: -> { _('Hostname') },
section: SECTION_TYPE_CONNECTION,
help: -> { _('Custom hostname of the Telegram API. The default value is `https://api.telegram.org`.') },
placeholder: 'https://api.telegram.org',
exposes_secrets: true,
required: false
field :token,
title: -> { _('Token') },
section: SECTION_TYPE_CONNECTION,
help: -> { s_('TelegramIntegration|Unique authentication token.') },
non_empty_password_title: -> { s_('TelegramIntegration|New token') },
non_empty_password_help: -> { s_('TelegramIntegration|Leave blank to use your current token.') },
placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
description: -> { _('The Telegram bot token (for example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`).') },
exposes_secrets: true,
is_secret: true,
required: true
field :room,
title: -> { _('Channel identifier') },
section: SECTION_TYPE_CONFIGURATION,
help: -> {
_("Unique identifier for the target chat or the username of the target channel " \
"(in the format `@channelusername`).")
},
placeholder: '@channelusername',
required: true
field :thread,
title: -> { _('Message thread ID') },
section: SECTION_TYPE_CONFIGURATION,
help: -> { _('Unique identifier for the target message thread (topic in a forum supergroup).') },
placeholder: '123',
required: false
field :notify_only_broken_pipelines,
title: -> { _('Notify only broken pipelines') },
type: :checkbox,
section: SECTION_TYPE_CONFIGURATION,
description: -> { _('Send notifications for broken pipelines.') },
help: -> { _('If selected, successful pipelines do not trigger a notification event.') }
field :branches_to_be_notified,
type: :select,
section: SECTION_TYPE_CONFIGURATION,
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
description: -> {
_('Branches to send notifications for. Valid options are `all`, `default`, `protected`, ' \
'and `default_and_protected`. The default value is `default`.')
},
choices: -> { branch_choices }
with_options if: :activated? do
validates :token, :room, presence: true
validates :thread, numericality: { only_integer: true }, allow_blank: true
end
before_validation :set_webhook
def self.title
'Telegram'
end
def self.description
s_("TelegramIntegration|Send notifications about project events to Telegram.")
end
def self.to_param
'telegram'
end
def self.help
build_help_page_url(
'user/project/integrations/telegram.md',
s_("TelegramIntegration|Send notifications about project events to Telegram.")
)
end
def self.supported_events
super - ['deployment']
end
private
def set_webhook
hostname = self.hostname.presence || 'https://api.telegram.org'
self.webhook = format(TELEGRAM_HOSTNAME, hostname: hostname, token: token) if token.present?
end
def notify(message, _opts)
body = {
text: message.summary,
chat_id: room,
message_thread_id: thread,
parse_mode: 'markdown'
}.compact_blank
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
# We're retrying the request with a different format to ensure accurate formatting and
# avoid receiving a 400 response due to invalid markdown.
if response.bad_request?
body.except!(:parse_mode)
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
end
response if response.success?
end
def custom_data(data)
super(data).merge(markdown: true)
end
include Integrations::Base::Telegram
end
end

View File

@ -8,14 +8,6 @@ description: Keeps connection between merge request and project approval rule
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8497
milestone: '11.7'
gitlab_schema: gitlab_main_cell
desired_sharding_key:
project_id:
references: projects
backfill_via:
parent:
foreign_key: approval_project_rule_id
table: approval_project_rules
sharding_key: project_id
belongs_to: approval_project_rule
desired_sharding_key_migration_job_name: BackfillApprovalMergeRequestRuleSourcesProjectId
table_size: small
sharding_key:
project_id: projects

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class ValidateApprovalMergeRequestRuleSourcesProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2]
milestone '17.10'
def up
validate_not_null_constraint :approval_merge_request_rule_sources, :project_id
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
f46d1d2ae51d974cb2fd139b82f87b72a3ef4ca666f36630012ffef297f38f38

View File

@ -8534,7 +8534,8 @@ CREATE TABLE approval_merge_request_rule_sources (
id bigint NOT NULL,
approval_merge_request_rule_id bigint NOT NULL,
approval_project_rule_id bigint NOT NULL,
project_id bigint
project_id bigint,
CONSTRAINT check_f82666a937 CHECK ((project_id IS NOT NULL))
);
CREATE SEQUENCE approval_merge_request_rule_sources_id_seq
@ -27257,9 +27258,6 @@ ALTER TABLE project_relation_exports
ALTER TABLE merge_request_blocks
ADD CONSTRAINT check_f8034ca45e CHECK ((project_id IS NOT NULL)) NOT VALID;
ALTER TABLE approval_merge_request_rule_sources
ADD CONSTRAINT check_f82666a937 CHECK ((project_id IS NOT NULL)) NOT VALID;
ALTER TABLE projects
ADD CONSTRAINT check_fa75869cb1 CHECK ((project_namespace_id IS NOT NULL)) NOT VALID;

View File

@ -21,6 +21,12 @@ Sometimes, HAML page is enough to satisfy requirements. This statement is correc
To better explain this, let's imagine the page that has one toggle, and toggling it sends an API request. This case does not involve any state we want to maintain, we send the request and switch the toggle. However, if we add one more toggle that should always be the opposite to the first one, we need a _state_: one toggle should be "aware" about the state of another one. When written in plain JavaScript, this logic usually involves listening to DOM event and reacting with modifying DOM. Cases like this are much easier to handle with Vue.js so we should create a Vue application here.
## How to add a Vue application to a page
1. Create a new folder in `app/assets/javascripts` for your Vue application.
1. Add [page-specific JavaScript](performance.md#page-specific-javascript) to load your application.
1. You can use the [`initSimpleApp helper](#the-initsimpleapp-helper) to simplify [passing data from HAML to JS](#providing-data-from-haml-to-javascript).
### What are some flags signaling that you might need Vue application?
- when you need to define complex conditionals based on multiple factors and update them on user interaction;

View File

@ -8,6 +8,7 @@ describe('add request form', () => {
const findGlFormInput = () => wrapper.findComponent(GlFormInput);
const findGlButton = () => wrapper.findComponent(GlButton);
const findGlSubmit = () => wrapper.findComponent('[type=submit]');
beforeEach(() => {
wrapper = mount(AddRequest);
@ -15,6 +16,7 @@ describe('add request form', () => {
it('hides the input on load', () => {
expect(findGlFormInput().exists()).toBe(false);
expect(findGlSubmit().exists()).toBe(false);
});
describe('when clicking the button', () => {
@ -25,6 +27,7 @@ describe('add request form', () => {
it('shows the form', () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlSubmit().exists()).toBe(true);
});
describe('when pressing escape', () => {
@ -35,6 +38,7 @@ describe('add request form', () => {
it('hides the input', () => {
expect(findGlFormInput().exists()).toBe(false);
expect(findGlSubmit().exists()).toBe(false);
});
});
@ -42,7 +46,7 @@ describe('add request form', () => {
beforeEach(async () => {
findGlFormInput().setValue('http://gitlab.example.com/users/root/calendar.json');
await nextTick();
findGlFormInput().trigger('keyup.enter');
findGlSubmit().trigger('submit');
await nextTick();
});
@ -55,6 +59,7 @@ describe('add request form', () => {
it('hides the input', () => {
expect(findGlFormInput().exists()).toBe(false);
expect(findGlSubmit().exists()).toBe(false);
});
it('clears the value for next time', async () => {

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Integrations::Instance::Telegram, feature_category: :integrations do
it_behaves_like Integrations::Base::Telegram
end

View File

@ -3,99 +3,5 @@
require "spec_helper"
RSpec.describe Integrations::Telegram, feature_category: :integrations do
it_behaves_like Integrations::HasAvatar
it_behaves_like "chat integration", "Telegram" do
let(:payload) do
{
text: be_present
}
end
end
describe 'validations' do
context 'when integration is active' do
before do
subject.activate!
end
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:room) }
it { is_expected.to validate_numericality_of(:thread).only_integer }
end
context 'when integration is inactive' do
before do
subject.deactivate!
end
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:room) }
it { is_expected.not_to validate_numericality_of(:thread).only_integer }
end
end
describe 'before_validation :set_webhook' do
context 'when token is not present' do
let(:integration) { build(:telegram_integration, token: nil) }
it 'does not set webhook value' do
expect(integration.webhook).to eq(nil)
expect(integration).not_to be_valid
end
end
context 'when token is present' do
let(:integration) { build_stubbed(:telegram_integration) }
it 'sets webhook value' do
expect(integration).to be_valid
expect(integration.webhook).to eq("https://api.telegram.org/bot123456:ABC-DEF1234/sendMessage")
end
context 'with custom hostname' do
before do
integration.hostname = 'https://gitlab.example.com'
end
it 'sets webhook value with custom hostname' do
expect(integration).to be_valid
expect(integration.webhook).to eq("https://gitlab.example.com/bot123456:ABC-DEF1234/sendMessage")
end
end
end
end
describe '#notify' do
let(:subject) { build(:telegram_integration) }
let(:message) { instance_double(Integrations::ChatMessage::PushMessage, summary: '_Test message') }
let(:header) { { 'Content-Type' => 'application/json' } }
let(:response) { instance_double(HTTParty::Response, bad_request?: true, success?: true) }
let(:body_1) do
{
text: '_Test message',
chat_id: subject.room,
message_thread_id: subject.thread,
parse_mode: 'markdown'
}.compact_blank
end
let(:body_2) { body_1.without(:parse_mode) }
before do
allow(Gitlab::HTTP).to receive(:post).and_return(response)
end
it 'removes the parse mode if the first request fails with a bad request' do
expect(Gitlab::HTTP).to receive(:post).with(subject.webhook, headers: header, body: Gitlab::Json.dump(body_1))
expect(Gitlab::HTTP).to receive(:post).with(subject.webhook, headers: header, body: Gitlab::Json.dump(body_2))
subject.send(:notify, message, {})
end
it 'makes a second request if the first one fails with a bad request' do
expect(Gitlab::HTTP).to receive(:post).twice
subject.send(:notify, message, {})
end
end
it_behaves_like Integrations::Base::Telegram
end

View File

@ -0,0 +1,102 @@
# frozen_string_literal: true
RSpec.shared_examples Integrations::Base::Telegram do
it_behaves_like Integrations::HasAvatar
it_behaves_like "chat integration", "Telegram" do
let(:payload) do
{
text: be_present
}
end
end
describe 'validations' do
subject(:integration) { build(:telegram_integration) }
context 'when integration is active' do
before do
integration.activate!
end
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:room) }
it { is_expected.to validate_numericality_of(:thread).only_integer }
end
context 'when integration is inactive' do
before do
integration.deactivate!
end
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:room) }
it { is_expected.not_to validate_numericality_of(:thread).only_integer }
end
end
describe 'before_validation :set_webhook' do
context 'when token is not present' do
subject(:integration) { build(:telegram_integration, token: nil) }
it 'does not set webhook value' do
expect(integration.webhook).to be_nil
expect(integration).not_to be_valid
end
end
context 'when token is present' do
subject(:integration) { build_stubbed(:telegram_integration) }
it 'sets webhook value' do
expect(integration).to be_valid
expect(integration.webhook).to eq("https://api.telegram.org/bot123456:ABC-DEF1234/sendMessage")
end
context 'with custom hostname' do
before do
integration.hostname = 'https://gitlab.example.com'
end
it 'sets webhook value with custom hostname' do
expect(integration).to be_valid
expect(integration.webhook).to eq("https://gitlab.example.com/bot123456:ABC-DEF1234/sendMessage")
end
end
end
end
describe '#notify' do
let(:message) { instance_double(Integrations::ChatMessage::PushMessage, summary: '_Test message') }
let(:header) { { 'Content-Type' => 'application/json' } }
let(:response) { instance_double(HTTParty::Response, bad_request?: true, success?: true) }
let(:body_1) do
{
text: '_Test message',
chat_id: integration.room,
message_thread_id: integration.thread,
parse_mode: 'markdown'
}.compact_blank
end
let(:body_2) { body_1.without(:parse_mode) }
subject(:integration) { build(:telegram_integration) }
before do
allow(Gitlab::HTTP).to receive(:post).and_return(response)
end
it 'removes the parse mode if the first request fails with a bad request' do
expect(Gitlab::HTTP).to receive(:post).with(integration.webhook, headers: header, body: Gitlab::Json.dump(body_1))
expect(Gitlab::HTTP).to receive(:post).with(integration.webhook, headers: header, body: Gitlab::Json.dump(body_2))
integration.send(:notify, message, {})
end
it 'makes a second request if the first one fails with a bad request' do
expect(Gitlab::HTTP).to receive(:post).twice
integration.send(:notify, message, {})
end
end
end