Skip to content

Commit dfe2a4b

Browse files
authored
fix(codecentric#1935): auditevents no longer fetched (codecentric#1936)
* fix(codecentric#1935): auditevents no longer fetched * fix(codecentric#1935): rename alias function usages
1 parent 4f9d0ae commit dfe2a4b

File tree

4 files changed

+196
-112
lines changed

4 files changed

+196
-112
lines changed

spring-boot-admin-server-ui/src/main/frontend/utils/rxjs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export {
2929
concatMap,
3030
delay,
3131
debounceTime,
32-
merge,
32+
mergeWith,
3333
map,
3434
retryWhen,
3535
tap,

spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ import Popper from '@/directives/popper';
7777
import subscribing from '@/mixins/subscribing';
7878
import NotificationFilter from '@/services/notification-filter';
7979
import {anyValueMatches} from '@/utils/collections';
80-
import {concatMap, merge, Subject, timer} from '@/utils/rxjs';
80+
import {concatMap, mergeWith, Subject, timer} from '@/utils/rxjs';
8181
import groupBy from 'lodash/groupBy';
8282
import sortBy from 'lodash/sortBy';
8383
import transform from 'lodash/transform';
@@ -200,7 +200,7 @@ export default {
200200
vm.notificationFilterSubject = new Subject();
201201
return timer(0, 60000)
202202
.pipe(
203-
merge(vm.notificationFilterSubject),
203+
mergeWith(vm.notificationFilterSubject),
204204
concatMap(this.fetchNotificationFilters),
205205
)
206206
.subscribe({
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {render} from '@/test-utils';
2+
import Auditevents from '@/views/instances/auditevents/index';
3+
import Instance from '@/services/instance';
4+
import {screen, waitFor} from '@testing-library/vue';
5+
import userEvent from '@testing-library/user-event';
6+
7+
describe('Auditevents', () => {
8+
const fetchAuditevents = jest.fn().mockResolvedValue({
9+
data: {
10+
events: []
11+
}
12+
});
13+
14+
beforeEach(async () => {
15+
await render(Auditevents, {
16+
props: {
17+
instance: createInstance(fetchAuditevents)
18+
}
19+
});
20+
});
21+
22+
it('fetches data on startup', async () => {
23+
await waitFor(() => expect(fetchAuditevents).toHaveBeenCalled());
24+
});
25+
26+
it('fetches data when filter for Principal is changed', async () => {
27+
const input = await screen.findByPlaceholderText('Principal');
28+
userEvent.type(input, 'Abc')
29+
30+
await waitFor(() => {
31+
let calls = fetchAuditevents.mock.calls;
32+
expect(calls[calls.length - 1][0].principal).toEqual('Abc');
33+
});
34+
});
35+
36+
it('fetches data when filter for Type is changed', async () => {
37+
const input = await screen.findByPlaceholderText('Type');
38+
userEvent.type(input, 'AUTHENTICATION_FAILURE')
39+
40+
await waitFor(() => {
41+
let calls = fetchAuditevents.mock.calls;
42+
expect(calls[calls.length - 1][0].type).toEqual('AUTHENTICATION_FAILURE');
43+
});
44+
});
45+
46+
it('handles error when fetching data', async () => {
47+
await render(Auditevents, {
48+
props: {
49+
instance: createInstance(jest.fn().mockRejectedValue({
50+
response: {
51+
headers: {
52+
'content-type': 'application/vnd.spring-boot.actuator.v2'
53+
}
54+
}
55+
}))
56+
}
57+
});
58+
59+
await screen.findByText('Fetching audit events failed.');
60+
});
61+
62+
it('handles error when fetching data from Spring 1 services', async () => {
63+
await render(Auditevents, {
64+
props: {
65+
instance: createInstance(jest.fn().mockRejectedValue({
66+
response: {
67+
headers: {
68+
'content-type': ''
69+
}
70+
}
71+
}))
72+
}
73+
});
74+
75+
await screen.findByText('Audit Log is not supported for Spring Boot 1.x applications.');
76+
});
77+
78+
function createInstance(fetchAuditevents) {
79+
let instance = new Instance({id: 4711});
80+
instance.fetchAuditevents = fetchAuditevents;
81+
return instance;
82+
}
83+
});

spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/index.vue

Lines changed: 110 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -87,128 +87,129 @@
8787
</template>
8888

8989
<script>
90-
import subscribing from '@/mixins/subscribing';
91-
import Instance from '@/services/instance';
92-
import {concatMap, debounceTime, merge, Subject, tap, timer} from '@/utils/rxjs';
93-
import AuditeventsList from '@/views/instances/auditevents/auditevents-list';
94-
import uniqBy from 'lodash/uniqBy';
95-
import moment from 'moment';
96-
import {VIEW_GROUP} from '../../index';
90+
import subscribing from '@/mixins/subscribing';
91+
import Instance from '@/services/instance';
92+
import {concatMap, debounceTime, mergeWith, Subject, tap, timer} from '@/utils/rxjs';
93+
import AuditeventsList from '@/views/instances/auditevents/auditevents-list';
94+
import uniqBy from 'lodash/uniqBy';
95+
import moment from 'moment';
96+
import {VIEW_GROUP} from '../../index';
9797
98-
class Auditevent {
99-
constructor({timestamp, ...event}) {
100-
Object.assign(this, event);
101-
this.zonedTimestamp = timestamp;
102-
this.timestamp = moment(timestamp);
103-
}
98+
class Auditevent {
99+
constructor({timestamp, ...event}) {
100+
Object.assign(this, event);
101+
this.zonedTimestamp = timestamp;
102+
this.timestamp = moment(timestamp);
103+
}
104104
105-
get key() {
106-
return `${this.zonedTimestamp}-${this.type}-${this.principal}`;
107-
}
105+
get key() {
106+
return `${this.zonedTimestamp}-${this.type}-${this.principal}`;
107+
}
108108
109-
get remoteAddress() {
110-
return this.data && this.data.details && this.data.details.remoteAddress || null;
111-
}
109+
get remoteAddress() {
110+
return this.data && this.data.details && this.data.details.remoteAddress || null;
111+
}
112112
113-
get sessionId() {
114-
return this.data && this.data.details && this.data.details.sessionId || null;
115-
}
113+
get sessionId() {
114+
return this.data && this.data.details && this.data.details.sessionId || null;
115+
}
116116
117-
isSuccess() {
118-
return this.type.toLowerCase().includes('success');
119-
}
117+
isSuccess() {
118+
return this.type.toLowerCase().includes('success');
119+
}
120120
121-
isFailure() {
122-
return this.type.toLowerCase().includes('failure');
123-
}
121+
isFailure() {
122+
return this.type.toLowerCase().includes('failure');
124123
}
124+
}
125125
126-
export default {
127-
props: {
128-
instance: {
129-
type: Instance,
130-
required: true
131-
}
126+
export default {
127+
props: {
128+
instance: {
129+
type: Instance,
130+
required: true
131+
}
132+
},
133+
mixins: [subscribing],
134+
components: {AuditeventsList},
135+
data: () => ({
136+
isLoading: false,
137+
error: null,
138+
events: [],
139+
filter: {
140+
after: moment().startOf('day'),
141+
type: null,
142+
principal: null
132143
},
133-
mixins: [subscribing],
134-
components: {AuditeventsList},
135-
data: () => ({
136-
isLoading: false,
137-
error: null,
138-
events: [],
139-
filter: {
140-
after: moment().startOf('day'),
141-
type: null,
142-
principal: null
143-
},
144-
isOldAuditevents: false
145-
}),
146-
watch: {
147-
filter: {
148-
deep: true,
149-
handler() {
150-
this.filterChanged.next();
151-
}
144+
isOldAuditevents: false
145+
}),
146+
watch: {
147+
filter: {
148+
deep: true,
149+
handler() {
150+
this.filterChanged.next();
152151
}
152+
}
153+
},
154+
methods: {
155+
formatDate(value) {
156+
return value.format(moment.HTML5_FMT.DATETIME_LOCAL);
153157
},
154-
methods: {
155-
formatDate(value) {
156-
return value.format(moment.HTML5_FMT.DATETIME_LOCAL);
157-
},
158-
parseDate(value) {
159-
return moment(value, moment.HTML5_FMT.DATETIME_LOCAL, true);
160-
},
161-
async fetchAuditevents() {
162-
this.isLoading = true;
163-
const response = await this.instance.fetchAuditevents(this.filter);
164-
const converted = response.data.events.map(event => new Auditevent(event));
165-
converted.reverse();
166-
this.isLoading = false;
167-
return converted;
168-
},
169-
createSubscription() {
170-
const vm = this;
171-
vm.filterChanged = new Subject();
172-
vm.error = null;
173-
return timer(0, 5000)
174-
.pipe(
175-
merge(vm.filterChanged.pipe(
176-
debounceTime(250),
177-
tap({
178-
next: () => vm.events = []
179-
})
180-
)),
181-
concatMap(this.fetchAuditevents)
182-
)
183-
.subscribe({
184-
next: events => {
185-
vm.addEvents(events);
186-
},
187-
error: error => {
188-
console.warn('Fetching audit events failed:', error);
189-
if (error.response.headers['content-type'].includes('application/vnd.spring-boot.actuator.v2')) {
190-
vm.error = error;
191-
} else {
192-
vm.isOldAuditevents = true;
193-
}
158+
parseDate(value) {
159+
return moment(value, moment.HTML5_FMT.DATETIME_LOCAL, true);
160+
},
161+
async fetchAuditevents() {
162+
this.isLoading = true;
163+
const response = await this.instance.fetchAuditevents(this.filter);
164+
const converted = response.data.events.map(event => new Auditevent(event));
165+
converted.reverse();
166+
this.isLoading = false;
167+
return converted;
168+
},
169+
createSubscription() {
170+
const vm = this;
171+
vm.filterChanged = new Subject();
172+
vm.error = null;
173+
174+
return timer(0, 5000)
175+
.pipe(
176+
mergeWith(vm.filterChanged.pipe(
177+
debounceTime(250),
178+
tap({
179+
next: () => vm.events = []
180+
})
181+
)),
182+
concatMap(this.fetchAuditevents)
183+
)
184+
.subscribe({
185+
next: events => {
186+
vm.addEvents(events);
187+
},
188+
error: error => {
189+
console.warn('Fetching audit events failed:', error);
190+
if (error.response.headers['content-type'].includes('application/vnd.spring-boot.actuator.v2')) {
191+
vm.error = error;
192+
} else {
193+
vm.isOldAuditevents = true;
194194
}
195-
});
196-
},
197-
addEvents(events) {
198-
this.events = uniqBy(this.events ? events.concat(this.events) : events, event => event.key);
199-
}
195+
}
196+
});
200197
},
201-
install({viewRegistry}) {
202-
viewRegistry.addView({
203-
name: 'instances/auditevents',
204-
parent: 'instances',
205-
path: 'auditevents',
206-
component: this,
207-
label: 'instances.auditevents.label',
208-
group: VIEW_GROUP.SECURITY,
209-
order: 600,
210-
isEnabled: ({instance}) => instance.hasEndpoint('auditevents')
211-
});
198+
addEvents(events) {
199+
this.events = uniqBy(this.events ? events.concat(this.events) : events, event => event.key);
212200
}
201+
},
202+
install({viewRegistry}) {
203+
viewRegistry.addView({
204+
name: 'instances/auditevents',
205+
parent: 'instances',
206+
path: 'auditevents',
207+
component: this,
208+
label: 'instances.auditevents.label',
209+
group: VIEW_GROUP.SECURITY,
210+
order: 600,
211+
isEnabled: ({instance}) => instance.hasEndpoint('auditevents')
212+
});
213213
}
214+
}
214215
</script>

0 commit comments

Comments
 (0)