mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-26 01:23:16 +00:00
Merge pull request #1359 from linkedin/nacho-remove-unusedcode
Removed old redux unused code
This commit is contained in:
commit
53ad5e3f86
@ -1,24 +0,0 @@
|
|||||||
import { assert } from '@ember/debug';
|
|
||||||
|
|
||||||
const actionSet = new Set();
|
|
||||||
const upperCaseUnderscoreRegex = /^[A-Z_]+$/g;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks that actionType strings are unique and meet a minimum set of rules
|
|
||||||
* @param {String} actionType a string representation of the action type
|
|
||||||
* @return {String} actionType
|
|
||||||
*/
|
|
||||||
export default actionType => {
|
|
||||||
assert(
|
|
||||||
`Action types must be of type string and at least have a length of 1 char, ${actionType} does not satisfy this`,
|
|
||||||
typeof actionType === 'string' && actionType.length
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
`For consistency action types must contain only uppercase and underscore characters e.g ACTION_TYPE. ${actionType} does not`,
|
|
||||||
String(actionType).match(upperCaseUnderscoreRegex)
|
|
||||||
);
|
|
||||||
assert(`The action type ${actionType} has already been previously registered`, !actionSet.has(actionType));
|
|
||||||
actionSet.add(actionType);
|
|
||||||
|
|
||||||
return actionType;
|
|
||||||
};
|
|
@ -1,74 +0,0 @@
|
|||||||
import actionSet from 'wherehows-web/actions/action-set';
|
|
||||||
import { createAction } from 'redux-actions';
|
|
||||||
import { lazyRequestPagedFlows, ActionTypes as FlowsActions } from 'wherehows-web/actions/flows';
|
|
||||||
import { lazyRequestPagedMetrics, ActionTypes as MetricsActions } from 'wherehows-web/actions/metrics';
|
|
||||||
import { lazyRequestPagedDatasets, ActionTypes as DatasetsActions } from 'wherehows-web/actions/datasets';
|
|
||||||
|
|
||||||
const ActionTypes = {
|
|
||||||
REQUEST_BROWSE_DATA: actionSet('REQUEST_BROWSE_DATA'),
|
|
||||||
RECEIVE_BROWSE_DATA: actionSet('RECEIVE_BROWSE_DATA')
|
|
||||||
};
|
|
||||||
|
|
||||||
// Caches Flux Standard (FSA) Action Creators
|
|
||||||
const requestBrowseData = createAction(ActionTypes.REQUEST_BROWSE_DATA);
|
|
||||||
const receiveBrowseData = createAction(ActionTypes.RECEIVE_BROWSE_DATA);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an Async Action Creator sync dispatches `requestBrowseData` delegates to
|
|
||||||
* Flows, Metrics and Datasets thunk creators,
|
|
||||||
* sync dispatches action in `receiveBrowseData` on completion
|
|
||||||
* @param {String|Number} page current browse page
|
|
||||||
* @param {String} entity the entity currently being viewed
|
|
||||||
* @param {Object.<String>} urls for the entities being viewed
|
|
||||||
*/
|
|
||||||
const asyncRequestBrowseData = (page, entity, urls) =>
|
|
||||||
/**
|
|
||||||
* Async Thunk
|
|
||||||
* @param {Function} dispatch
|
|
||||||
* @return {Promise.<*>}
|
|
||||||
*/
|
|
||||||
async function(dispatch) {
|
|
||||||
dispatch(requestBrowseData({ entity }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const thunks = await Promise.all([
|
|
||||||
dispatch(lazyRequestPagedFlows({ baseURL: urls.flows, query: { page } })),
|
|
||||||
dispatch(lazyRequestPagedMetrics({ baseURL: urls.metrics, query: { page } })),
|
|
||||||
dispatch(lazyRequestPagedDatasets({ baseURL: urls.datasets, query: { page } }))
|
|
||||||
]);
|
|
||||||
const [...actions] = thunks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that none of the actions has an error flag in FSA
|
|
||||||
* @type {boolean|*}
|
|
||||||
*/
|
|
||||||
const isValidBrowseData = actions.every(({ error = false }) => !error);
|
|
||||||
|
|
||||||
// If all requests are successful, apply browse data for each entity
|
|
||||||
if (isValidBrowseData) {
|
|
||||||
const browseData = actions.reduce((browseData, { payload, type }) => {
|
|
||||||
const { count, itemsPerPage } = payload;
|
|
||||||
const entity = {
|
|
||||||
[FlowsActions.RECEIVE_PAGED_FLOWS]: 'flows',
|
|
||||||
[MetricsActions.RECEIVE_PAGED_METRICS]: 'metrics',
|
|
||||||
[DatasetsActions.RECEIVE_PAGED_DATASETS]: 'datasets'
|
|
||||||
}[type];
|
|
||||||
|
|
||||||
browseData[entity] = {
|
|
||||||
count,
|
|
||||||
itemsPerPage
|
|
||||||
};
|
|
||||||
|
|
||||||
return browseData;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return dispatch(receiveBrowseData({ browseData }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatch(receiveBrowseData(new Error(`An error occurred during the data request`)));
|
|
||||||
} catch (e) {
|
|
||||||
return dispatch(receiveBrowseData(new Error(`An error occurred during the data request: ${e}`)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { ActionTypes, asyncRequestBrowseData };
|
|
@ -1,54 +0,0 @@
|
|||||||
/**
|
|
||||||
* Takes a requestActionCreator to trigger the action indicating that a request
|
|
||||||
* has occurred in the application, and a receiverActionCreator to trigger, indicating
|
|
||||||
* that data for the request has been received.
|
|
||||||
* @param {Function} requesterActionCreator redux action creator
|
|
||||||
* @param {Function} receiverActionCreator redux action creator
|
|
||||||
* @param asyncExecutor
|
|
||||||
* @return {function(String, Number|String): Function}
|
|
||||||
*/
|
|
||||||
const createLazyRequest = (
|
|
||||||
requesterActionCreator,
|
|
||||||
receiverActionCreator,
|
|
||||||
asyncExecutor
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Object} props = {}
|
|
||||||
*/
|
|
||||||
) => (props = {}) =>
|
|
||||||
/**
|
|
||||||
* Redux Thunk. Function takes a store dispatch, and unused getState that get passed
|
|
||||||
* into the returned function from createAsyncThunk
|
|
||||||
* @arg {Function} dispatch callback passed in by redux-thunk
|
|
||||||
*/
|
|
||||||
function(dispatch) {
|
|
||||||
dispatch(requesterActionCreator(Object.assign({}, props)));
|
|
||||||
return createAsyncThunk(receiverActionCreator, asyncExecutor)(...arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Async action creator, fetches a list of entities for the currentPage in the
|
|
||||||
* store
|
|
||||||
* @param {Function} receiverActionCreator
|
|
||||||
* @param {Function} asyncExecutor
|
|
||||||
*/
|
|
||||||
const createAsyncThunk = (
|
|
||||||
receiverActionCreator,
|
|
||||||
asyncExecutor
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Function} dispatch callback for action dispatch from redux thunk
|
|
||||||
* @param {Function} getState callback to get the current store state
|
|
||||||
* @return {Promise.<*>}
|
|
||||||
*/
|
|
||||||
) => async (dispatch, getState) => {
|
|
||||||
const response = await asyncExecutor(getState);
|
|
||||||
|
|
||||||
if (response.status === 'ok') {
|
|
||||||
return dispatch(receiverActionCreator(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatch(receiverActionCreator(new Error(`Request failed with status ${status}`)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export { createLazyRequest };
|
|
@ -1 +0,0 @@
|
|||||||
export * from 'wherehows-web/actions/entities/entities';
|
|
@ -1,37 +0,0 @@
|
|||||||
import { ActionTypes } from 'wherehows-web/actions/browse';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial state for browse feature
|
|
||||||
* @type {{entity: string}}
|
|
||||||
*/
|
|
||||||
const initialState = {
|
|
||||||
entity: 'datasets',
|
|
||||||
isFetching: false,
|
|
||||||
browseData: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduces browse actions into state object
|
|
||||||
* @param {Object} state = initialState
|
|
||||||
* @param {Object} action Flux Standard Action FSA object
|
|
||||||
* @return {Object} final state of the browse key in the store
|
|
||||||
*/
|
|
||||||
export default (state = initialState, action = {}) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case ActionTypes.REQUEST_BROWSE_DATA:
|
|
||||||
case ActionTypes.SELECT_BROWSE_DATA:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
isFetching: true,
|
|
||||||
entity: action.payload.entity
|
|
||||||
});
|
|
||||||
|
|
||||||
case ActionTypes.RECEIVE_BROWSE_DATA:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
isFetching: false,
|
|
||||||
browseData: action.payload.browseData
|
|
||||||
});
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,46 +0,0 @@
|
|||||||
import { ActionTypes } from 'wherehows-web/actions/browse/entity';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial state for browse.entity feature
|
|
||||||
* @type {{currentEntity: string, isFetching: boolean, datasets: {listURL: string, query: {}, queryParams: Array}, flows: {listURL: string, query: {}, queryParams: Array}, metrics: {listURL: string, query: {}, queryParams: Array}}}
|
|
||||||
*/
|
|
||||||
const initialState = {
|
|
||||||
currentEntity: 'datasets',
|
|
||||||
isFetching: false,
|
|
||||||
datasets: { listURL: '', query: {}, queryParams: [] },
|
|
||||||
flows: { listURL: '', query: {}, queryParams: [] },
|
|
||||||
metrics: { listURL: '', query: {}, queryParams: [] }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduces browse.entity actions into state object
|
|
||||||
* @param {Object} state = initialState
|
|
||||||
* @param {Object} action Flux Standard Action FSA object
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
export default (state = initialState, action = {}) => {
|
|
||||||
const { payload = {} } = action;
|
|
||||||
const currentEntity = payload.entity;
|
|
||||||
const previousProps = state[currentEntity];
|
|
||||||
|
|
||||||
switch (action.type) {
|
|
||||||
case ActionTypes.REQUEST_NODE_LIST:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
currentEntity,
|
|
||||||
isFetching: true,
|
|
||||||
[currentEntity]: Object.assign({}, previousProps, {
|
|
||||||
listURL: payload.listURL,
|
|
||||||
query: payload.query,
|
|
||||||
queryParams: payload.queryParams
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
case ActionTypes.RECEIVE_NODE_LIST:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
isFetching: false
|
|
||||||
});
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,68 +0,0 @@
|
|||||||
import {
|
|
||||||
initializeState,
|
|
||||||
urnsToNodeUrn,
|
|
||||||
receiveEntities,
|
|
||||||
createUrnMapping,
|
|
||||||
createPageMapping
|
|
||||||
} from 'wherehows-web/reducers/entities';
|
|
||||||
import { ActionTypes } from 'wherehows-web/actions/datasets';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default initial state for metrics slice. Appends a byName property to the shared representation.
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
const initialState = Object.assign({}, initializeState(), {
|
|
||||||
nodesByUrn: []
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* datasets root reducer
|
|
||||||
* Takes the `datasets` slice of the state tree and performs the specified reductions for each action
|
|
||||||
* @param {Object} state slice of the state tree this reducer is responsible for
|
|
||||||
* @param {Object} action Flux Standard Action representing the action to be preformed on the state
|
|
||||||
* @prop {String} action.type actionType
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
export default (state = initialState, action = {}) => {
|
|
||||||
switch (action.type) {
|
|
||||||
// Action indicating a request for datasets by page
|
|
||||||
case ActionTypes.SELECT_PAGED_DATASETS:
|
|
||||||
case ActionTypes.REQUEST_PAGED_DATASETS:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
query: Object.assign({}, state.query, {
|
|
||||||
page: action.payload.query.page
|
|
||||||
}),
|
|
||||||
baseURL: action.payload.baseURL || state.baseURL,
|
|
||||||
isFetching: true
|
|
||||||
});
|
|
||||||
|
|
||||||
case ActionTypes.REQUEST_PAGED_URN_DATASETS:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
query: Object.assign({}, state.query, action.payload.query)
|
|
||||||
});
|
|
||||||
|
|
||||||
case ActionTypes.RECEIVE_PAGED_DATASETS: // Action indicating a receipt of datasets by page
|
|
||||||
case ActionTypes.RECEIVE_PAGED_URN_DATASETS:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
isFetching: false,
|
|
||||||
byId: receiveEntities('datasets')(state.byId, action.payload),
|
|
||||||
byUrn: createUrnMapping('datasets')(state.byUrn, action.payload),
|
|
||||||
byPage: createPageMapping('datasets')(state.byPage, action.payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
case ActionTypes.REQUEST_DATASET_NODES: // Action indicating a request for list nodes
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
query: Object.assign({}, state.query, action.payload.query),
|
|
||||||
isFetching: true
|
|
||||||
});
|
|
||||||
|
|
||||||
case ActionTypes.RECEIVE_DATASET_NODES: // Action indicating a receipt of list nodes / datasets for dataset urn
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
isFetching: false,
|
|
||||||
nodesByUrn: urnsToNodeUrn(state, action.payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,164 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { mapEntitiesToIds } from 'wherehows-web/reducers/utils';
|
|
||||||
|
|
||||||
const { merge, union } = _;
|
|
||||||
|
|
||||||
// Initial state for entities (metrics, flows, datasets)
|
|
||||||
const _initialState = {
|
|
||||||
count: null,
|
|
||||||
page: null,
|
|
||||||
itemsPerPage: null,
|
|
||||||
totalPages: null,
|
|
||||||
query: {
|
|
||||||
urn: '',
|
|
||||||
page: ''
|
|
||||||
},
|
|
||||||
byId: {},
|
|
||||||
byPage: {},
|
|
||||||
byUrn: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure we deep clone since this shape is shared amongst entities
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
const initializeState = () => JSON.parse(JSON.stringify(_initialState));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges previous entities and a new map of ids to entities into a new map
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const appendEntityIdMap = (
|
|
||||||
entityName
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Object} prevEntities current list of ids mapped to entities
|
|
||||||
* @param {Object} props
|
|
||||||
* @props {Array} props[entityName] list of received entities
|
|
||||||
*/
|
|
||||||
) => (prevEntities, props) => merge({}, prevEntities, mapEntitiesToIds(props[entityName]));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a list of child entity ids for a given urn. urn is null for top level entities
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const appendUrnIdMap = (
|
|
||||||
entityName
|
|
||||||
/**
|
|
||||||
* @param {Object} urnEntities current mapping of Urns to ids
|
|
||||||
* @param {Object} props payload with new objects containing id, and urn prop
|
|
||||||
*/
|
|
||||||
) => (urnEntities, { parentUrn = null, [entityName]: entities = [] }) =>
|
|
||||||
Object.assign({}, urnEntities, {
|
|
||||||
[parentUrn]: union(urnEntities[parentUrn], entities.mapBy('id'))
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a list of child entities ids for a given name. name is null for top level entities
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const appendNamedIdMap = entityName => (nameEntities, { parentName = null, [entityName]: entities = [] }) =>
|
|
||||||
Object.assign({}, nameEntities, {
|
|
||||||
[parentName]: union(nameEntities[parentName], entities.mapBy('id'))
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a curried function that receives entityName to lookup on the props object
|
|
||||||
* @param {String} entityName
|
|
||||||
* @return {Function}
|
|
||||||
*/
|
|
||||||
const entitiesToPage = (
|
|
||||||
entityName
|
|
||||||
/**
|
|
||||||
* a new map of page numbers to datasetIds
|
|
||||||
* @param {Object} pagedEntities
|
|
||||||
* @param {Object} props
|
|
||||||
* @props {Array} props[entityName] list of received entities
|
|
||||||
* @return {Object} props.page page the page that contains the returned list of entities
|
|
||||||
*/
|
|
||||||
) => (pagedEntities = {}, props) => {
|
|
||||||
const entities = props[entityName];
|
|
||||||
const { page } = props;
|
|
||||||
|
|
||||||
return Object.assign({}, pagedEntities, {
|
|
||||||
[page]: union(pagedEntities[page], entities.mapBy('id'))
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a urn to a list of child nodes from the list api
|
|
||||||
* @param {Object} state
|
|
||||||
* @param {Array} nodes
|
|
||||||
* @param {String} parentUrn
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
const urnsToNodeUrn = (state, { nodes = [], parentUrn = null } = {}) => {
|
|
||||||
const { nodesByUrn } = state;
|
|
||||||
return Object.assign({}, nodesByUrn, {
|
|
||||||
[parentUrn]: union(nodes)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a name to a list of child nodes from the list api
|
|
||||||
* @param {Object} state
|
|
||||||
* @param {Array} nodes
|
|
||||||
* @param {String} parentName
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
const namesToNodeName = (state, { nodes = [], parentName = null } = {}) => {
|
|
||||||
const { nodesByName } = state;
|
|
||||||
return Object.assign({}, nodesByName, {
|
|
||||||
[parentName]: union(nodes)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes an identifier for an entity and returns a reducing function with the entityName as context
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const receiveEntities = (
|
|
||||||
entityName
|
|
||||||
/**
|
|
||||||
* entities (flows|metrics|datasets) for the ActionTypes.RECEIVE_PAGED_[ENTITY_NAME] action
|
|
||||||
* @param {Object} state previous state for datasets
|
|
||||||
* @param {Object} payload data received through ActionTypes.RECEIVE_PAGED_[ENTITY_NAME]
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
) => (state, payload = {}) => {
|
|
||||||
return appendEntityIdMap(entityName)(state, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const createUrnMapping = entityName => (state, payload = {}) => {
|
|
||||||
return appendUrnIdMap(entityName)(state, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Curries an entityName into a function to append named entity entities
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const createNameMapping = entityName => (state, payload = {}) => {
|
|
||||||
return appendNamedIdMap(entityName)(state, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a list of entities to a page
|
|
||||||
* @param {String} entityName
|
|
||||||
*/
|
|
||||||
const createPageMapping = entityName => (state, payload = {}) => {
|
|
||||||
return entitiesToPage(entityName)(state, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
initializeState,
|
|
||||||
namesToNodeName,
|
|
||||||
urnsToNodeUrn,
|
|
||||||
receiveEntities,
|
|
||||||
createUrnMapping,
|
|
||||||
createPageMapping,
|
|
||||||
createNameMapping
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export * from 'wherehows-web/reducers/entities/entities';
|
|
@ -1,13 +0,0 @@
|
|||||||
import datasets from 'wherehows-web/reducers/datasets';
|
|
||||||
import flows from 'wherehows-web/reducers/flows';
|
|
||||||
import metrics from 'wherehows-web/reducers/metrics';
|
|
||||||
import browse from 'wherehows-web/reducers/browse';
|
|
||||||
import browseEntity from 'wherehows-web/reducers/browse/entity';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
datasets,
|
|
||||||
flows,
|
|
||||||
metrics,
|
|
||||||
browse,
|
|
||||||
browseEntity
|
|
||||||
};
|
|
@ -1,2 +0,0 @@
|
|||||||
import mapEntitiesToIds from 'wherehows-web/reducers/utils/map-entities-to-ids';
|
|
||||||
export { mapEntitiesToIds };
|
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Creates a map of ids to entity
|
|
||||||
* @param {Array} entities = [] list of entities to map to ids
|
|
||||||
*/
|
|
||||||
export default (entities = []) =>
|
|
||||||
entities.reduce((idMap, entity) => {
|
|
||||||
idMap[entity.id] = entity;
|
|
||||||
return idMap;
|
|
||||||
}, {});
|
|
Loading…
x
Reference in New Issue
Block a user