Commit 02c976e5 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 14a9b920 f80e7b91
<script> <script>
import { GlTabs, GlTab } from '@gitlab/ui'; import { GlTabs, GlTab } from '@gitlab/ui';
import { CLUSTERS_TABS, MAX_CLUSTERS_LIST, MAX_LIST_COUNT, AGENT } from '../constants'; import Tracking from '~/tracking';
import {
CLUSTERS_TABS,
MAX_CLUSTERS_LIST,
MAX_LIST_COUNT,
AGENT,
EVENT_LABEL_TABS,
EVENT_ACTIONS_CHANGE,
} from '../constants';
import Agents from './agents.vue'; import Agents from './agents.vue';
import InstallAgentModal from './install_agent_modal.vue'; import InstallAgentModal from './install_agent_modal.vue';
import ClustersActions from './clusters_actions.vue'; import ClustersActions from './clusters_actions.vue';
import Clusters from './clusters.vue'; import Clusters from './clusters.vue';
import ClustersViewAll from './clusters_view_all.vue'; import ClustersViewAll from './clusters_view_all.vue';
const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_TABS });
export default { export default {
components: { components: {
GlTabs, GlTabs,
...@@ -18,6 +28,7 @@ export default { ...@@ -18,6 +28,7 @@ export default {
InstallAgentModal, InstallAgentModal,
}, },
CLUSTERS_TABS, CLUSTERS_TABS,
mixins: [trackingMixin],
props: { props: {
defaultBranchName: { defaultBranchName: {
default: '.noBranch', default: '.noBranch',
...@@ -34,9 +45,12 @@ export default { ...@@ -34,9 +45,12 @@ export default {
methods: { methods: {
onTabChange(tabName) { onTabChange(tabName) {
this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName); this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName);
this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST; this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST;
}, },
trackTabChange(tab) {
const tabName = CLUSTERS_TABS[tab].queryParamValue;
this.track(EVENT_ACTIONS_CHANGE, { property: tabName });
},
}, },
}; };
</script> </script>
...@@ -47,6 +61,7 @@ export default { ...@@ -47,6 +61,7 @@ export default {
sync-active-tab-with-query-params sync-active-tab-with-query-params
nav-class="gl-flex-grow-1 gl-align-items-center" nav-class="gl-flex-grow-1 gl-align-items-center"
lazy lazy
@input="trackTabChange"
> >
<gl-tab <gl-tab
v-for="(tab, idx) in $options.CLUSTERS_TABS" v-for="(tab, idx) in $options.CLUSTERS_TABS"
......
...@@ -11,8 +11,19 @@ import { ...@@ -11,8 +11,19 @@ import {
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue'; import CodeBlock from '~/vue_shared/components/code_block.vue';
import Tracking from '~/tracking';
import { generateAgentRegistrationCommand } from '../clusters_util'; import { generateAgentRegistrationCommand } from '../clusters_util';
import { INSTALL_AGENT_MODAL_ID, I18N_AGENT_MODAL, KAS_DISABLED_ERROR } from '../constants'; import {
INSTALL_AGENT_MODAL_ID,
I18N_AGENT_MODAL,
KAS_DISABLED_ERROR,
EVENT_LABEL_MODAL,
EVENT_ACTIONS_OPEN,
EVENT_ACTIONS_SELECT,
EVENT_ACTIONS_CLICK,
MODAL_TYPE_EMPTY,
MODAL_TYPE_REGISTER,
} from '../constants';
import { addAgentToStore, addAgentConfigToStore } from '../graphql/cache_update'; import { addAgentToStore, addAgentConfigToStore } from '../graphql/cache_update';
import createAgent from '../graphql/mutations/create_agent.mutation.graphql'; import createAgent from '../graphql/mutations/create_agent.mutation.graphql';
import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql'; import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
...@@ -20,8 +31,13 @@ import getAgentsQuery from '../graphql/queries/get_agents.query.graphql'; ...@@ -20,8 +31,13 @@ import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql'; import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql';
import AvailableAgentsDropdown from './available_agents_dropdown.vue'; import AvailableAgentsDropdown from './available_agents_dropdown.vue';
const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_MODAL });
export default { export default {
modalId: INSTALL_AGENT_MODAL_ID, modalId: INSTALL_AGENT_MODAL_ID,
EVENT_ACTIONS_OPEN,
EVENT_ACTIONS_CLICK,
EVENT_LABEL_MODAL,
components: { components: {
AvailableAgentsDropdown, AvailableAgentsDropdown,
ClipboardButton, ClipboardButton,
...@@ -34,6 +50,7 @@ export default { ...@@ -34,6 +50,7 @@ export default {
GlModal, GlModal,
GlSprintf, GlSprintf,
}, },
mixins: [trackingMixin],
inject: ['projectPath', 'kasAddress', 'emptyStateImage'], inject: ['projectPath', 'kasAddress', 'emptyStateImage'],
props: { props: {
defaultBranchName: { defaultBranchName: {
...@@ -81,7 +98,7 @@ export default { ...@@ -81,7 +98,7 @@ export default {
return !this.registering && this.agentName !== null; return !this.registering && this.agentName !== null;
}, },
canCancel() { canCancel() {
return !this.registered && !this.registering && this.isRegisterModal; return !this.registered && !this.registering && this.isAgentRegistrationModal;
}, },
agentRegistrationCommand() { agentRegistrationCommand() {
return generateAgentRegistrationCommand(this.agentToken, this.kasAddress); return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
...@@ -117,21 +134,24 @@ export default { ...@@ -117,21 +134,24 @@ export default {
return `/${this.projectPath}`; return `/${this.projectPath}`;
}, },
modalType() { modalType() {
return !this.availableAgents?.length && !this.registered ? 'install' : 'register'; return !this.availableAgents?.length && !this.registered
? MODAL_TYPE_EMPTY
: MODAL_TYPE_REGISTER;
}, },
modalSize() { modalSize() {
return this.isInstallModal ? 'sm' : 'md'; return this.isEmptyStateModal ? 'sm' : 'md';
}, },
isInstallModal() { isEmptyStateModal() {
return this.modalType === 'install'; return this.modalType === MODAL_TYPE_EMPTY;
}, },
isRegisterModal() { isAgentRegistrationModal() {
return this.modalType === 'register'; return this.modalType === MODAL_TYPE_REGISTER;
}, },
}, },
methods: { methods: {
setAgentName(name) { setAgentName(name) {
this.agentName = name; this.agentName = name;
this.track(EVENT_ACTIONS_SELECT);
}, },
closeModal() { closeModal() {
this.$refs.modal.hide(); this.$refs.modal.hide();
...@@ -242,8 +262,9 @@ export default { ...@@ -242,8 +262,9 @@ export default {
static static
lazy lazy
@hidden="resetModal" @hidden="resetModal"
@show="track($options.EVENT_ACTIONS_OPEN, { property: modalType })"
> >
<template v-if="isRegisterModal"> <template v-if="isAgentRegistrationModal">
<template v-if="!registered"> <template v-if="!registered">
<p> <p>
<strong>{{ i18n.selectAgentTitle }}</strong> <strong>{{ i18n.selectAgentTitle }}</strong>
...@@ -347,23 +368,40 @@ export default { ...@@ -347,23 +368,40 @@ export default {
</template> </template>
<template #modal-footer> <template #modal-footer>
<gl-button v-if="canCancel" @click="closeModal">{{ i18n.cancel }} </gl-button> <gl-button
v-if="canCancel"
:data-track-action="$options.EVENT_ACTIONS_CLICK"
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="cancel"
@click="closeModal"
>{{ i18n.cancel }}
</gl-button>
<gl-button v-if="registered" variant="confirm" category="primary" @click="closeModal" <gl-button
v-if="registered"
variant="confirm"
category="primary"
:data-track-action="$options.EVENT_ACTIONS_CLICK"
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="close"
@click="closeModal"
>{{ i18n.close }} >{{ i18n.close }}
</gl-button> </gl-button>
<gl-button <gl-button
v-else-if="isRegisterModal" v-else-if="isAgentRegistrationModal"
:disabled="!nextButtonDisabled" :disabled="!nextButtonDisabled"
variant="confirm" variant="confirm"
category="primary" category="primary"
:data-track-action="$options.EVENT_ACTIONS_CLICK"
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="register"
@click="registerAgent" @click="registerAgent"
>{{ i18n.registerAgentButton }} >{{ i18n.registerAgentButton }}
</gl-button> </gl-button>
<gl-button <gl-button
v-if="isInstallModal" v-if="isEmptyStateModal"
:href="repositoryPath" :href="repositoryPath"
variant="confirm" variant="confirm"
category="secondary" category="secondary"
...@@ -371,7 +409,14 @@ export default { ...@@ -371,7 +409,14 @@ export default {
>{{ i18n.secondaryButton }} >{{ i18n.secondaryButton }}
</gl-button> </gl-button>
<gl-button v-if="isInstallModal" variant="confirm" category="primary" @click="closeModal" <gl-button
v-if="isEmptyStateModal"
variant="confirm"
category="primary"
:data-track-action="$options.EVENT_ACTIONS_CLICK"
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="done"
@click="closeModal"
>{{ i18n.done }} >{{ i18n.done }}
</gl-button> </gl-button>
</template> </template>
......
...@@ -65,7 +65,7 @@ export const STATUSES = { ...@@ -65,7 +65,7 @@ export const STATUSES = {
}; };
export const I18N_AGENT_MODAL = { export const I18N_AGENT_MODAL = {
register: { agent_registration: {
registerAgentButton: s__('ClusterAgents|Register Agent'), registerAgentButton: s__('ClusterAgents|Register Agent'),
close: __('Close'), close: __('Close'),
cancel: __('Cancel'), cancel: __('Cancel'),
...@@ -104,7 +104,7 @@ export const I18N_AGENT_MODAL = { ...@@ -104,7 +104,7 @@ export const I18N_AGENT_MODAL = {
registrationErrorTitle: __('Failed to register Agent'), registrationErrorTitle: __('Failed to register Agent'),
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'), unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
}, },
install: { empty_state: {
modalTitle: s__('ClusterAgents|Install new Agent'), modalTitle: s__('ClusterAgents|Install new Agent'),
modalBody: s__( modalBody: s__(
'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.', 'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
...@@ -236,3 +236,13 @@ export const CLUSTERS_ACTIONS = { ...@@ -236,3 +236,13 @@ export const CLUSTERS_ACTIONS = {
export const AGENT = 'agent'; export const AGENT = 'agent';
export const CERTIFICATE_BASED = 'certificate_based'; export const CERTIFICATE_BASED = 'certificate_based';
export const EVENT_LABEL_MODAL = 'agent_registration_modal';
export const EVENT_LABEL_TABS = 'kubernetes_section_tabs';
export const EVENT_ACTIONS_OPEN = 'open_modal';
export const EVENT_ACTIONS_SELECT = 'select_agent';
export const EVENT_ACTIONS_CLICK = 'click_button';
export const EVENT_ACTIONS_CHANGE = 'change_tab';
export const MODAL_TYPE_EMPTY = 'empty_state';
export const MODAL_TYPE_REGISTER = 'agent_registration';
...@@ -67,7 +67,7 @@ export default { ...@@ -67,7 +67,7 @@ export default {
data-qa-selector="dropdown_button" data-qa-selector="dropdown_button"
@click.stop="openDropdown()" @click.stop="openDropdown()"
> >
<gl-icon name="ellipsis_v" /> <gl-icon name="chevron-down" /> <gl-icon name="ellipsis_v" />
</button> </button>
<ul ref="dropdownMenu" class="dropdown-menu dropdown-menu-right"> <ul ref="dropdownMenu" class="dropdown-menu dropdown-menu-right">
<template v-if="type === 'tree'"> <template v-if="type === 'tree'">
......
...@@ -22,8 +22,7 @@ This tutorial assumes you are familiar with GitLab CI/CD and Vault. ...@@ -22,8 +22,7 @@ This tutorial assumes you are familiar with GitLab CI/CD and Vault.
To follow along, you must have: To follow along, you must have:
- An account on GitLab. - An account on GitLab.
- A running Vault server and access to it is required to configure authentication and create roles - Access to a running Vault server (at least v1.2.0) to configure authentication and to create roles and policies. For HashiCorp Vaults, this can be the Open Source or Enterprise version.
and policies. For HashiCorp Vaults, this can be the Open Source or Enterprise version.
NOTE: NOTE:
You must replace the `vault.example.com` URL below with the URL of your Vault server, and `gitlab.example.com` with the URL of your GitLab instance. You must replace the `vault.example.com` URL below with the URL of your Vault server, and `gitlab.example.com` with the URL of your GitLab instance.
......
...@@ -53,6 +53,7 @@ and supports multiple secrets engines. ...@@ -53,6 +53,7 @@ and supports multiple secrets engines.
To configure your Vault server: To configure your Vault server:
1. Ensure your Vault server is running on version 1.2.0 or higher.
1. Enable the authentication method by running these commands. They provide your Vault 1. Enable the authentication method by running these commands. They provide your Vault
server the [JSON Web Key Set](https://tools.ietf.org/html/rfc7517) (JWKS) endpoint for your GitLab instance, so Vault server the [JSON Web Key Set](https://tools.ietf.org/html/rfc7517) (JWKS) endpoint for your GitLab instance, so Vault
can fetch the public signing key and verify the JSON Web Token (JWT) when authenticating: can fetch the public signing key and verify the JSON Web Token (JWT) when authenticating:
......
...@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
INFO: INFO:
Want to try out container scanning? Want to try out container scanning?
[Get a free 30-day trial GitLab Ultimate](https://about.gitlab.com/free-trial?glm_source=docs.gitlab.com&glm_content=u-container-scanning-docs). [Get a free 30-day trial of GitLab Ultimate](https://about.gitlab.com/free-trial?glm_source=docs.gitlab.com&glm_content=u-container-scanning-docs).
Your application's Docker image may itself be based on Docker images that contain known Your application's Docker image may itself be based on Docker images that contain known
vulnerabilities. By including an extra job in your pipeline that scans for those vulnerabilities and vulnerabilities. By including an extra job in your pipeline that scans for those vulnerabilities and
...@@ -135,6 +135,7 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u ...@@ -135,6 +135,7 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u
| `CI_APPLICATION_TAG` | `$CI_COMMIT_SHA` | Docker repository tag for the image to be scanned. | All | | `CI_APPLICATION_TAG` | `$CI_COMMIT_SHA` | Docker repository tag for the image to be scanned. | All |
| `CS_ANALYZER_IMAGE` | `registry.gitlab.com/security-products/container-scanning:4` | Docker image of the analyzer. | All | | `CS_ANALYZER_IMAGE` | `registry.gitlab.com/security-products/container-scanning:4` | Docker image of the analyzer. | All |
| `CS_DEFAULT_BRANCH_IMAGE` | `""` | The name of the `DOCKER_IMAGE` on the default branch. See [Setting the default branch image](#setting-the-default-branch-image) for more details. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/338877) in GitLab 14.5. | All | | `CS_DEFAULT_BRANCH_IMAGE` | `""` | The name of the `DOCKER_IMAGE` on the default branch. See [Setting the default branch image](#setting-the-default-branch-image) for more details. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/338877) in GitLab 14.5. | All |
| `CS_DISABLE_DEPENDENCY_SCAN` | `"true"` | Disable Dependency Scanning for packages installed in the scanned image. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345434) in GitLab 14.6. | All |
| `CS_DOCKER_INSECURE` | `"false"` | Allow access to secure Docker registries using HTTPS without validating the certificates. | All | | `CS_DOCKER_INSECURE` | `"false"` | Allow access to secure Docker registries using HTTPS without validating the certificates. | All |
| `CS_REGISTRY_INSECURE` | `"false"` | Allow access to insecure registries (HTTP only). Should only be set to `true` when testing the image locally. Works with all scanners, but the registry must listen on port `80/tcp` for Trivy to work. | All | | `CS_REGISTRY_INSECURE` | `"false"` | Allow access to insecure registries (HTTP only). Should only be set to `true` when testing the image locally. Works with all scanners, but the registry must listen on port `80/tcp` for Trivy to work. | All |
| `CS_SEVERITY_THRESHOLD` | `UNKNOWN` | Severity level threshold. The scanner outputs vulnerabilities with severity level higher than or equal to this threshold. Supported levels are Unknown, Low, Medium, High, and Critical. | Trivy | | `CS_SEVERITY_THRESHOLD` | `UNKNOWN` | Severity level threshold. The scanner outputs vulnerabilities with severity level higher than or equal to this threshold. Supported levels are Unknown, Low, Medium, High, and Critical. | Trivy |
......
...@@ -98,8 +98,11 @@ RSpec.describe Security::SecurityOrchestrationPolicies::CiConfigurationService d ...@@ -98,8 +98,11 @@ RSpec.describe Security::SecurityOrchestrationPolicies::CiConfigurationService d
stage: 'test', stage: 'test',
allow_failure: true, allow_failure: true,
artifacts: { artifacts: {
reports: { container_scanning: 'gl-container-scanning-report.json' }, reports: {
paths: ['gl-container-scanning-report.json'] container_scanning: 'gl-container-scanning-report.json',
dependency_scanning: 'gl-dependency-scanning-report.json'
},
paths: ['gl-container-scanning-report.json', 'gl-dependency-scanning-report.json']
}, },
dependencies: [], dependencies: [],
script: ['gtcs scan'], script: ['gtcs scan'],
......
...@@ -38,7 +38,8 @@ container_scanning: ...@@ -38,7 +38,8 @@ container_scanning:
artifacts: artifacts:
reports: reports:
container_scanning: gl-container-scanning-report.json container_scanning: gl-container-scanning-report.json
paths: [gl-container-scanning-report.json] dependency_scanning: gl-dependency-scanning-report.json
paths: [gl-container-scanning-report.json, gl-dependency-scanning-report.json]
dependencies: [] dependencies: []
script: script:
- gtcs scan - gtcs scan
......
import { GlTabs, GlTab } from '@gitlab/ui'; import { GlTabs, GlTab } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import ClustersMainView from '~/clusters_list/components/clusters_main_view.vue'; import ClustersMainView from '~/clusters_list/components/clusters_main_view.vue';
import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue';
import { import {
...@@ -8,12 +9,15 @@ import { ...@@ -8,12 +9,15 @@ import {
CLUSTERS_TABS, CLUSTERS_TABS,
MAX_CLUSTERS_LIST, MAX_CLUSTERS_LIST,
MAX_LIST_COUNT, MAX_LIST_COUNT,
EVENT_LABEL_TABS,
EVENT_ACTIONS_CHANGE,
} from '~/clusters_list/constants'; } from '~/clusters_list/constants';
const defaultBranchName = 'default-branch'; const defaultBranchName = 'default-branch';
describe('ClustersMainViewComponent', () => { describe('ClustersMainViewComponent', () => {
let wrapper; let wrapper;
let trackingSpy;
const propsData = { const propsData = {
defaultBranchName, defaultBranchName,
...@@ -23,6 +27,7 @@ describe('ClustersMainViewComponent', () => { ...@@ -23,6 +27,7 @@ describe('ClustersMainViewComponent', () => {
wrapper = shallowMountExtended(ClustersMainView, { wrapper = shallowMountExtended(ClustersMainView, {
propsData, propsData,
}); });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
afterEach(() => { afterEach(() => {
...@@ -71,6 +76,7 @@ describe('ClustersMainViewComponent', () => { ...@@ -71,6 +76,7 @@ describe('ClustersMainViewComponent', () => {
beforeEach(() => { beforeEach(() => {
findComponent().vm.$emit('changeTab', AGENT); findComponent().vm.$emit('changeTab', AGENT);
}); });
it('changes the tab', () => { it('changes the tab', () => {
expect(findTabs().attributes('value')).toBe('1'); expect(findTabs().attributes('value')).toBe('1');
}); });
...@@ -78,5 +84,13 @@ describe('ClustersMainViewComponent', () => { ...@@ -78,5 +84,13 @@ describe('ClustersMainViewComponent', () => {
it('passes correct max-agents param to the modal', () => { it('passes correct max-agents param to the modal', () => {
expect(findModal().props('maxAgents')).toBe(MAX_LIST_COUNT); expect(findModal().props('maxAgents')).toBe(MAX_LIST_COUNT);
}); });
it('sends the correct tracking event', () => {
findTabs().vm.$emit('input', 1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_CHANGE, {
label: EVENT_LABEL_TABS,
property: AGENT,
});
});
}); });
}); });
...@@ -2,9 +2,18 @@ import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui'; ...@@ -2,9 +2,18 @@ import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue'; import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue';
import { I18N_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants'; import {
I18N_AGENT_MODAL,
MAX_LIST_COUNT,
EVENT_LABEL_MODAL,
EVENT_ACTIONS_OPEN,
EVENT_ACTIONS_SELECT,
MODAL_TYPE_EMPTY,
MODAL_TYPE_REGISTER,
} from '~/clusters_list/constants';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql'; import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
import getAgentConfigurations from '~/clusters_list/graphql/queries/agent_configurations.query.graphql'; import getAgentConfigurations from '~/clusters_list/graphql/queries/agent_configurations.query.graphql';
import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql'; import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql';
...@@ -34,6 +43,7 @@ const maxAgents = MAX_LIST_COUNT; ...@@ -34,6 +43,7 @@ const maxAgents = MAX_LIST_COUNT;
describe('InstallAgentModal', () => { describe('InstallAgentModal', () => {
let wrapper; let wrapper;
let apolloProvider; let apolloProvider;
let trackingSpy;
const configurations = [{ agentName: 'agent-name' }]; const configurations = [{ agentName: 'agent-name' }];
const apolloQueryResponse = { const apolloQueryResponse = {
...@@ -56,7 +66,7 @@ describe('InstallAgentModal', () => { ...@@ -56,7 +66,7 @@ describe('InstallAgentModal', () => {
const findActionButton = () => findButtonByVariant('confirm'); const findActionButton = () => findButtonByVariant('confirm');
const findCancelButton = () => findButtonByVariant('default'); const findCancelButton = () => findButtonByVariant('default');
const findSecondaryButton = () => wrapper.findByTestId('agent-secondary-button'); const findSecondaryButton = () => wrapper.findByTestId('agent-secondary-button');
const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.install.altText }); const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.empty_state.altText });
const expectDisabledAttribute = (element, disabled) => { const expectDisabledAttribute = (element, disabled) => {
if (disabled) { if (disabled) {
...@@ -121,6 +131,7 @@ describe('InstallAgentModal', () => { ...@@ -121,6 +131,7 @@ describe('InstallAgentModal', () => {
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)], [getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
]); ]);
createWrapper(); createWrapper();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
afterEach(() => { afterEach(() => {
...@@ -129,7 +140,7 @@ describe('InstallAgentModal', () => { ...@@ -129,7 +140,7 @@ describe('InstallAgentModal', () => {
}); });
describe('when agent configurations are present', () => { describe('when agent configurations are present', () => {
const i18n = I18N_AGENT_MODAL.register; const i18n = I18N_AGENT_MODAL.agent_registration;
describe('initial state', () => { describe('initial state', () => {
it('renders the dropdown for available agents', () => { it('renders the dropdown for available agents', () => {
...@@ -150,6 +161,14 @@ describe('InstallAgentModal', () => { ...@@ -150,6 +161,14 @@ describe('InstallAgentModal', () => {
expect(findActionButton().text()).toBe(i18n.registerAgentButton); expect(findActionButton().text()).toBe(i18n.registerAgentButton);
expectDisabledAttribute(findActionButton(), true); expectDisabledAttribute(findActionButton(), true);
}); });
it('sends the event with the modalType', () => {
findModal().vm.$emit('show');
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
label: EVENT_LABEL_MODAL,
property: MODAL_TYPE_REGISTER,
});
});
}); });
describe('an agent is selected', () => { describe('an agent is selected', () => {
...@@ -161,6 +180,12 @@ describe('InstallAgentModal', () => { ...@@ -161,6 +180,12 @@ describe('InstallAgentModal', () => {
expect(findActionButton().isVisible()).toBe(true); expect(findActionButton().isVisible()).toBe(true);
expectDisabledAttribute(findActionButton(), false); expectDisabledAttribute(findActionButton(), false);
}); });
it('sends the correct tracking event', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_SELECT, {
label: EVENT_LABEL_MODAL,
});
});
}); });
describe('registering an agent', () => { describe('registering an agent', () => {
...@@ -247,7 +272,7 @@ describe('InstallAgentModal', () => { ...@@ -247,7 +272,7 @@ describe('InstallAgentModal', () => {
}); });
describe('when there are no agent configurations present', () => { describe('when there are no agent configurations present', () => {
const i18n = I18N_AGENT_MODAL.install; const i18n = I18N_AGENT_MODAL.empty_state;
const apolloQueryEmptyResponse = { const apolloQueryEmptyResponse = {
data: { data: {
project: { project: {
...@@ -272,5 +297,13 @@ describe('InstallAgentModal', () => { ...@@ -272,5 +297,13 @@ describe('InstallAgentModal', () => {
expect(findSecondaryButton().isVisible()).toBe(true); expect(findSecondaryButton().isVisible()).toBe(true);
expect(findSecondaryButton().text()).toBe(i18n.secondaryButton); expect(findSecondaryButton().text()).toBe(i18n.secondaryButton);
}); });
it('sends the event with the modalType', () => {
findModal().vm.$emit('show');
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
label: EVENT_LABEL_MODAL,
property: MODAL_TYPE_EMPTY,
});
});
}); });
}); });
...@@ -31,9 +31,13 @@ module Database ...@@ -31,9 +31,13 @@ module Database
# See https://gitlab.com/gitlab-org/gitlab/-/issues/339396 # See https://gitlab.com/gitlab-org/gitlab/-/issues/339396
return if sql.include?("DISABLE TRIGGER") || sql.include?("ENABLE TRIGGER") return if sql.include?("DISABLE TRIGGER") || sql.include?("ENABLE TRIGGER")
tables = begin
PgQuery.parse(sql).tables
rescue PgQuery::ParseError
# PgQuery might fail in some cases due to limited nesting: # PgQuery might fail in some cases due to limited nesting:
# https://github.com/pganalyze/pg_query/issues/209 # https://github.com/pganalyze/pg_query/issues/209
tables = PgQuery.parse(sql).tables return
end
schemas = ::Gitlab::Database::GitlabSchema.table_schemas(tables) schemas = ::Gitlab::Database::GitlabSchema.table_schemas(tables)
......
...@@ -39,6 +39,15 @@ RSpec.describe Database::PreventCrossJoins do ...@@ -39,6 +39,15 @@ RSpec.describe Database::PreventCrossJoins do
expect { main_and_ci_query_allowlist_nested }.not_to raise_error expect { main_and_ci_query_allowlist_nested }.not_to raise_error
end end
end end
context 'when there is a parser error' do
it 'does not raise parse PGQuery::ParseError' do
# Since this is in an invalid query it still raises from ActiveRecord
# but this tests that we rescue the PGQuery::ParseError which would
# have otherwise raised first
expect { ApplicationRecord.connection.execute('SELECT SELECT FROM SELECT') }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end end
end end
......
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