Commit f0e3a8ea authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 7547c4d6 04a281bc
......@@ -5,15 +5,19 @@ module Packages
protected
def find_or_create_package!(package_type, name: params[:name], version: params[:version])
# safe_find_or_create_by! was originally called here.
# We merely switched to `find_or_create_by!`
# rubocop: disable CodeReuse/ActiveRecord
project
.packages
.with_package_type(package_type)
.safe_find_or_create_by!(name: name, version: version) do |package|
.find_or_create_by!(name: name, version: version) do |package|
package.status = params[:status] if params[:status]
package.creator = package_creator
add_build_info(package)
end
# rubocop: enable CodeReuse/ActiveRecord
end
def create_package!(package_type, attrs = {})
......
......@@ -23,8 +23,3 @@ running on.
[strace-parser](https://gitlab.com/wchandler/strace-parser) is a small tool to analyze
and summarize raw `strace` data.
## Pritaly
[Pritaly](https://gitlab.com/wchandler/pritaly) takes Gitaly logs and colorizes output
or converts the logs to JSON.
......@@ -24,9 +24,11 @@ When a fast-forward merge is not possible, the user is given the option to rebas
## Enabling fast-forward merges
1. Navigate to your project's **Settings** and search for the 'Merge method'
1. Select the **Fast-forward merge** option
1. Hit **Save changes** for the changes to take effect
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > General**.
1. Expand **Merge requests**.
1. In the **Merge method** section, select **Fast-forward merge**.
1. Select **Save changes**.
Now, when you visit the merge request page, you can accept it
**only if a fast-forward merge is possible**.
......
......@@ -2,6 +2,7 @@
import { GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui';
import { mapActions } from 'vuex';
import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Alerts from './alerts/alerts.vue';
import NoEnvironmentEmptyState from './no_environment_empty_state.vue';
import PolicyList from './policy_list.vue';
......@@ -22,6 +23,7 @@ export default {
PolicyList,
NoEnvironmentEmptyState,
},
mixins: [glFeatureFlagMixin()],
inject: ['documentationPath'],
props: {
defaultEnvironmentId: {
......@@ -46,6 +48,11 @@ export default {
isSetUpMaybe: this.isValidEnvironmentId(this.defaultEnvironmentId),
};
},
computed: {
shouldShowPolicies() {
return !this.glFeatures.securityOrchestrationPoliciesConfiguration;
},
},
created() {
if (this.isSetUpMaybe) {
this.setCurrentEnvironmentId(this.defaultEnvironmentId);
......@@ -95,6 +102,7 @@ export default {
<alerts />
</gl-tab>
<gl-tab
v-if="shouldShowPolicies"
ref="policyTab"
:title="s__('ThreatMonitoring|Policies')"
data-qa-selector="policies_tab"
......
......@@ -10,6 +10,7 @@ export const EMPTY_STATE_DESCRIPTION = s__(
`ThreatMonitoring|To view this data, ensure you have configured an environment
for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}`,
);
export const NEW_POLICY_BUTTON_TEXT = s__('SecurityOrchestration|New policy');
export const COLORS = {
nominal: gray700,
......
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
import { NEW_POLICY_BUTTON_TEXT } from '../constants';
export default {
components: {
GlEmptyState,
},
i18n: {
emptyFilterTitle: s__('SecurityOrchestration|Sorry, your filter produced no results.'),
emptyFilterDescription: s__(
'SecurityOrchestration|To widen your search, change filters above or select a different security policy project.',
),
emptyStateDescription: s__(
'SecurityOrchestration|This project does not contain any security policies.',
),
newPolicyButtonText: NEW_POLICY_BUTTON_TEXT,
},
inject: ['emptyFilterSvgPath', 'emptyListSvgPath', 'newPolicyPath'],
props: {
hasExistingPolicies: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<gl-empty-state
v-if="hasExistingPolicies"
key="empty-filter-state"
data-testid="empty-filter-state"
:svg-path="emptyFilterSvgPath"
:title="$options.i18n.emptyFilterTitle"
:description="$options.i18n.emptyFilterDescription"
/>
<gl-empty-state
v-else
key="empty-list-state"
data-testid="empty-list-state"
:primary-button-link="newPolicyPath"
:primary-button-text="$options.i18n.newPolicyButtonText"
:svg-path="emptyListSvgPath"
title=""
>
<template #description>
<p class="gl-font-weight-bold">
{{ $options.i18n.emptyStateDescription }}
</p>
</template>
</gl-empty-state>
</template>
<script>
import { mapActions } from 'vuex';
import NoEnvironmentEmptyState from '../no_environment_empty_state.vue';
import PoliciesHeader from './policies_header.vue';
import PoliciesList from './policies_list.vue';
......@@ -8,7 +7,6 @@ export default {
components: {
PoliciesHeader,
PoliciesList,
NoEnvironmentEmptyState,
},
inject: ['defaultEnvironmentId'],
data() {
......@@ -42,9 +40,8 @@ export default {
<template>
<div>
<policies-header @update-policy-list="handleUpdatePolicyList" />
<no-environment-empty-state v-if="!shouldFetchEnvironment" />
<policies-list
v-else
:has-environment="shouldFetchEnvironment"
:should-update-policy-list="shouldUpdatePolicyList"
@update-policy-list="handleUpdatePolicyList"
/>
......
<script>
import { GlAlert, GlSprintf, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
import { NEW_POLICY_BUTTON_TEXT } from '../constants';
import ScanNewPolicyModal from './scan_new_policy_modal.vue';
export default {
......@@ -21,7 +22,7 @@ export default {
subtitle: s__(
'SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}',
),
newPolicyButtonText: s__('SecurityOrchestration|New policy'),
newPolicyButtonText: NEW_POLICY_BUTTON_TEXT,
editPolicyProjectButtonText: s__('SecurityOrchestration|Edit policy project'),
},
data() {
......
<script>
import {
GlTable,
GlEmptyState,
GlAlert,
GlSprintf,
GlLink,
GlIcon,
GlTooltipDirective,
} from '@gitlab/ui';
import { GlAlert, GlIcon, GlLink, GlSprintf, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import createFlash from '~/flash';
......@@ -22,6 +14,7 @@ import EnvironmentPicker from '../environment_picker.vue';
import PolicyDrawer from '../policy_drawer/policy_drawer.vue';
import PolicyEnvironments from '../policy_environments.vue';
import PolicyTypeFilter from '../policy_type_filter.vue';
import NoPoliciesEmptyState from './no_policies_empty_state.vue';
const createPolicyFetchError = ({ gqlError, networkError }) => {
const error =
......@@ -41,13 +34,13 @@ const getPoliciesWithType = (policies, policyType) =>
export default {
components: {
GlTable,
GlEmptyState,
GlAlert,
GlSprintf,
GlLink,
GlIcon,
GlLink,
GlSprintf,
GlTable,
EnvironmentPicker,
NoPoliciesEmptyState,
PolicyTypeFilter,
PolicyDrawer,
PolicyEnvironments,
......@@ -55,8 +48,13 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['projectPath', 'documentationPath', 'newPolicyPath'],
inject: ['documentationPath', 'projectPath', 'newPolicyPath'],
props: {
hasEnvironment: {
type: Boolean,
required: false,
default: false,
},
shouldUpdatePolicyList: {
type: Boolean,
required: false,
......@@ -74,9 +72,11 @@ export default {
},
update(data) {
const policies = data?.project?.networkPolicies?.nodes ?? [];
const predefined = PREDEFINED_NETWORK_POLICIES.filter(
({ name }) => !policies.some((policy) => name === policy.name),
);
const predefined = this.hasEnvironment
? PREDEFINED_NETWORK_POLICIES.filter(
({ name }) => !policies.some((policy) => name === policy.name),
)
: [];
return [...policies, ...predefined];
},
error: createPolicyFetchError,
......@@ -169,6 +169,9 @@ export default {
policyType() {
return this.selectedPolicy ? getPolicyType(this.selectedPolicy.yaml) : 'container';
},
hasExistingPolicies() {
return !(this.selectedPolicyType === POLICY_TYPE_OPTIONS.ALL.value && !this.policies.length);
},
fields() {
const environments = {
key: 'environments',
......@@ -232,14 +235,9 @@ export default {
},
},
i18n: {
emptyStateDescription: s__(
`NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints.`,
),
autodevopsNoticeDescription: s__(
`NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}.`,
`SecurityOrchestration|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}.`,
),
emptyStateButton: __('Learn more'),
emptyStateTitle: s__('NetworkPolicies|No policies detected'),
statusEnabled: __('Enabled'),
statusDisabled: __('Disabled'),
},
......@@ -317,15 +315,7 @@ export default {
</template>
<template #empty>
<slot name="empty-state">
<gl-empty-state
ref="tableEmptyState"
:title="$options.i18n.emptyStateTitle"
:description="$options.i18n.emptyStateDescription"
:primary-button-link="documentationFullPath"
:primary-button-text="$options.i18n.emptyStateButton"
/>
</slot>
<no-policies-empty-state :has-existing-policies="hasExistingPolicies" />
</template>
</gl-table>
......
......@@ -18,7 +18,8 @@ export default () => {
disableSecurityPolicyProject,
defaultEnvironmentId,
environmentsEndpoint,
emptyStateSvgPath,
emptyFilterSvgPath,
emptyListSvgPath,
documentationPath,
newPolicyPath,
projectPath,
......@@ -37,7 +38,8 @@ export default () => {
documentationPath,
newPolicyPath,
projectPath,
emptyStateSvgPath,
emptyFilterSvgPath,
emptyListSvgPath,
defaultEnvironmentId: parseInt(defaultEnvironmentId, 10),
},
render(createElement) {
......
......@@ -7,7 +7,8 @@
default_environment_id: default_environment_id,
disable_security_policy_project: disable_security_policy_project.to_s,
documentation_path: help_page_path('user/application_security/policies/index.md'),
empty_state_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
empty_filter_svg_path: image_path('illustrations/issues.svg'),
empty_list_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
new_policy_path: new_project_security_policy_path(project),
environments_endpoint: project_environments_path(project),
project_path: project.full_path } }
......@@ -134,4 +134,14 @@ describe('ThreatMonitoringApp component', () => {
expect(findAlertsView().exists()).toBe(true);
});
});
describe('with "securityOrchestrationPoliciesConfiguration" feature flag enabled', () => {
beforeEach(() => {
factory({ provide: { glFeatures: { securityOrchestrationPoliciesConfiguration: true } } });
});
it('does not render the Policies tab', () => {
expect(findPolicyTab().exists()).toBe(false);
});
});
});
import NoPoliciesEmptyState from 'ee/threat_monitoring/components/policies/no_policies_empty_state.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('NoPoliciesEmptyState component', () => {
let wrapper;
const findEmptyFilterState = () => wrapper.findByTestId('empty-filter-state');
const findEmptyListState = () => wrapper.findByTestId('empty-list-state');
const factory = (hasExistingPolicies = false) => {
wrapper = shallowMountExtended(NoPoliciesEmptyState, {
propsData: {
hasExistingPolicies,
},
provide: {
emptyFilterSvgPath: 'path/to/filter/svg',
emptyListSvgPath: 'path/to/list/svg',
newPolicyPath: 'path/to/new/policy',
},
});
};
afterEach(() => {
wrapper.destroy();
});
it.each`
title | findComponent | state | factoryFn
${'does display the empty filter state'} | ${findEmptyFilterState} | ${false} | ${factory}
${'does not display the empty list state'} | ${findEmptyListState} | ${true} | ${factory}
${'does not display the empty filter state'} | ${findEmptyFilterState} | ${true} | ${() => factory(true)}
${'does display the empty list state'} | ${findEmptyListState} | ${false} | ${() => factory(true)}
`('$title', async ({ factoryFn, findComponent, state }) => {
factoryFn();
await wrapper.vm.$nextTick();
expect(findComponent().exists()).toBe(state);
});
});
import NoEnvironmentEmptyState from 'ee/threat_monitoring/components/no_environment_empty_state.vue';
import PoliciesApp from 'ee/threat_monitoring/components/policies/policies_app.vue';
import PoliciesHeader from 'ee/threat_monitoring/components/policies/policies_header.vue';
import PoliciesList from 'ee/threat_monitoring/components/policies/policies_list.vue';
......@@ -13,7 +12,6 @@ describe('Policies App', () => {
const findPoliciesHeader = () => wrapper.findComponent(PoliciesHeader);
const findPoliciesList = () => wrapper.findComponent(PoliciesList);
const findEmptyState = () => wrapper.findComponent(NoEnvironmentEmptyState);
const createWrapper = ({ provide } = {}) => {
store = createStore();
......@@ -49,11 +47,8 @@ describe('Policies App', () => {
});
it('mounts the policies list component', () => {
expect(findPoliciesList().exists()).toBe(true);
});
it('does not mount the empty state', () => {
expect(findEmptyState().exists()).toBe(false);
const policiesList = findPoliciesList();
expect(policiesList.props('hasEnvironment')).toBe(true);
});
it('fetches the environments when created', async () => {
......@@ -81,16 +76,9 @@ describe('Policies App', () => {
createWrapper();
});
it('mounts the policies header component', () => {
expect(findPoliciesHeader().exists()).toBe(true);
});
it('does not mount the policies list component', () => {
expect(findPoliciesList().exists()).toBe(false);
});
it('mounts the empty state', () => {
expect(findEmptyState().exists()).toBe(true);
it('mounts the policies list component', () => {
const policiesList = findPoliciesList();
expect(policiesList.props('hasEnvironment')).toBe(false);
});
it('does not fetch the environments when created', () => {
......
import { GlAlert, GlButton, GlSprintf } from '@gitlab/ui';
import { NEW_POLICY_BUTTON_TEXT } from 'ee/threat_monitoring/components/constants';
import PoliciesHeader from 'ee/threat_monitoring/components/policies/policies_header.vue';
import ScanNewPolicyModal from 'ee/threat_monitoring/components/policies/scan_new_policy_modal.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
......@@ -52,7 +53,7 @@ describe('Policies Header Component', () => {
});
it('displays New policy button with correct text and link', () => {
expect(findNewPolicyButton().text()).toBe('New policy');
expect(findNewPolicyButton().text()).toBe(NEW_POLICY_BUTTON_TEXT);
expect(findNewPolicyButton().attributes('href')).toBe(newPolicyPath);
});
......
......@@ -65,6 +65,7 @@ describe('PoliciesList component', () => {
{
propsData: {
documentationPath: 'documentation_path',
hasEnvironment: true,
newPolicyPath: '/policies/new',
},
store,
......@@ -84,6 +85,7 @@ describe('PoliciesList component', () => {
...GlDrawer.props,
},
}),
NoPoliciesEmptyState: true,
},
localVue,
},
......@@ -152,6 +154,10 @@ describe('PoliciesList component', () => {
rows = wrapper.findAll('tr');
});
it('does render default network policies', () => {
expect(findPolicyStatusCells()).toHaveLength(5);
});
it('fetches network policies on environment change', async () => {
store.dispatch.mockReset();
await store.commit('threatMonitoring/SET_CURRENT_ENVIRONMENT_ID', 3);
......@@ -305,4 +311,18 @@ describe('PoliciesList component', () => {
expect(findAutodevopsAlert().exists()).toBe(true);
});
});
describe('given no environement', () => {
beforeEach(() => {
mountWrapper({
propsData: {
hasEnvironment: false,
},
});
});
it('does not render default network policies', () => {
expect(findPolicyStatusCells()).toHaveLength(3);
});
});
});
......@@ -29646,6 +29646,9 @@ msgstr ""
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
msgstr ""
msgid "SecurityOrchestration|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
msgstr ""
msgid "SecurityOrchestration|Network"
msgstr ""
......@@ -29676,9 +29679,18 @@ msgstr ""
msgid "SecurityOrchestration|Select security project"
msgstr ""
msgid "SecurityOrchestration|Sorry, your filter produced no results."
msgstr ""
msgid "SecurityOrchestration|There was a problem creating the new security policy"
msgstr ""
msgid "SecurityOrchestration|This project does not contain any security policies."
msgstr ""
msgid "SecurityOrchestration|To widen your search, change filters above or select a different security policy project."
msgstr ""
msgid "SecurityOrchestration|Update scan execution policies"
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