mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
New: Server Side UI Filtering, Error Boundaries (#501)
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
parent
a95191dc3b
commit
64a8d02f77
110 changed files with 1564 additions and 431 deletions
|
@ -1,65 +0,0 @@
|
|||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import generateUUIDv4 from 'Utilities/String/generateUUIDv4';
|
||||
|
||||
function createRemoveCustomFilterReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
const newState = getSectionState(state, section);
|
||||
const index = newState.customFilters.findIndex((c) => c.key === payload.key);
|
||||
|
||||
newState.customFilters = [...newState.customFilters];
|
||||
newState.customFilters.splice(index, 1);
|
||||
|
||||
// Reset the selected filter to the first filter if the selected filter
|
||||
// is being deleted.
|
||||
// TODO: Server side collections need to have their collections refetched
|
||||
|
||||
if (newState.selectedFilterKey === payload.key) {
|
||||
newState.selectedFilterKey = newState.filters[0].key;
|
||||
}
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
};
|
||||
}
|
||||
|
||||
function createSaveCustomFilterReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
const newState = getSectionState(state, section);
|
||||
|
||||
const {
|
||||
label,
|
||||
filters
|
||||
} = payload;
|
||||
|
||||
let key = payload.key;
|
||||
|
||||
newState.customFilters = [...newState.customFilters];
|
||||
|
||||
if (key) {
|
||||
const index = newState.customFilters.findIndex((c) => c.key === key);
|
||||
|
||||
newState.customFilters.splice(index, 1, { key, label, filters });
|
||||
} else {
|
||||
key = generateUUIDv4();
|
||||
|
||||
newState.customFilters.push({
|
||||
key,
|
||||
label,
|
||||
filters
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Server side collections need to have their collections refetched
|
||||
newState.selectedFilterKey = key;
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
};
|
||||
}
|
||||
|
||||
export default function createCustomFilterReducers(section, handlers) {
|
||||
return {
|
||||
[handlers[customFilterHandlers.REMOVE]]: createRemoveCustomFilterReducer(section),
|
||||
[handlers[customFilterHandlers.SAVE]]: createSaveCustomFilterReducer(section)
|
||||
};
|
||||
}
|
|
@ -20,13 +20,13 @@ function createRemoveItemHandler(section, url) {
|
|||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
removeItem({ section, id }),
|
||||
|
||||
set({
|
||||
section,
|
||||
isDeleting: false,
|
||||
deleteError: null
|
||||
})
|
||||
}),
|
||||
|
||||
removeItem({ section, id })
|
||||
]));
|
||||
});
|
||||
|
||||
|
|
|
@ -21,10 +21,11 @@ function createSaveProviderHandler(section, url, options = {}) {
|
|||
|
||||
const {
|
||||
id,
|
||||
queryParams = {}
|
||||
queryParams = {},
|
||||
...otherPayload
|
||||
} = payload;
|
||||
|
||||
const saveData = getProviderState(payload, getState, section);
|
||||
const saveData = getProviderState({ id, ...otherPayload }, getState, section);
|
||||
|
||||
const ajaxOptions = {
|
||||
url: `${url}?${$.param(queryParams, true)}`,
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import getMonitoringOptions from 'Utilities/Artist/getMonitoringOptions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
|
@ -29,7 +29,55 @@ export const defaultState = {
|
|||
selectedFilterKey: 'all',
|
||||
filters,
|
||||
filterPredicates,
|
||||
customFilters: []
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'monitored',
|
||||
label: 'Monitored',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.ARTIST_STATUS
|
||||
},
|
||||
{
|
||||
name: 'artistType',
|
||||
label: 'Artist Type',
|
||||
type: filterBuilderTypes.EXACT
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: 'Quality Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'languageProfileId',
|
||||
label: 'Language Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.LANGUAGE_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'metadataProfileId',
|
||||
label: 'Metadata Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.METADATA_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: 'Root Folder Path',
|
||||
type: filterBuilderTypes.EXACT
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
|
|
@ -103,6 +103,20 @@ export const filterPredicates = {
|
|||
const predicate = filterTypePredicates[type];
|
||||
|
||||
return predicate(item.ratings.value * 10, filterValue);
|
||||
},
|
||||
|
||||
albumCount: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
const albumCount = item.statistics ? item.statistics.albumCount : 0;
|
||||
|
||||
return predicate(albumCount, filterValue);
|
||||
},
|
||||
|
||||
sizeOnDisk: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
const sizeOnDisk = item.statistics ? item.statistics.sizeOnDisk : 0;
|
||||
|
||||
return predicate(sizeOnDisk, filterValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import { set, updateItem } from './baseActions';
|
||||
import { filters, filterPredicates } from './artistActions';
|
||||
|
@ -79,8 +77,7 @@ export const defaultState = {
|
|||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
],
|
||||
customFilters: []
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
@ -97,8 +94,6 @@ export const SET_ARTIST_EDITOR_SORT = 'artistEditor/setArtistEditorSort';
|
|||
export const SET_ARTIST_EDITOR_FILTER = 'artistEditor/setArtistEditorFilter';
|
||||
export const SAVE_ARTIST_EDITOR = 'artistEditor/saveArtistEditor';
|
||||
export const BULK_DELETE_ARTIST = 'artistEditor/bulkDeleteArtist';
|
||||
export const REMOVE_ARTIST_EDITOR_CUSTOM_FILTER = 'artistEditor/removeArtistEditorCustomFilter';
|
||||
export const SAVE_ARTIST_EDITOR_CUSTOM_FILTER = 'artistEditor/saveArtistEditorCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -107,8 +102,6 @@ export const setArtistEditorSort = createAction(SET_ARTIST_EDITOR_SORT);
|
|||
export const setArtistEditorFilter = createAction(SET_ARTIST_EDITOR_FILTER);
|
||||
export const saveArtistEditor = createThunk(SAVE_ARTIST_EDITOR);
|
||||
export const bulkDeleteArtist = createThunk(BULK_DELETE_ARTIST);
|
||||
export const removeArtistEditorCustomFilter = createAction(REMOVE_ARTIST_EDITOR_CUSTOM_FILTER);
|
||||
export const saveArtistEditorCustomFilter = createAction(SAVE_ARTIST_EDITOR_CUSTOM_FILTER);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
@ -193,11 +186,6 @@ export const actionHandlers = handleThunks({
|
|||
export const reducers = createHandleActions({
|
||||
|
||||
[SET_ARTIST_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
|
||||
...createCustomFilterReducers(section, {
|
||||
[customFilterHandlers.REMOVE]: REMOVE_ARTIST_EDITOR_CUSTOM_FILTER,
|
||||
[customFilterHandlers.SAVE]: SAVE_ARTIST_EDITOR_CUSTOM_FILTER
|
||||
})
|
||||
[SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import moment from 'moment';
|
||||
import { createAction } from 'redux-actions';
|
||||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import { filters, filterPredicates } from './artistActions';
|
||||
|
||||
|
@ -292,7 +290,8 @@ export const defaultState = {
|
|||
{
|
||||
name: 'sizeOnDisk',
|
||||
label: 'Size on Disk',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
type: filterBuilderTypes.NUMBER,
|
||||
valueType: filterBuilderValueTypes.BYTES
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
|
@ -324,8 +323,7 @@ export const defaultState = {
|
|||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
],
|
||||
customFilters: []
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
@ -350,8 +348,6 @@ export const SET_ARTIST_TABLE_OPTION = 'artistIndex/setArtistTableOption';
|
|||
export const SET_ARTIST_POSTER_OPTION = 'artistIndex/setArtistPosterOption';
|
||||
export const SET_ARTIST_BANNER_OPTION = 'artistIndex/setArtistBannerOption';
|
||||
export const SET_ARTIST_OVERVIEW_OPTION = 'artistIndex/setArtistOverviewOption';
|
||||
export const REMOVE_ARTIST_CUSTOM_FILTER = 'artistIndex/removeArtistCustomFilter';
|
||||
export const SAVE_ARTIST_CUSTOM_FILTER = 'artistIndex/saveArtistCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -363,8 +359,7 @@ export const setArtistTableOption = createAction(SET_ARTIST_TABLE_OPTION);
|
|||
export const setArtistPosterOption = createAction(SET_ARTIST_POSTER_OPTION);
|
||||
export const setArtistBannerOption = createAction(SET_ARTIST_BANNER_OPTION);
|
||||
export const setArtistOverviewOption = createAction(SET_ARTIST_OVERVIEW_OPTION);
|
||||
export const removeArtistCustomFilter = createAction(REMOVE_ARTIST_CUSTOM_FILTER);
|
||||
export const saveArtistCustomFilter = createAction(SAVE_ARTIST_CUSTOM_FILTER);
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
|
@ -413,11 +408,6 @@ export const reducers = createHandleActions({
|
|||
...payload
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
...createCustomFilterReducers(section, {
|
||||
[customFilterHandlers.REMOVE]: REMOVE_ARTIST_CUSTOM_FILTER,
|
||||
[customFilterHandlers.SAVE]: SAVE_ARTIST_CUSTOM_FILTER
|
||||
})
|
||||
}
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
55
frontend/src/Store/Actions/customFilterActions.js
Normal file
55
frontend/src/Store/Actions/customFilterActions.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
|
||||
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'customFilters';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isDeleting: false,
|
||||
deleteError: null,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
};
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_CUSTOM_FILTERS = 'customFilters/fetchCustomFilters';
|
||||
export const SAVE_CUSTOM_FILTER = 'customFilters/saveCustomFilter';
|
||||
export const DELETE_CUSTOM_FILTER = 'customFilters/deleteCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchCustomFilters = createThunk(FETCH_CUSTOM_FILTERS);
|
||||
export const saveCustomFilter = createThunk(SAVE_CUSTOM_FILTER);
|
||||
export const deleteCustomFilter = createThunk(DELETE_CUSTOM_FILTER);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[FETCH_CUSTOM_FILTERS]: createFetchHandler(section, '/customFilter'),
|
||||
|
||||
[SAVE_CUSTOM_FILTER]: createSaveProviderHandler(section, '/customFilter'),
|
||||
|
||||
[DELETE_CUSTOM_FILTER]: createRemoveItemHandler(section, '/customFilter')
|
||||
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
export const reducers = createHandleActions({}, defaultState, section);
|
|
@ -2,6 +2,7 @@ import * as addArtist from './addArtistActions';
|
|||
import * as app from './appActions';
|
||||
import * as blacklist from './blacklistActions';
|
||||
import * as captcha from './captchaActions';
|
||||
import * as customFilters from './customFilterActions';
|
||||
import * as devices from './deviceActions';
|
||||
import * as calendar from './calendarActions';
|
||||
import * as commands from './commandActions';
|
||||
|
@ -35,6 +36,7 @@ export default [
|
|||
captcha,
|
||||
calendar,
|
||||
commands,
|
||||
customFilters,
|
||||
devices,
|
||||
albums,
|
||||
trackFiles,
|
||||
|
|
|
@ -10,6 +10,7 @@ import createHandleActions from './Creators/createHandleActions';
|
|||
// Variables
|
||||
|
||||
export const section = 'oAuth';
|
||||
const callbackUrl = `${window.location.origin}${window.Lidarr.urlBase}/oauth.html`;
|
||||
|
||||
//
|
||||
// State
|
||||
|
@ -64,6 +65,19 @@ function showOAuthWindow(url) {
|
|||
return deferred.promise();
|
||||
}
|
||||
|
||||
function executeIntermediateRequest(payload, ajaxOptions) {
|
||||
return $.ajax(ajaxOptions).then((data) => {
|
||||
return requestAction({
|
||||
action: 'continueOAuth',
|
||||
queryParams: {
|
||||
...data,
|
||||
callbackUrl
|
||||
},
|
||||
...payload
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
|
@ -72,7 +86,7 @@ export const actionHandlers = handleThunks({
|
|||
[START_OAUTH]: function(getState, payload, dispatch) {
|
||||
const actionPayload = {
|
||||
action: 'startOAuth',
|
||||
queryParams: { callbackUrl: `${window.location.origin}${window.Lidarr.urlBase}/oauth.html` },
|
||||
queryParams: { callbackUrl },
|
||||
...payload
|
||||
};
|
||||
|
||||
|
@ -85,7 +99,16 @@ export const actionHandlers = handleThunks({
|
|||
const promise = requestAction(actionPayload)
|
||||
.then((response) => {
|
||||
startResponse = response;
|
||||
return showOAuthWindow(response.oauthUrl);
|
||||
|
||||
if (response.oauthUrl) {
|
||||
return showOAuthWindow(response.oauthUrl);
|
||||
}
|
||||
|
||||
return executeIntermediateRequest(payload, response).then((intermediateResponse) => {
|
||||
startResponse = intermediateResponse;
|
||||
|
||||
return showOAuthWindow(intermediateResponse.oauthUrl);
|
||||
});
|
||||
})
|
||||
.then((queryParams) => {
|
||||
return requestAction({
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
||||
|
@ -45,8 +43,6 @@ export const defaultState = {
|
|||
}
|
||||
},
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
|
@ -143,9 +139,7 @@ export const defaultState = {
|
|||
label: 'Rejections',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
}
|
||||
],
|
||||
|
||||
customFilters: []
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
@ -163,9 +157,6 @@ export const CLEAR_RELEASES = 'releases/clearReleases';
|
|||
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
||||
export const ADD_RELEASES_CUSTOM_FILTER = 'releases/addReleasesCustomFilter';
|
||||
export const REMOVE_RELEASES_CUSTOM_FILTER = 'releases/removeReleasesCustomFilter';
|
||||
export const SAVE_RELEASES_CUSTOM_FILTER = 'releases/saveReleasesCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -177,9 +168,6 @@ export const clearReleases = createAction(CLEAR_RELEASES);
|
|||
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
||||
export const addReleasesCustomFilter = createAction(ADD_RELEASES_CUSTOM_FILTER);
|
||||
export const removeReleasesCustomFilter = createAction(REMOVE_RELEASES_CUSTOM_FILTER);
|
||||
export const saveReleasesCustomFilter = createAction(SAVE_RELEASES_CUSTOM_FILTER);
|
||||
|
||||
//
|
||||
// Helpers
|
||||
|
@ -266,11 +254,6 @@ export const reducers = createHandleActions({
|
|||
},
|
||||
|
||||
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
|
||||
...createCustomFilterReducers(section, {
|
||||
[customFilterHandlers.REMOVE]: REMOVE_RELEASES_CUSTOM_FILTER,
|
||||
[customFilterHandlers.SAVE]: SAVE_RELEASES_CUSTOM_FILTER
|
||||
})
|
||||
[SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
91
frontend/src/Store/Middleware/createSentryMiddleware.js
Normal file
91
frontend/src/Store/Middleware/createSentryMiddleware.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
import _ from 'lodash';
|
||||
import * as sentry from '@sentry/browser';
|
||||
import parseUrl from 'Utilities/String/parseUrl';
|
||||
|
||||
function cleanseUrl(url) {
|
||||
const properties = parseUrl(url);
|
||||
|
||||
return `${properties.pathname}${properties.search}`;
|
||||
}
|
||||
|
||||
function cleanseData(data) {
|
||||
const result = _.cloneDeep(data);
|
||||
|
||||
result.transaction = cleanseUrl(result.transaction);
|
||||
|
||||
if (result.exception) {
|
||||
result.exception.values.forEach((exception) => {
|
||||
const stacktrace = exception.stacktrace;
|
||||
|
||||
if (stacktrace) {
|
||||
stacktrace.frames.forEach((frame) => {
|
||||
frame.filename = cleanseUrl(frame.filename);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
result.request.url = cleanseUrl(result.request.url);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function identity(stuff) {
|
||||
return stuff;
|
||||
}
|
||||
|
||||
function createMiddleware() {
|
||||
return (store) => (next) => (action) => {
|
||||
try {
|
||||
// Adds a breadcrumb for reporting later (if necessary).
|
||||
sentry.addBreadcrumb({
|
||||
category: 'redux',
|
||||
message: action.type
|
||||
});
|
||||
|
||||
return next(action);
|
||||
} catch (err) {
|
||||
console.error(`[sentry] Reporting error to Sentry: ${err}`);
|
||||
|
||||
// Send the report including breadcrumbs.
|
||||
sentry.captureException(err, {
|
||||
extra: {
|
||||
action: identity(action),
|
||||
state: identity(store.getState())
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function createSentryMiddleware() {
|
||||
const {
|
||||
analytics,
|
||||
branch,
|
||||
version,
|
||||
release,
|
||||
isProduction
|
||||
} = window.Lidarr;
|
||||
|
||||
if (!analytics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95@sentry.io/216290' :
|
||||
'https://baede6f14da54cf48ff431479e400adf@sentry.io/1249427';
|
||||
|
||||
sentry.init({
|
||||
dsn,
|
||||
environment: isProduction ? 'production' : 'development',
|
||||
release,
|
||||
sendDefaultPii: true,
|
||||
beforeSend: cleanseData
|
||||
});
|
||||
|
||||
sentry.configureScope((scope) => {
|
||||
scope.setTag('branch', branch);
|
||||
scope.setTag('version', version);
|
||||
});
|
||||
|
||||
return createMiddleware();
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { applyMiddleware, compose } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
import sentryMiddleware from './sentryMiddleware';
|
||||
import createSentryMiddleware from './createSentryMiddleware';
|
||||
import persistState from './persistState';
|
||||
|
||||
export default function(history) {
|
||||
const middlewares = [];
|
||||
const ravenMiddleware = sentryMiddleware();
|
||||
const sentryMiddleware = createSentryMiddleware();
|
||||
|
||||
if (ravenMiddleware) {
|
||||
middlewares.push(ravenMiddleware);
|
||||
if (sentryMiddleware) {
|
||||
middlewares.push(sentryMiddleware);
|
||||
}
|
||||
|
||||
middlewares.push(routerMiddleware(history));
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import Raven from 'raven-js';
|
||||
import createRavenMiddleware from 'raven-for-redux';
|
||||
import parseUrl from 'Utilities/String/parseUrl';
|
||||
|
||||
function cleanseUrl(url) {
|
||||
const properties = parseUrl(url);
|
||||
|
||||
return `${properties.pathname}${properties.search}`;
|
||||
}
|
||||
|
||||
function cleanseData(data) {
|
||||
const result = _.cloneDeep(data);
|
||||
|
||||
result.culprit = cleanseUrl(result.culprit);
|
||||
result.request.url = cleanseUrl(result.request.url);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default function sentryMiddleware() {
|
||||
const {
|
||||
analytics,
|
||||
branch,
|
||||
version,
|
||||
release,
|
||||
isProduction
|
||||
} = window.Lidarr;
|
||||
|
||||
if (!analytics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95@sentry.io/216290' :
|
||||
'https://baede6f14da54cf48ff431479e400adf@sentry.io/1249427';
|
||||
|
||||
Raven.config(
|
||||
dsn,
|
||||
{
|
||||
environment: isProduction ? 'production' : 'development',
|
||||
release,
|
||||
tags: {
|
||||
branch,
|
||||
version
|
||||
},
|
||||
dataCallback: cleanseData
|
||||
}
|
||||
).install();
|
||||
|
||||
return createRavenMiddleware(Raven);
|
||||
}
|
|
@ -94,12 +94,24 @@ function sort(items, state) {
|
|||
return _.orderBy(items, clauses, orders);
|
||||
}
|
||||
|
||||
function createCustomFiltersSelector(type, alternateType) {
|
||||
return createSelector(
|
||||
(state) => state.customFilters.items,
|
||||
(customFilters) => {
|
||||
return customFilters.filter((customFilter) => {
|
||||
return customFilter.type === type || customFilter.type === alternateType;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createClientSideCollectionSelector(section, uiSection) {
|
||||
return createSelector(
|
||||
(state) => _.get(state, section),
|
||||
(state) => _.get(state, uiSection),
|
||||
(sectionState, uiSectionState = {}) => {
|
||||
const state = Object.assign({}, sectionState, uiSectionState);
|
||||
createCustomFiltersSelector(section, uiSection),
|
||||
(sectionState, uiSectionState = {}, customFilters) => {
|
||||
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
||||
|
||||
const filtered = filter(state.items, state);
|
||||
const sorted = sort(filtered, state);
|
||||
|
@ -107,6 +119,7 @@ function createClientSideCollectionSelector(section, uiSection) {
|
|||
return {
|
||||
...sectionState,
|
||||
...uiSectionState,
|
||||
customFilters,
|
||||
items: sorted,
|
||||
totalItems: state.items.length
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue