diff --git a/server/service/core/action/webhook/logs.go b/server/service/core/action/webhook/logs.go index 4a8886a02..292e3e757 100644 --- a/server/service/core/action/webhook/logs.go +++ b/server/service/core/action/webhook/logs.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "github.com/factly/dega-server/service/core/model" "github.com/factly/x/errorx" @@ -11,6 +12,7 @@ import ( "github.com/factly/x/middlewarex" "github.com/factly/x/renderx" "github.com/factly/x/requestx" + "github.com/go-chi/chi" "github.com/spf13/viper" ) @@ -32,6 +34,15 @@ type logPaging struct { // @Success 200 {object} paging // @Router /core/webhooks/logs [get] func logs(w http.ResponseWriter, r *http.Request) { + webhookID := chi.URLParam(r, "webhook_id") + wID, err := strconv.Atoi(webhookID) + + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.InvalidID())) + return + } + uID, err := middlewarex.GetUser(r.Context()) if err != nil { loggerx.Error(err) @@ -46,7 +57,7 @@ func logs(w http.ResponseWriter, r *http.Request) { return } - hukzURL := viper.GetString("hukz_url") + "/webhooks/logs?tag=app:dega&tag=space:" + fmt.Sprint(sID) + "&limit=" + r.URL.Query().Get("limit") + "&page=" + r.URL.Query().Get("page") + hukzURL := viper.GetString("hukz_url") + "/webhooks/space/" + fmt.Sprint(sID) + "/webhook/" + fmt.Sprint(wID) + "/logs?tag=app:dega&tag=space:" + fmt.Sprint(sID) + "&limit=" + r.URL.Query().Get("limit") + "&page=" + r.URL.Query().Get("page") resp, err := requestx.Request("GET", hukzURL, nil, map[string]string{ "X-User": fmt.Sprint(uID), diff --git a/server/service/core/action/webhook/route.go b/server/service/core/action/webhook/route.go index 171617757..b799c6782 100644 --- a/server/service/core/action/webhook/route.go +++ b/server/service/core/action/webhook/route.go @@ -22,8 +22,8 @@ func Router() chi.Router { r.With(util.CheckKetoPolicy(entity, "get")).Get("/", list) r.With(util.CheckKetoPolicy(entity, "create")).Post("/", create) - r.With(util.CheckKetoPolicy(entity, "get")).Get("/logs", logs) r.Route("/{webhook_id}", func(r chi.Router) { + r.With(util.CheckKetoPolicy(entity, "get")).Get("/logs", logs) r.With(util.CheckKetoPolicy(entity, "get")).Get("/", details) r.With(util.CheckKetoPolicy(entity, "update")).Put("/", update) r.With(util.CheckKetoPolicy(entity, "delete")).Delete("/", delete) diff --git a/studio/src/actions/webhooklogs.js b/studio/src/actions/webhooklogs.js new file mode 100644 index 000000000..017050346 --- /dev/null +++ b/studio/src/actions/webhooklogs.js @@ -0,0 +1,55 @@ +import axios from 'axios'; +import { + ADD_WEBHOOKLOGS, + ADD_WEBHOOKLOGS_REQUEST, + SET_WEBHOOKLOGS_LOADING, + RESET_WEBHOOKLOGS, +} from '../constants/webhooklogs'; +import { addErrorNotification, addSuccessNotification } from './notifications'; +import { addEvents } from './events'; +import getError from '../utils/getError'; +import { WEBHOOKS_API } from '../constants/webhooks'; + +export const getWebhooklogs = (id, query) => { + return (dispatch) => { + dispatch(loadingWebhookLogs()); + return axios + .get(WEBHOOKS_API + '/' + id + '/logs', { + params: query, + }) + .then((response) => { + dispatch(addWebhooklogList(response.data.nodes)); + dispatch( + addWebhookRequest({ + data: response.data.nodes.map((item) => item.id), + query: query, + total: response.data.total, + }), + ); + }) + .catch((error) => { + dispatch(addErrorNotification(getError(error))); + }) + .finally(() => dispatch(stopWebhookLogsLoading())); + }; +}; + +export const loadingWebhookLogs = () => ({ + type: SET_WEBHOOKLOGS_LOADING, + payload: true, +}); +export const stopWebhookLogsLoading = () => ({ + type: SET_WEBHOOKLOGS_LOADING, + payload: false, +}); +export const addWebhooklogList = (data) => ({ + type: ADD_WEBHOOKLOGS, + payload: data, +}); +export const addWebhookRequest = (data) => ({ + type: ADD_WEBHOOKLOGS_REQUEST, + payload: data, +}); +export const resetWebhooks = () => ({ + type: RESET_WEBHOOKLOGS, +}); \ No newline at end of file diff --git a/studio/src/constants/webhooklogs.js b/studio/src/constants/webhooklogs.js new file mode 100644 index 000000000..64cc3d3be --- /dev/null +++ b/studio/src/constants/webhooklogs.js @@ -0,0 +1,6 @@ +//Actions +export const ADD_WEBHOOKLOG = 'ADD_WEBHOOKLOG'; +export const ADD_WEBHOOKLOGS = 'ADD_WEBHOOKLOGS'; +export const ADD_WEBHOOKLOGS_REQUEST = 'ADD_WEBHOOKLOGS_REQUEST'; +export const RESET_WEBHOOKLOGS = 'RESET_WEBHOOKLOGS'; +export const SET_WEBHOOKLOGS_LOADING = 'SET_WEBHOOKLOGS_LOADING'; \ No newline at end of file diff --git a/studio/src/pages/webhooks/EditWebhook.js b/studio/src/pages/webhooks/EditWebhook.js index 96a4412d8..47dccf78d 100644 --- a/studio/src/pages/webhooks/EditWebhook.js +++ b/studio/src/pages/webhooks/EditWebhook.js @@ -4,9 +4,10 @@ import { useDispatch, useSelector } from 'react-redux'; import { updateWebhook, getWebhook } from '../../actions/webhooks'; import { useHistory } from 'react-router-dom'; import { useParams } from 'react-router-dom'; -import { Skeleton } from 'antd'; +import { Skeleton , Row , Col } from 'antd'; import RecordNotFound from '../../components/ErrorsAndImage/RecordNotFound'; import { Helmet } from 'react-helmet'; +import Webhooklogs from './webhooklogs'; function EditWebhook() { const history = useHistory(); @@ -37,7 +38,10 @@ function EditWebhook() { return ( <> - + + + + ); } diff --git a/studio/src/pages/webhooks/components/WebhookForm.js b/studio/src/pages/webhooks/components/WebhookForm.js index f3c810564..2a559ecd4 100644 --- a/studio/src/pages/webhooks/components/WebhookForm.js +++ b/studio/src/pages/webhooks/components/WebhookForm.js @@ -3,15 +3,8 @@ import { Button, Form, Input, Space, Switch, Checkbox, Row, Col } from 'antd'; import { useDispatch, useSelector } from 'react-redux'; import { getEvents } from '../../../actions/events'; import deepEqual from 'deep-equal'; +import { getEventName } from '../../../utils/event'; -const layout = { - labelCol: { - span: 8, - }, - wrapperCol: { - span: 10, - }, -}; const tailLayout = { wrapperCol: { offset: 10, @@ -19,13 +12,21 @@ const tailLayout = { }, }; -const buttonLayout = { - wrapperCol: { - offset: 2, - span: 10, - }, -}; const WebhookForm = ({ onCreate, data = {} }) => { + const layout = { + labelCol: { + span: data?.id ? 5 : 8, + }, + wrapperCol: { + span: 10, + }, + }; + const buttonLayout = { + wrapperCol: { + offset: data?.id ? 1 : 2, + span: 10, + }, + }; const [form] = Form.useForm(); const [valueChange, setValueChange] = React.useState(false); const dispatch = useDispatch(); @@ -62,13 +63,6 @@ const WebhookForm = ({ onCreate, data = {} }) => { const fetchEvents = () => { dispatch(getEvents(filters)); }; - const getEventName = (eventLabel) => { - var labelArr = eventLabel.split('.'); - for (var i = 0; i < labelArr.length; i++) { - labelArr[i] = labelArr[i][0].toUpperCase() + labelArr[i].slice(1); - } - return labelArr.join(' '); - }; if (events) { return (
{ {...buttonLayout} style={{ display: 'flex', - justifyContent: 'center', + justifyContent: 'space-around', alignItems: 'center', }} > diff --git a/studio/src/pages/webhooks/webhooklogs/components/WebhookLogsList.js b/studio/src/pages/webhooks/webhooklogs/components/WebhookLogsList.js new file mode 100644 index 000000000..3e5c1ba65 --- /dev/null +++ b/studio/src/pages/webhooks/webhooklogs/components/WebhookLogsList.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Table } from 'antd'; +import { getEventName } from '../../../../utils/event'; +import { getDateAndTimeFromString } from '../../../../utils/date'; +function WebhookLogsList({data, filters, setFilters}) { + const columns = [ + { + title: 'Event', + dataIndex: 'event', + key: 'event', + render: (_, record) => getEventName(record.event) + }, + { + title: 'Time', + dataIndex: 'created_at', + key: 'created_at', + render: (_, record) => getDateAndTimeFromString(record.created_at) + } + ]; + + return ( + setFilters({ page: pageNumber, limit: pageSize }), + pageSizeOptions: ['10', '15', '20'], + }} + /> + ); +} + +export default WebhookLogsList; diff --git a/studio/src/pages/webhooks/webhooklogs/index.js b/studio/src/pages/webhooks/webhooklogs/index.js new file mode 100644 index 000000000..186ec00f5 --- /dev/null +++ b/studio/src/pages/webhooks/webhooklogs/index.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { Space } from 'antd'; +import { useDispatch, useSelector } from 'react-redux'; +import deepEqual from 'deep-equal'; +import { getWebhooklogs } from '../../../actions/webhooklogs'; +import WebhookLogsList from './components/WebhookLogsList'; + +function Webhooklogs({WebhookId}) { + const spaces = useSelector(({ spaces }) => spaces); + const dispatch = useDispatch(); + const [filters, setFilters] = React.useState({ + page: 1, + limit: 20, + }); + const { webhooklogs, total, loading } = useSelector((state) => { + const node = state.webhooklogs.req.find((item) => { + return deepEqual(item.query, filters); + }); + if (node) + return { + webhooklogs: node.data.map((element) => state.webhooklogs.details[element]), + total: node.total, + loading: state.webhooks.loading, + }; + return { webhooklogs: [], total: 0, loading: state.webhooklogs.loading }; + }); + React.useEffect(() => { + fetchWebhooklogs(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filters]); + + const fetchWebhooklogs = () => { + dispatch(getWebhooklogs(WebhookId,filters)); + }; + + return ( + + + + ); +} + +export default Webhooklogs; diff --git a/studio/src/reducers/index.js b/studio/src/reducers/index.js index 2bf0e435a..eed63fe93 100644 --- a/studio/src/reducers/index.js +++ b/studio/src/reducers/index.js @@ -34,6 +34,7 @@ import profile from './profileReducer'; import search from './searchReducer'; import sachFactChecks from './sachFactChecksReducer'; import roles from './rolesReducer'; +import webhooklogs from './webhooklogsReducer'; const appReducer = combineReducers({ admin, @@ -66,6 +67,7 @@ const appReducer = combineReducers({ sidebar, events, webhooks, + webhooklogs, profile, search, sachFactChecks, diff --git a/studio/src/reducers/webhooklogsReducer.js b/studio/src/reducers/webhooklogsReducer.js new file mode 100644 index 000000000..b05fae570 --- /dev/null +++ b/studio/src/reducers/webhooklogsReducer.js @@ -0,0 +1,51 @@ +import { + ADD_WEBHOOKLOG, + ADD_WEBHOOKLOGS, + ADD_WEBHOOKLOGS_REQUEST, + SET_WEBHOOKLOGS_LOADING, + RESET_WEBHOOKLOGS, +} from '../constants/webhooklogs'; +import deepEqual from 'deep-equal'; + +const initialState = { + req: [], + details: {}, + loading: true, +}; + +export default function webhooklogsReducer(state = initialState, action = {}) { + switch (action.type) { + case RESET_WEBHOOKLOGS: + return { + ...state, + req: [], + details: {}, + loading: true, + }; + case SET_WEBHOOKLOGS_LOADING: + return { + ...state, + loading: action.payload, + }; + case ADD_WEBHOOKLOGS_REQUEST: + return { + ...state, + req: state.req + .filter((value) => !deepEqual(value.query, action.payload.query)) + .concat(action.payload), + }; + case ADD_WEBHOOKLOGS: + if (action.payload.length === 0) { + return state; + } + return { + ...state, + details: { + ...state.details, + ...action?.payload?.reduce((obj, item) => Object.assign(obj, { [item.id]: item }), {}), + }, + }; + default: + return state; + } +} diff --git a/studio/src/utils/date.js b/studio/src/utils/date.js index 71ea06c52..c2fba7cb8 100644 --- a/studio/src/utils/date.js +++ b/studio/src/utils/date.js @@ -9,3 +9,5 @@ export const getDatefromStringWithoutDay = (dateString) => { const dateObj = new Date(Date.parse(dateString)); return `${listOfMonths[dateObj.getMonth()]} ${dateObj.getDate()} ${dateObj.getFullYear()}`; }; + +export const getDateAndTimeFromString = (dateString) => new Date(dateString).toLocaleString().replaceAll(",","").replaceAll("/","-") \ No newline at end of file diff --git a/studio/src/utils/event.js b/studio/src/utils/event.js new file mode 100644 index 000000000..393b08429 --- /dev/null +++ b/studio/src/utils/event.js @@ -0,0 +1,7 @@ +export const getEventName = (eventLabel) => { + var labelArr = eventLabel.split('.'); + for (var i = 0; i < labelArr.length; i++) { + labelArr[i] = labelArr[i][0].toUpperCase() + labelArr[i].slice(1); + } + return labelArr.join(' '); + }; \ No newline at end of file