Commit 456be7e9 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '345203-polish-slack-application-installation-page' into 'master'

Clean up Slack Application installation page

See merge request gitlab-org/gitlab!78094
parents 3d2d0430 81099a12
...@@ -874,14 +874,6 @@ Image Commenting cursor ...@@ -874,14 +874,6 @@ Image Commenting cursor
$image-comment-cursor-left-offset: 12; $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 12; $image-comment-cursor-top-offset: 12;
/*
Add GitLab Slack Application
*/
$add-to-slack-popup-max-width: 400px;
$add-to-slack-gif-max-width: 850px;
$add-to-slack-well-max-width: 750px;
$add-to-slack-logo-size: 100px;
/* /*
Security & Compliance Carousel Security & Compliance Carousel
*/ */
......
...@@ -363,25 +363,12 @@ table.u2f-registrations { ...@@ -363,25 +363,12 @@ table.u2f-registrations {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
.gitlab-slack-gif { .gitlab-slack-body {
width: 100%; max-width: 420px;
max-width: $add-to-slack-gif-max-width;
} }
.gitlab-slack-well { .gitlab-slack-slack-logo {
background-color: $white; transform: scale(200%); // Slack logo SVG is scaled down 50% and has empty space around it
box-shadow: none;
max-width: $add-to-slack-well-max-width;
}
.gitlab-slack-logo {
width: $add-to-slack-logo-size;
height: $add-to-slack-logo-size;
}
.gitlab-slack-popup {
width: 100%;
max-width: $add-to-slack-popup-max-width;
} }
.skype-icon { .skype-icon {
......
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
import { GlButton, GlIcon } from '@gitlab/ui';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import GitlabSlackService from '../services/gitlab_slack_service';
export default {
components: {
GlButton,
GlIcon,
},
props: {
projects: {
type: Array,
required: false,
default: () => [],
},
isSignedIn: {
type: Boolean,
required: true,
},
gitlabForSlackGifPath: {
type: String,
required: true,
},
signInPath: {
type: String,
required: true,
},
slackLinkPath: {
type: String,
required: true,
},
gitlabLogoPath: {
type: String,
required: true,
},
slackLogoPath: {
type: String,
required: true,
},
docsPath: {
type: String,
required: true,
},
},
data() {
return {
popupOpen: false,
selectedProjectId: this.projects && this.projects.length ? this.projects[0].id : 0,
};
},
computed: {
hasProjects() {
return this.projects.length > 0;
},
},
methods: {
togglePopup() {
this.popupOpen = !this.popupOpen;
},
addToSlack() {
GitlabSlackService.addToSlack(this.slackLinkPath, this.selectedProjectId)
.then((response) => redirectTo(response.data.add_to_slack_link))
.catch(() =>
createFlash({
message: __('Unable to build Slack link.'),
}),
);
},
},
};
</script>
<template>
<div class="text-center">
<h1>{{ __('GitLab for Slack') }}</h1>
<p>{{ __('Track your GitLab projects with GitLab for Slack.') }}</p>
<div v-once class="gl-my-5 gl-display-flex gl-justify-content-center gl-align-items-center">
<img :src="gitlabLogoPath" class="gitlab-slack-logo" />
<gl-icon name="double-headed-arrow" :size="72" class="gl-mx-5 gl-text-gray-200" />
<img :src="slackLogoPath" class="gitlab-slack-logo" />
</div>
<gl-button
category="primary"
variant="success"
class="js-popup-button gl-mt-3"
@click="togglePopup"
>
{{ __('Add GitLab to Slack') }}
</gl-button>
<div
v-if="popupOpen"
class="popup gitlab-slack-popup mx-auto prepend-top-20 text-center js-popup"
>
<div v-if="isSignedIn && hasProjects" class="inline">
<strong>{{ __('Select GitLab project to link with your Slack team') }}</strong>
<select v-model="selectedProjectId" class="js-project-select form-control gl-mt-3 gl-mb-3">
<option v-for="project in projects" :key="project.id" :value="project.id">
{{ project.name }}
</option>
</select>
<gl-button
category="primary"
variant="success"
class="float-right js-add-button"
@click="addToSlack"
>
{{ __('Add to Slack') }}
</gl-button>
</div>
<span v-else-if="isSignedIn && !hasProjects" class="js-no-projects">{{
__("You don't have any projects available.")
}}</span>
<span v-else>
You have to
<a v-once :href="signInPath" class="js-gitlab-slack-sign-in-link">{{ __('log in') }}</a>
</span>
</div>
<div class="center prepend-top-20 gl-mb-3 gl-mr-2 gl-ml-2">
<img v-once :src="gitlabForSlackGifPath" class="gitlab-slack-gif" />
</div>
<div v-once class="text-center">
<h3>{{ __('How it works') }}</h3>
<div class="well gitlab-slack-well mx-auto">
<code class="code mx-auto gl-mb-3"
>/gitlab &lt;project-alias&gt; issue show &lt;id&gt;</code
>
<span>
<gl-icon name="arrow-right" class="gl-mr-2 gl-text-gray-200" />
Shows the issue with id <strong>&lt;id&gt;</strong>
</span>
<div class="gl-mt-3">
<a v-once :href="docsPath">{{ __('More Slack commands') }}</a>
</div>
</div>
</div>
</div>
</template>
import Vue from 'vue';
import AddGitlabSlackApplication from './components/add_gitlab_slack_application.vue';
export default () => {
const el = document.getElementById('js-add-gitlab-slack-application-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-add-gitlab-slack-application-entry-data');
const initialData = JSON.parse(dataNode.innerHTML);
const AddGitlabSlackApplicationComp = Vue.extend(AddGitlabSlackApplication);
new AddGitlabSlackApplicationComp({
propsData: {
projects: initialData.projects,
isSignedIn: initialData.is_signed_in,
gitlabForSlackGifPath: initialData.gitlab_for_slack_gif_path,
signInPath: initialData.sign_in_path,
slackLinkPath: initialData.slack_link_profile_slack_path,
gitlabLogoPath: initialData.gitlab_logo_path,
slackLogoPath: initialData.slack_logo_path,
docsPath: initialData.docs_path,
},
}).$mount(el);
};
import axios from '~/lib/utils/axios_utils';
export default {
addToSlack(url, projectId) {
return axios.get(url, {
params: {
project_id: projectId,
},
});
},
};
import axios from '~/lib/utils/axios_utils';
export const addProjectToSlack = (url, projectId) => {
return axios.get(url, {
params: { project_id: projectId },
});
};
<script>
import { GlButton, GlIcon } from '@gitlab/ui';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { addProjectToSlack } from '../api';
import { i18n } from '../constants';
import ProjectsDropdown from './projects_dropdown.vue';
export default {
components: {
GlButton,
GlIcon,
ProjectsDropdown,
},
props: {
projects: {
type: Array,
required: false,
default: () => [],
},
isSignedIn: {
type: Boolean,
required: true,
},
signInPath: {
type: String,
required: true,
},
slackLinkPath: {
type: String,
required: true,
},
gitlabLogoPath: {
type: String,
required: true,
},
slackLogoPath: {
type: String,
required: true,
},
},
i18n,
data() {
return {
selectedProject: null,
};
},
computed: {
hasProjects() {
return this.projects.length > 0;
},
},
methods: {
selectProject(project) {
this.selectedProject = project;
},
addToSlack() {
addProjectToSlack(this.slackLinkPath, this.selectedProject.id)
.then((response) => redirectTo(response.data.add_to_slack_link))
.catch(() =>
createFlash({
message: i18n.slackErrorMessage,
}),
);
},
},
};
</script>
<template>
<div class="gitlab-slack-body gl-mx-auto gl-mt-11 gl-text-center">
<div v-once class="gl-my-5 gl-display-flex gl-justify-content-center gl-align-items-center">
<img :src="gitlabLogoPath" :alt="$options.i18n.gitlabLogoAlt" class="gl-h-11 gl-w-11" />
<gl-icon name="arrow-right" :size="32" class="gl-mx-5 gl-text-gray-200" />
<img
:src="slackLogoPath"
:alt="$options.i18n.slackLogoAlt"
class="gitlab-slack-slack-logo gl-h-11 gl-w-11"
/>
</div>
<h2>{{ $options.i18n.title }}</h2>
<div class="gl-mt-6" data-testid="gitlab-slack-content">
<template v-if="isSignedIn">
<template v-if="hasProjects">
<p>
{{ $options.i18n.dropdownLabel }}
</p>
<projects-dropdown
:projects="projects"
:selected-project="selectedProject"
@project-selected="selectProject"
/>
<div class="gl-display-flex gl-justify-content-end">
<gl-button
category="primary"
variant="confirm"
:disabled="!selectedProject"
@click="addToSlack"
>
{{ $options.i18n.dropdownButtonText }}
</gl-button>
</div>
</template>
<p v-else>{{ $options.i18n.noProjects }}</p>
</template>
<template v-else>
<p>{{ $options.i18n.signInLabel }}</p>
<gl-button category="primary" variant="confirm" :href="signInPath">
{{ $options.i18n.signInButtonText }}
</gl-button>
</template>
</div>
</div>
</template>
<script>
import { GlDropdown } from '@gitlab/ui';
import { __ } from '~/locale';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
export default {
components: {
GlDropdown,
ProjectListItem,
},
props: {
projectDropdownText: {
type: String,
required: false,
default: __('Select a project'),
},
projects: {
type: Array,
required: false,
default: () => [],
},
selectedProject: {
type: Object,
required: false,
default: null,
},
},
computed: {
dropdownText() {
return this.selectedProject
? this.selectedProject.name_with_namespace
: this.projectDropdownText;
},
},
methods: {
onClick(project) {
this.$emit('project-selected', project);
this.$refs.dropdown.hide(true);
},
},
};
</script>
<template>
<gl-dropdown ref="dropdown" block :text="dropdownText" menu-class="gl-w-full!">
<project-list-item
v-for="project in projects"
:key="project.id"
:project="project"
:selected="false"
@click="onClick(project)"
/>
</gl-dropdown>
</template>
import { __, s__ } from '~/locale';
export const i18n = {
slackErrorMessage: __('Unable to build Slack link.'),
gitlabLogoAlt: __('GitLab logo'),
slackLogoAlt: __('Slack logo'),
title: s__('SlackIntegration|GitLab for Slack'),
dropdownLabel: s__('SlackIntegration|Select a GitLab project to link with your Slack workspace.'),
dropdownButtonText: __('Continue'),
noProjects: __("You don't have any projects available."),
signInLabel: s__('JiraService|Sign in to GitLab.com to get started.'),
signInButtonText: __('Sign in to GitLab'),
};
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import GitlabSlackApplication from './components/gitlab_slack_application.vue';
export default () => {
const el = document.querySelector('.js-gitlab-slack-application');
if (!el) return null;
const {
projects,
isSignedIn,
signInPath,
slackLinkPath,
gitlabLogoPath,
slackLogoPath,
} = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(GitlabSlackApplication, {
props: {
projects: JSON.parse(projects),
isSignedIn: parseBoolean(isSignedIn),
signInPath,
slackLinkPath,
gitlabLogoPath,
slackLogoPath,
},
});
},
});
};
import mountAddGitlabSlackApplication from 'ee/add_gitlab_slack_application'; import initGitlabSlackApplication from 'ee/integrations/gitlab_slack_application';
mountAddGitlabSlackApplication(); initGitlabSlackApplication();
...@@ -10,7 +10,7 @@ class Profiles::SlacksController < Profiles::ApplicationController ...@@ -10,7 +10,7 @@ class Profiles::SlacksController < Profiles::ApplicationController
feature_category :users feature_category :users
def edit def edit
@projects = disabled_projects.select([:id, :name]) if current_user @projects = disabled_projects.inc_routes if current_user
end end
def slack_link def slack_link
......
...@@ -34,17 +34,15 @@ module EE ...@@ -34,17 +34,15 @@ module EE
"https://slack.com/oauth/authorize?scope=commands&client_id=#{slack_app_id}&redirect_uri=#{slack_auth_project_settings_slack_url(project)}&state=#{escaped_form_authenticity_token}" "https://slack.com/oauth/authorize?scope=commands&client_id=#{slack_app_id}&redirect_uri=#{slack_auth_project_settings_slack_url(project)}&state=#{escaped_form_authenticity_token}"
end end
def add_to_slack_data(projects) def gitlab_slack_application_data(projects)
{ {
projects: projects.as_json(only: [:id, :name]), projects: (projects || []).to_json(only: [:id, :name], methods: [:avatar_url, :name_with_namespace]),
sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'), sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'),
is_signed_in: current_user.present?, is_signed_in: current_user.present?.to_s,
slack_link_profile_slack_path: slack_link_profile_slack_path, slack_link_path: slack_link_profile_slack_path,
gitlab_for_slack_gif_path: image_path('gitlab_for_slack.gif'),
gitlab_logo_path: image_path('illustrations/gitlab_logo.svg'), gitlab_logo_path: image_path('illustrations/gitlab_logo.svg'),
slack_logo_path: image_path('illustrations/slack_logo.svg'), slack_logo_path: image_path('illustrations/slack_logo.svg')
docs_path: help_page_path('user/project/integrations/gitlab_slack_application.md') }
}.to_json.html_safe
end end
def escaped_form_authenticity_token def escaped_form_authenticity_token
......
-# haml-lint:disable InlineJavaScript - @hide_breadcrumbs = true
%script#js-add-gitlab-slack-application-entry-data{ type: "application/json" } - @hide_top_links = true
= add_to_slack_data(@projects) - @content_class = 'limit-container-width'
- page_title s_('SlackIntegration|GitLab for Slack')
#js-add-gitlab-slack-application-entry-point .js-gitlab-slack-application{ data: gitlab_slack_application_data(@projects) }
import Vue from 'vue';
import addGitlabSlackApplication from 'ee/add_gitlab_slack_application/components/add_gitlab_slack_application.vue';
import GitlabSlackService from 'ee/add_gitlab_slack_application/services/gitlab_slack_service';
import mountComponent from 'helpers/vue_mount_component_helper';
import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
describe('AddGitlabSlackApplication', () => {
const redirectLink = '//redirectLink';
const gitlabForSlackGifPath = '//gitlabForSlackGifPath';
const signInPath = '//signInPath';
const slackLinkPath = '//slackLinkPath';
const docsPath = '//docsPath';
const gitlabLogoPath = '//gitlabLogoPath';
const slackLogoPath = '//slackLogoPath';
const projects = [
{
id: 4,
name: 'test',
},
{
id: 6,
name: 'nope',
},
];
const DEFAULT_PROPS = {
projects,
gitlabForSlackGifPath,
signInPath,
slackLinkPath,
docsPath,
gitlabLogoPath,
slackLogoPath,
isSignedIn: false,
};
const AddGitlabSlackApplication = Vue.extend(addGitlabSlackApplication);
it('opens popup when button is clicked', () => {
const vm = mountComponent(AddGitlabSlackApplication, DEFAULT_PROPS);
vm.$el.querySelector('.js-popup-button').click();
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.js-popup')).toBeDefined();
});
});
it('hides popup when button is clicked', () => {
const vm = mountComponent(AddGitlabSlackApplication, DEFAULT_PROPS);
vm.popupOpen = true;
return vm
.$nextTick()
.then(() => vm.$el.querySelector('.js-popup-button').click())
.then(vm.$nextTick)
.then(() => {
expect(vm.$el.querySelector('.js-popup')).toBeNull();
});
});
it('popup has a project select when signed in', () => {
const vm = mountComponent(AddGitlabSlackApplication, {
...DEFAULT_PROPS,
isSignedIn: true,
});
vm.popupOpen = true;
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.js-project-select')).toBeDefined();
});
});
it('popup has a message when there is no projects', () => {
const vm = mountComponent(AddGitlabSlackApplication, {
...DEFAULT_PROPS,
projects: [],
isSignedIn: true,
});
vm.popupOpen = true;
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.js-no-projects').textContent).toMatch(
"You don't have any projects available.",
);
});
});
it('popup has a sign in link when logged out', () => {
const vm = mountComponent(AddGitlabSlackApplication, {
...DEFAULT_PROPS,
});
vm.popupOpen = true;
vm.selectedProjectId = 4;
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.js-gitlab-slack-sign-in-link').href).toMatch(
new RegExp(signInPath, 'i'),
);
});
});
it('redirects user to external link when submitted', () => {
const vm = mountComponent(AddGitlabSlackApplication, {
...DEFAULT_PROPS,
isSignedIn: true,
});
const addToSlackPromise = Promise.resolve({ data: { add_to_slack_link: redirectLink } });
jest.spyOn(GitlabSlackService, 'addToSlack').mockReturnValue(addToSlackPromise);
vm.popupOpen = true;
return vm
.$nextTick()
.then(() => vm.$el.querySelector('.js-add-button').click())
.then(vm.$nextTick)
.then(addToSlackPromise)
.then(() => {
expect(redirectTo).toHaveBeenCalledWith(redirectLink);
});
});
});
import { GlButton } from '@gitlab/ui';
import GitlabSlackApplication from 'ee/integrations/gitlab_slack_application/components/gitlab_slack_application.vue';
import { addProjectToSlack } from 'ee/integrations/gitlab_slack_application/api';
import { i18n } from 'ee/integrations/gitlab_slack_application/constants';
import ProjectsDropdown from 'ee/integrations/gitlab_slack_application/components/projects_dropdown.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import { mockProjects } from '../mock_data';
jest.mock('ee/integrations/gitlab_slack_application/api');
jest.mock('~/lib/utils/url_utility');
describe('GitlabSlackApplication', () => {
let wrapper;
const defaultProps = {
projects: [],
gitlabForSlackGifPath: '//gitlabForSlackGifPath',
signInPath: '//signInPath',
slackLinkPath: '//slackLinkPath',
docsPath: '//docsPath',
gitlabLogoPath: '//gitlabLogoPath',
slackLogoPath: '//slackLogoPath',
isSignedIn: true,
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(GitlabSlackApplication, {
propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
const findGlButton = () => wrapper.findComponent(GlButton);
const findProjectsDropdown = () => wrapper.findComponent(ProjectsDropdown);
const findAppContent = () => wrapper.findByTestId('gitlab-slack-content');
describe('template', () => {
describe('when user is not signed in', () => {
it('renders "Sign in" button', () => {
createComponent({
props: { isSignedIn: false },
});
expect(findGlButton().attributes('href')).toBe(defaultProps.signInPath);
});
});
describe('when user is signed in', () => {
describe('user does not have any projects', () => {
it('renders empty text', () => {
createComponent();
expect(findAppContent().text()).toBe(i18n.noProjects);
});
});
describe('user has projects', () => {
beforeEach(() => {
createComponent({
props: {
projects: mockProjects,
},
});
});
it('renders ProjectsDropdown', () => {
expect(findProjectsDropdown().props('projects')).toBe(mockProjects);
});
it('redirects to slackLinkPath when submitted', async () => {
const redirectLink = '//redirectLink';
const mockProject = mockProjects[1];
const addToSlackData = { data: { add_to_slack_link: redirectLink } };
addProjectToSlack.mockResolvedValue(addToSlackData);
findProjectsDropdown().vm.$emit('project-selected', mockProject);
await wrapper.vm.$nextTick();
expect(findProjectsDropdown().props('selectedProject')).toBe(mockProject);
expect(findGlButton().props('disabled')).toBe(false);
findGlButton().vm.$emit('click');
await waitForPromises();
expect(redirectTo).toHaveBeenCalledWith(redirectLink);
});
});
});
});
});
export const mockProjects = [
{
id: 1,
name: 'Test',
avatar_url: 'avatar.jpg',
name_with_namespace: 'Test org / Test',
},
{
id: 2,
name: 'Shell',
avatar_url: 'avatar.jpg',
name_with_namespace: 'Test org / Shell',
},
];
...@@ -108,11 +108,11 @@ RSpec.describe EE::IntegrationsHelper do ...@@ -108,11 +108,11 @@ RSpec.describe EE::IntegrationsHelper do
end end
end end
describe '#add_to_slack_data' do describe '#gitlab_slack_application_data' do
let_it_be(:projects) { create_list(:project, 3) } let_it_be(:projects) { create_list(:project, 3) }
def relation def relation
Project.id_in(projects.pluck(:id)) Project.id_in(projects.pluck(:id)).inc_routes
end end
let(:request) do let(:request) do
...@@ -132,29 +132,27 @@ RSpec.describe EE::IntegrationsHelper do ...@@ -132,29 +132,27 @@ RSpec.describe EE::IntegrationsHelper do
end end
it 'includes the required keys' do it 'includes the required keys' do
additions = Gitlab::Json.parse(subject.add_to_slack_data(relation)) additions = subject.gitlab_slack_application_data(relation)
expect(additions.keys).to match_array %w[ expect(additions.keys).to include(
projects :projects,
sign_in_path :sign_in_path,
is_signed_in :is_signed_in,
slack_link_profile_slack_path :slack_link_path,
gitlab_for_slack_gif_path :gitlab_logo_path,
gitlab_logo_path :slack_logo_path
slack_logo_path )
docs_path
]
end end
it 'does not suffer from N+1 performance issues' do it 'does not suffer from N+1 performance issues' do
baseline = ActiveRecord::QueryRecorder.new { subject.add_to_slack_data(relation.limit(1)) } baseline = ActiveRecord::QueryRecorder.new { subject.gitlab_slack_application_data(relation.limit(1)) }
expect do expect do
subject.add_to_slack_data(relation) subject.gitlab_slack_application_data(relation)
end.not_to exceed_query_limit(baseline) end.not_to exceed_query_limit(baseline)
end end
it 'serializes nil projects without error' do it 'serializes nil projects without error' do
expect(subject.add_to_slack_data(nil)).to include('"projects":null') expect(subject.gitlab_slack_application_data(nil)).to include(projects: '[]')
end end
end end
end end
...@@ -1982,9 +1982,6 @@ msgstr "" ...@@ -1982,9 +1982,6 @@ msgstr ""
msgid "Add CONTRIBUTING" msgid "Add CONTRIBUTING"
msgstr "" msgstr ""
msgid "Add GitLab to Slack"
msgstr ""
msgid "Add Jaeger URL" msgid "Add Jaeger URL"
msgstr "" msgstr ""
...@@ -2198,9 +2195,6 @@ msgstr "" ...@@ -2198,9 +2195,6 @@ msgstr ""
msgid "Add text to the sign-in page. Markdown enabled." msgid "Add text to the sign-in page. Markdown enabled."
msgstr "" msgstr ""
msgid "Add to Slack"
msgstr ""
msgid "Add to board" msgid "Add to board"
msgstr "" msgstr ""
...@@ -16166,9 +16160,6 @@ msgstr "" ...@@ -16166,9 +16160,6 @@ msgstr ""
msgid "GitLab export" msgid "GitLab export"
msgstr "" msgstr ""
msgid "GitLab for Slack"
msgstr ""
msgid "GitLab group: %{source_link}" msgid "GitLab group: %{source_link}"
msgstr "" msgstr ""
...@@ -16193,6 +16184,9 @@ msgstr "" ...@@ -16193,6 +16184,9 @@ msgstr ""
msgid "GitLab is undergoing maintenance and is operating in read-only mode." msgid "GitLab is undergoing maintenance and is operating in read-only mode."
msgstr "" msgstr ""
msgid "GitLab logo"
msgstr ""
msgid "GitLab member or Email address" msgid "GitLab member or Email address"
msgstr "" msgstr ""
...@@ -17724,9 +17718,6 @@ msgstr "" ...@@ -17724,9 +17718,6 @@ msgstr ""
msgid "How do I use file templates?" msgid "How do I use file templates?"
msgstr "" msgstr ""
msgid "How it works"
msgstr ""
msgid "How many days need to pass between marking entity for deletion and actual removing it." msgid "How many days need to pass between marking entity for deletion and actual removing it."
msgstr "" msgstr ""
...@@ -23140,9 +23131,6 @@ msgstr "" ...@@ -23140,9 +23131,6 @@ msgstr ""
msgid "More Information" msgid "More Information"
msgstr "" msgstr ""
msgid "More Slack commands"
msgstr ""
msgid "More actions" msgid "More actions"
msgstr "" msgstr ""
...@@ -32065,9 +32053,6 @@ msgstr "" ...@@ -32065,9 +32053,6 @@ msgstr ""
msgid "Select Git revision" msgid "Select Git revision"
msgstr "" msgstr ""
msgid "Select GitLab project to link with your Slack team"
msgstr ""
msgid "Select Page" msgid "Select Page"
msgstr "" msgstr ""
...@@ -33118,6 +33103,15 @@ msgstr "" ...@@ -33118,6 +33103,15 @@ msgstr ""
msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window." msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
msgstr "" msgstr ""
msgid "Slack logo"
msgstr ""
msgid "SlackIntegration|GitLab for Slack"
msgstr ""
msgid "SlackIntegration|Select a GitLab project to link with your Slack workspace."
msgstr ""
msgid "SlackIntegration|Sends notifications about project events to Slack channels." msgid "SlackIntegration|Sends notifications about project events to Slack channels."
msgstr "" msgstr ""
...@@ -37464,9 +37458,6 @@ msgstr "" ...@@ -37464,9 +37458,6 @@ msgstr ""
msgid "Track time with quick actions" msgid "Track time with quick actions"
msgstr "" msgstr ""
msgid "Track your GitLab projects with GitLab for Slack."
msgstr ""
msgid "Training mode" msgid "Training mode"
msgstr "" msgstr ""
...@@ -42391,9 +42382,6 @@ msgstr "" ...@@ -42391,9 +42382,6 @@ msgstr ""
msgid "locked by %{path_lock_user_name} %{created_at}" msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "" msgstr ""
msgid "log in"
msgstr ""
msgid "manual" msgid "manual"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment