Skip to content
Snippets Groups Projects
Verified Commit 81a57479 authored by Robert Hunt's avatar Robert Hunt :two:
Browse files

Adds the ability to show a read-only form on an existing destination

- Add custom headers to GraphQL query
- Add new button to streaming items
- Add editing mode to streaming items to toggle the editor
- Add streaming destination editor to streaming items and pass the item
into it as a prop
- Update streaming destination editor to be disabled when item is passed
in
- Update streaming destination editor to add values to fields when item
is passed in
- Update streaming destination editor button to say 'Save' rather than
'Add' when the item is passed in
- Update docs to reflect that you can see all the headers in the UI
- Update translation file
parent bc091220
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !91692. Comments created here will be created in the context of that merge request.
......@@ -232,7 +232,11 @@ mutation {
The header is deleted if the returned `errors` object is empty.
### List all custom headers with the API
### List all custom headers
List all custom HTTP headers with the API or GitLab UI.
#### Use the API
You can list all custom headers for a top-level group as well as their value and ID using the GraphQL `externalAuditEventDestinations` query. The ID
value returned by this query is what you need to pass to the `deletion` mutation.
......@@ -258,6 +262,24 @@ query {
}
```
#### Use the GitLab UI
FLAG:
On self-managed GitLab, by default the UI for this feature is not available. To make it available per group, ask an administrator to
[enable the feature flag](../administration/feature_flags.md) named `custom_headers_streaming_audit_events_ui`. On GitLab.com, the UI for this feature is
not available. The UI for this feature is not ready for production use.
Users with at least the Owner role for a group can add event streaming destinations and custom HTTP headers for it:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Security & Compliance > Audit events**
1. On the main area, select **Streams** tab.
1. Select **{pencil}** at the right side of an item.
1. A read-only view of the items custom headers will be shown.
- To track progress on adding editing functionality, see the
[relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/361925).
1. Select **Cancel** to close the read-only view.
## Verify event authenticity
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345424) in GitLab 14.8.
......
......@@ -100,7 +100,7 @@ export default {
<span class="gl-mr-4">{{ destinationsCount }}</span>
<gl-button :aria-label="$options.i18n.ADD_STREAM" icon="plus" @click="setEditMode(true)" />
</div>
<div v-if="isEditing" class="gl-p-4">
<div v-if="isEditing" class="gl-p-6 gl-border gl-rounded-base">
<stream-destination-editor @added="onAddedDestination" @cancel="setEditMode(false)" />
</div>
<div v-if="destinationsCount" class="gl-p-4">
......@@ -111,6 +111,7 @@ export default {
:key="item.id"
:item="item"
@delete="refreshDestinations"
@updated="onAddedDestination"
/>
</ul>
</div>
......
......@@ -9,6 +9,7 @@ import {
GlSprintf,
GlTableLite,
} from '@gitlab/ui';
import { isEmpty } from 'lodash';
import * as Sentry from '@sentry/browser';
import { thWidthPercent } from '~/lib/utils/table_utility';
import externalAuditEventDestinationCreate from '../../graphql/create_external_destination.mutation.graphql';
......@@ -37,6 +38,13 @@ export default {
GlTableLite,
},
inject: ['groupPath', 'showStreamsHeaders', 'maxHeaders'],
props: {
item: {
type: Object,
required: false,
default: () => ({}),
},
},
data() {
return {
destinationUrl: '',
......@@ -63,8 +71,43 @@ export default {
return this.headers.some((header) => header.name !== '' && header.value !== '');
},
isSubmitButtonDisabled() {
return !this.destinationUrl || this.hasHeaderValidationErrors || this.hasMissingKeyValuePairs;
return (
this.isEditing ||
!this.destinationUrl ||
this.hasHeaderValidationErrors ||
this.hasMissingKeyValuePairs
);
},
isEditing() {
return !isEmpty(this.item);
},
addButtonName() {
return this.isEditing
? ADD_STREAM_EDITOR_I18N.SAVE_BUTTON_NAME
: ADD_STREAM_EDITOR_I18N.ADD_BUTTON_NAME;
},
addButtonText() {
return this.isEditing
? ADD_STREAM_EDITOR_I18N.SAVE_BUTTON_TEXT
: ADD_STREAM_EDITOR_I18N.ADD_BUTTON_TEXT;
},
},
mounted() {
const existingHeaders = this.item?.headers?.nodes || [];
if (existingHeaders.length > 0) {
this.headers = existingHeaders.map(({ id, key, value }) => ({
id,
name: key,
value,
active: true,
disabled: true,
deletionDisabled: true,
validationErrors: {},
}));
} else if (this.item) {
this.$set(this.headers, 0, { ...this.headers[0], disabled: true, deletionDisabled: true });
}
},
methods: {
clearError(index) {
......@@ -231,7 +274,7 @@ export default {
{
key: 'active',
label: ADD_STREAM_EDITOR_I18N.TABLE_COLUMN_ACTIVE_LABEL,
thClass: `${thClasses} ${thWidthPercent(5)}`,
thClass: `${thClasses} ${thWidthPercent(10)}`,
tdClass: tdClasses,
},
{
......@@ -245,7 +288,7 @@ export default {
</script>
<template>
<div class="gl-p-4 gl-bg-white gl-border gl-rounded-base">
<div class="gl-bg-white">
<gl-alert
:title="$options.i18n.WARNING_TITLE"
:dismissible="false"
......@@ -276,6 +319,7 @@ export default {
<gl-form-input
v-model="destinationUrl"
:placeholder="$options.i18n.DESTINATION_URL_PLACEHOLDER"
:disabled="isEditing"
data-testid="destination-url"
/>
</gl-form-group>
......@@ -353,12 +397,12 @@ export default {
<gl-button
:disabled="isSubmitButtonDisabled"
:loading="loading"
:name="$options.i18n.ADD_BUTTON_NAME"
:name="addButtonName"
class="gl-mr-3"
variant="confirm"
type="submit"
data-testid="stream-destination-add-button"
>{{ $options.i18n.ADD_BUTTON_TEXT }}</gl-button
>{{ addButtonText }}</gl-button
>
<gl-button
:name="$options.i18n.CANCEL_BUTTON_NAME"
......
<script>
import { GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { createAlert } from '~/flash';
import { sprintf, s__ } from '~/locale';
import { sprintf } from '~/locale';
import deleteExternalDestination from '../../graphql/delete_external_destination.mutation.graphql';
import { AUDIT_STREAMS_NETWORK_ERRORS } from '../../constants';
import { AUDIT_STREAMS_NETWORK_ERRORS, STREAM_ITEMS_I18N } from '../../constants';
import StreamDestinationEditor from './stream_destination_editor.vue';
export default {
components: {
GlButton,
StreamDestinationEditor,
},
directives: {
GlTooltip,
......@@ -20,15 +22,26 @@ export default {
},
data() {
return {
isEditing: false,
isDeleting: false,
};
},
computed: {
editButtonLabel() {
return sprintf(STREAM_ITEMS_I18N.EDIT_BUTTON_LABEL, { link: this.item.destinationUrl });
},
deleteButtonLabel() {
return sprintf(s__('AuditStreams|Delete %{link}'), { link: this.item.destinationUrl });
return sprintf(STREAM_ITEMS_I18N.DELETE_BUTTON_LABEL, { link: this.item.destinationUrl });
},
},
methods: {
setEditMode(state) {
this.isEditing = state;
},
onUpdated(event) {
this.setEditMode(false);
this.$emit('updated', event);
},
async deleteDestination() {
this.isDeleting = true;
try {
......@@ -61,27 +74,40 @@ export default {
}
},
},
i18n: AUDIT_STREAMS_NETWORK_ERRORS,
i18n: { ...AUDIT_STREAMS_NETWORK_ERRORS, ...STREAM_ITEMS_I18N },
};
</script>
<template>
<li class="list-item py-0">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-pl-5 gl-pr-3 gl-py-3 gl-rounded-base"
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-pl-5 gl-pr-3 gl-rounded-base"
:class="[isEditing ? 'gl-py-5' : 'gl-py-3']"
>
<div class="gl-h-4">{{ item.destinationUrl }}</div>
<div class="actions-button">
<span class="gl-display-block">{{ item.destinationUrl }}</span>
<div v-if="!isEditing">
<gl-button
v-gl-tooltip
:aria-label="editButtonLabel"
:loading="isDeleting"
:title="$options.i18n.EDIT_BUTTON_TOOLTIP"
category="tertiary"
icon="pencil"
@click="setEditMode(true)"
/>
<gl-button
v-gl-tooltip
:aria-label="deleteButtonLabel"
:loading="isDeleting"
:title="__('Delete')"
:title="$options.i18n.DELETE_BUTTON_TOOLTIP"
category="tertiary"
icon="remove"
@click="deleteDestination"
/>
</div>
</div>
<div v-if="isEditing" class="gl-p-4">
<stream-destination-editor :item="item" @added="onUpdated" @cancel="setEditMode(false)" />
</div>
</li>
</template>
......@@ -73,6 +73,13 @@ export const ADD_STREAM = s__('AuditStreams|Add stream');
export const ACTIVE_STREAM = s__('AuditStreams|Active');
export const STREAM_COUNT_ICON_ALT = s__('AuditStreams|Stream count icon');
export const STREAM_ITEMS_I18N = {
EDIT_BUTTON_LABEL: s__('AuditStreams|Edit %{link}'),
DELETE_BUTTON_LABEL: s__('AuditStreams|Delete %{link}'),
EDIT_BUTTON_TOOLTIP: __('Edit'),
DELETE_BUTTON_TOOLTIP: __('Delete'),
};
export const ADD_STREAM_EDITOR_I18N = {
WARNING_TITLE: s__('AuditStreams|Destinations receive all audit event data'),
WARNING_CONTENT: s__(
......@@ -90,8 +97,10 @@ export const ADD_STREAM_EDITOR_I18N = {
MAXIMUM_HEADERS_TEXT: s__('AuditStreams|Maximum of %{number} HTTP headers has been reached.'),
ADD_BUTTON_TEXT: __('Add'),
ADD_BUTTON_NAME: s__('AuditStreams|Add external stream destination'),
SAVE_BUTTON_TEXT: __('Save'),
SAVE_BUTTON_NAME: s__('AuditStreams|Save external stream destination'),
CANCEL_BUTTON_TEXT: __('Cancel'),
CANCEL_BUTTON_NAME: __('AuditStreams|Cancel editing'),
CANCEL_BUTTON_NAME: s__('AuditStreams|Cancel editing'),
};
export const AUDIT_STREAMS_EMPTY_STATE_I18N = {
......
......@@ -5,6 +5,13 @@ query getExternalDestinations($fullPath: ID!) {
nodes {
destinationUrl
id
headers {
nodes {
key
value
id
}
}
}
}
}
......
......@@ -5328,12 +5328,18 @@ msgstr ""
msgid "AuditStreams|Destinations receive all audit event data"
msgstr ""
 
msgid "AuditStreams|Edit %{link}"
msgstr ""
msgid "AuditStreams|Header"
msgstr ""
 
msgid "AuditStreams|Maximum of %{number} HTTP headers has been reached."
msgstr ""
 
msgid "AuditStreams|Save external stream destination"
msgstr ""
msgid "AuditStreams|Setup streaming for audit events"
msgstr ""
 
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment