mirror of
https://github.com/strapi/strapi.git
synced 2025-11-03 11:25:17 +00:00
Merge branch 'master' into develop
This commit is contained in:
commit
62c01a08e5
@ -28,7 +28,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -57,7 +57,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -86,7 +86,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -148,7 +148,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -176,7 +176,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@ -211,7 +211,7 @@ Strapi currently supports `Node.js v12.x.x`. The following steps will install No
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
|
||||
...
|
||||
sudo apt-get install nodejs
|
||||
...
|
||||
|
||||
@ -70,7 +70,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -99,7 +99,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -128,7 +128,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -486,7 +486,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -519,7 +519,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.log('An error occurred:', error);
|
||||
console.log('An error occurred:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
@ -545,7 +545,7 @@ axios
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error.
|
||||
console.err('An error occured:', err);
|
||||
console.error('An error occured:', error.response);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@ -582,8 +582,8 @@ class Wysiwyg extends React.Component {
|
||||
Modifier.replaceText(contentState, this.getSelection(), text);
|
||||
|
||||
onChange = editorState => {
|
||||
this.setState({ editorState });
|
||||
this.sendData(editorState);
|
||||
this.setState({ editorState });
|
||||
};
|
||||
|
||||
handleTab = e => {
|
||||
|
||||
@ -23,6 +23,16 @@ const generateFileName = name => {
|
||||
return `${baseName}_${randomSuffix()}`;
|
||||
};
|
||||
|
||||
const sendMediaMetrics = data => {
|
||||
if (_.has(data, 'caption') && !_.isEmpty(data.caption)) {
|
||||
strapi.telemetry.send('didSaveMediaWithCaption');
|
||||
}
|
||||
|
||||
if (_.has(data, 'alternativeText') && !_.isEmpty(data.alternativeText)) {
|
||||
strapi.telemetry.send('didSaveMediaWithAlternativeText');
|
||||
}
|
||||
};
|
||||
|
||||
const combineFilters = params => {
|
||||
// FIXME: until we support boolean operators for querying we need to make mime_ncontains use AND instead of OR
|
||||
if (_.has(params, 'mime_ncontains') && Array.isArray(params.mime_ncontains)) {
|
||||
@ -244,12 +254,16 @@ module.exports = {
|
||||
},
|
||||
|
||||
async update(params, values) {
|
||||
sendMediaMetrics(values);
|
||||
|
||||
const res = await strapi.query('file', 'upload').update(params, values);
|
||||
strapi.eventHub.emit('media.update', { media: res });
|
||||
return res;
|
||||
},
|
||||
|
||||
async add(values) {
|
||||
sendMediaMetrics(values);
|
||||
|
||||
const res = await strapi.query('file', 'upload').create(values);
|
||||
strapi.eventHub.emit('media.create', { media: res });
|
||||
return res;
|
||||
@ -335,6 +349,12 @@ module.exports = {
|
||||
},
|
||||
|
||||
setSettings(value) {
|
||||
if (value.responsiveDimensions === true) {
|
||||
strapi.telemetry.send('didEnableResponsiveDimensions');
|
||||
} else {
|
||||
strapi.telemetry.send('didDisableResponsiveDimensions');
|
||||
}
|
||||
|
||||
return strapi
|
||||
.store({
|
||||
type: 'plugin',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const fs = require('../fs');
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
jest.mock('fs-extra');
|
||||
|
||||
@ -23,8 +24,8 @@ describe('Strapi fs utils', () => {
|
||||
|
||||
await strapiFS.writeAppFile('test', content);
|
||||
|
||||
expect(fse.ensureFile).toHaveBeenCalledWith('/tmp/test');
|
||||
expect(fse.writeFile).toHaveBeenCalledWith('/tmp/test', content);
|
||||
expect(fse.ensureFile).toHaveBeenCalledWith(path.join('/', 'tmp', 'test'));
|
||||
expect(fse.writeFile).toHaveBeenCalledWith(path.join('/', 'tmp', 'test'), content);
|
||||
});
|
||||
|
||||
test('Normalize the path to avoid relative access to folders in parent directories', async () => {
|
||||
@ -34,8 +35,8 @@ describe('Strapi fs utils', () => {
|
||||
|
||||
await strapiFS.writeAppFile('../../test', content);
|
||||
|
||||
expect(fse.ensureFile).toHaveBeenCalledWith('/tmp/test');
|
||||
expect(fse.writeFile).toHaveBeenCalledWith('/tmp/test', content);
|
||||
expect(fse.ensureFile).toHaveBeenCalledWith(path.join('/', 'tmp', 'test'));
|
||||
expect(fse.writeFile).toHaveBeenCalledWith(path.join('/', 'tmp', 'test'), content);
|
||||
});
|
||||
|
||||
test('Works with array path', async () => {
|
||||
@ -45,8 +46,11 @@ describe('Strapi fs utils', () => {
|
||||
|
||||
await strapiFS.writeAppFile(['test', 'sub', 'path'], content);
|
||||
|
||||
expect(fse.ensureFile).toHaveBeenCalledWith('/tmp/test/sub/path');
|
||||
expect(fse.writeFile).toHaveBeenCalledWith('/tmp/test/sub/path', content);
|
||||
expect(fse.ensureFile).toHaveBeenCalledWith(path.join('/', 'tmp', 'test', 'sub', 'path'));
|
||||
expect(fse.writeFile).toHaveBeenCalledWith(
|
||||
path.join('/', 'tmp', 'test', 'sub', 'path'),
|
||||
content
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -58,11 +62,7 @@ describe('Strapi fs utils', () => {
|
||||
|
||||
strapiFS.writeAppFile = jest.fn(() => Promise.resolve());
|
||||
|
||||
await strapiFS.writePluginFile(
|
||||
'users-permissions',
|
||||
['test', 'sub', 'path'],
|
||||
content
|
||||
);
|
||||
await strapiFS.writePluginFile('users-permissions', ['test', 'sub', 'path'], content);
|
||||
|
||||
expect(strapiFS.writeAppFile).toHaveBeenCalledWith(
|
||||
'extensions/users-permissions/test/sub/path',
|
||||
|
||||
@ -52,7 +52,7 @@ const createCoreStore = ({ environment: defaultEnv, db }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.type === 'object' || data.type === 'array' || data.type === 'boolean') {
|
||||
if (data.type === 'object' || data.type === 'array' || data.type === 'boolean' || data.type === 'string') {
|
||||
try {
|
||||
return JSON.parse(data.value);
|
||||
} catch (err) {
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
const wrapWithRateLimiter = require('../rate-limiter');
|
||||
|
||||
describe('Telemetry daily RateLimiter', () => {
|
||||
test('Passes event and payload to sender', async () => {
|
||||
const sender = jest.fn(() => Promise.resolve(true));
|
||||
|
||||
const send = wrapWithRateLimiter(sender, { limitedEvents: ['testEvent'] });
|
||||
|
||||
const payload = { key: 'value' };
|
||||
await send('notRestricted', payload);
|
||||
|
||||
expect(sender).toHaveBeenCalledWith('notRestricted', payload);
|
||||
});
|
||||
|
||||
test('Calls sender if event is not restricted', async () => {
|
||||
const sender = jest.fn(() => Promise.resolve(true));
|
||||
|
||||
const send = wrapWithRateLimiter(sender, { limitedEvents: ['testEvent'] });
|
||||
|
||||
await send('notRestricted');
|
||||
|
||||
expect(sender).toHaveBeenCalledWith('notRestricted', undefined);
|
||||
});
|
||||
|
||||
test('Calls the sender as many times as request when events is not restricted', async () => {
|
||||
const sender = jest.fn(() => Promise.resolve(true));
|
||||
|
||||
const send = wrapWithRateLimiter(sender, { limitedEvents: ['testEvent'] });
|
||||
|
||||
await send('notRestricted');
|
||||
await send('notRestricted');
|
||||
await send('notRestricted');
|
||||
|
||||
expect(sender).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('Calls the sender only once when event is restricted', async () => {
|
||||
const sender = jest.fn(() => Promise.resolve(true));
|
||||
|
||||
const send = wrapWithRateLimiter(sender, { limitedEvents: ['restrictedEvent'] });
|
||||
|
||||
await send('restrictedEvent');
|
||||
await send('restrictedEvent');
|
||||
await send('restrictedEvent');
|
||||
|
||||
expect(sender).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@ -3,60 +3,27 @@
|
||||
* Strapi telemetry package.
|
||||
* You can learn more at https://strapi.io/documentation/3.0.0-beta.x/global-strapi/usage-information.html#commitment-to-our-users-data-collection
|
||||
*/
|
||||
const os = require('os');
|
||||
|
||||
const isDocker = require('is-docker');
|
||||
const { machineIdSync } = require('node-machine-id');
|
||||
const fetch = require('node-fetch');
|
||||
const ciEnv = require('ci-info');
|
||||
const { scheduleJob } = require('node-schedule');
|
||||
|
||||
const wrapWithRateLimit = require('./rate-limiter');
|
||||
const createSender = require('./sender');
|
||||
const createMiddleware = require('./middleware');
|
||||
const isTruthy = require('./is-truthy');
|
||||
|
||||
const LIMITED_EVENTS = [
|
||||
'didSaveMediaWithAlternativeText',
|
||||
'didSaveMediaWithCaption',
|
||||
'didDisableResponsiveDimensions',
|
||||
'didEnableResponsiveDimensions',
|
||||
];
|
||||
|
||||
const createTelemetryInstance = strapi => {
|
||||
const uuid = strapi.config.uuid;
|
||||
const deviceId = machineIdSync();
|
||||
|
||||
const isDisabled = !uuid || isTruthy(process.env.STRAPI_TELEMETRY_DISABLED);
|
||||
|
||||
const anonymous_metadata = {
|
||||
environment: strapi.config.environment,
|
||||
os: os.type(),
|
||||
osPlatform: os.platform(),
|
||||
osRelease: os.release(),
|
||||
nodeVersion: process.version,
|
||||
docker: process.env.DOCKER || isDocker(),
|
||||
isCI: ciEnv.isCI,
|
||||
version: strapi.config.info.strapi,
|
||||
strapiVersion: strapi.config.info.strapi,
|
||||
};
|
||||
|
||||
const sendEvent = async (event, payload) => {
|
||||
// do not send anything when user has disabled analytics
|
||||
if (isDisabled) return true;
|
||||
|
||||
try {
|
||||
const res = await fetch('https://analytics.strapi.io/track', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
event,
|
||||
uuid,
|
||||
deviceId,
|
||||
properties: {
|
||||
...payload,
|
||||
...anonymous_metadata,
|
||||
},
|
||||
}),
|
||||
timeout: 1000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
return res.ok;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const sender = createSender(strapi);
|
||||
const sendEvent = wrapWithRateLimit(sender, { limitedEvents: LIMITED_EVENTS });
|
||||
|
||||
if (!isDisabled) {
|
||||
scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
|
||||
@ -64,7 +31,10 @@ const createTelemetryInstance = strapi => {
|
||||
}
|
||||
|
||||
return {
|
||||
send: sendEvent,
|
||||
async send(event, payload) {
|
||||
if (isDisabled) return true;
|
||||
return sendEvent(event, payload);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
27
packages/strapi/lib/services/metrics/rate-limiter.js
Normal file
27
packages/strapi/lib/services/metrics/rate-limiter.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @param events a list of events that need to be limited
|
||||
*/
|
||||
module.exports = (sender, { limitedEvents = [] } = {}) => {
|
||||
let currentDay = new Date().getDate();
|
||||
const eventCache = new Map();
|
||||
|
||||
return async (event, payload) => {
|
||||
if (!limitedEvents.includes(event)) {
|
||||
return sender(event, payload);
|
||||
}
|
||||
|
||||
if (new Date().getDate() !== currentDay) {
|
||||
eventCache.clear();
|
||||
currentDay = new Date().getDate();
|
||||
}
|
||||
|
||||
if (eventCache.has(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
eventCache.set(event, true);
|
||||
return sender(event, payload);
|
||||
};
|
||||
};
|
||||
53
packages/strapi/lib/services/metrics/sender.js
Normal file
53
packages/strapi/lib/services/metrics/sender.js
Normal file
@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const isDocker = require('is-docker');
|
||||
const { machineIdSync } = require('node-machine-id');
|
||||
const fetch = require('node-fetch');
|
||||
const ciEnv = require('ci-info');
|
||||
|
||||
/**
|
||||
* Create a send function for event with all the necessary metadatas
|
||||
* @param {Object} strapi strapi app
|
||||
* @returns {Function} (event, payload) -> Promise{boolean}
|
||||
*/
|
||||
module.exports = strapi => {
|
||||
const uuid = strapi.config.uuid;
|
||||
const deviceId = machineIdSync();
|
||||
|
||||
const anonymous_metadata = {
|
||||
environment: strapi.config.environment,
|
||||
os: os.type(),
|
||||
osPlatform: os.platform(),
|
||||
osRelease: os.release(),
|
||||
nodeVersion: process.version,
|
||||
docker: process.env.DOCKER || isDocker(),
|
||||
isCI: ciEnv.isCI,
|
||||
version: strapi.config.info.strapi,
|
||||
strapiVersion: strapi.config.info.strapi,
|
||||
};
|
||||
|
||||
return async (event, payload = {}) => {
|
||||
try {
|
||||
const res = await fetch('https://analytics.strapi.io/track', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
event,
|
||||
uuid,
|
||||
deviceId,
|
||||
properties: {
|
||||
...payload,
|
||||
...anonymous_metadata,
|
||||
},
|
||||
}),
|
||||
timeout: 1000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
return res.ok;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user