Merge branch 'master' into patch-content-manager

This commit is contained in:
Johann Pinson 2018-05-29 12:01:37 +02:00 committed by GitHub
commit fc85f8b47e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 161 additions and 57 deletions

View File

@ -46,6 +46,7 @@
* [Logging](advanced/logging.md)
* [Hooks](advanced/hooks.md)
* [Middlewares](advanced/middlewares.md)
* [Tracking usage](advanced/usage-tracking.md)
### API Reference
* [Table of contents](api-reference/reference.md)

View File

@ -0,0 +1,28 @@
# Usage tracking
In order to improve the product and understand how the community is using it, we are collecting non-sensitive data.
## Collected data
Here is the list of the collected data and why we need them.
- **UUID**
*Identify the app with a unique identifier.*
- **Model names and attributes names**
*Understand what kind of APIs are built with Strapi (content or product or service?)*
- **Environment state (development, staging, production)**
*Understand how the developers are using the different configurations? How many projects are started in production mode?*
- **Node modules names**
*Are developers integrating Strapi with Stripe? It means that we should develop a plugin to simplify the development process with Stripe.
Are developers using Strapi with strapi-bookshelf or strapi-mongoose? It helps us prioritize the issues.*
- **OS**
*Is the community using Windows, Linux or Mac? It helps us prioritize the issues.*
- **Build configurations**
*How many people are deploying the admin on another server?*
We are not collecting sensitive data such as databases configurations, environment or custom variables. The data are encrypted and anonymised.
> GDPR: The collected data are non-sensitive or personal data. We are compliant with the European recommendations (see our [Privacy Policy](https://strapi.io/privacy)).
## Disable
You can disable the tracking by removing the `uuid` property in the `package.json` file at the root of your project.

View File

@ -1,6 +1,7 @@
.modal {
.modal-dialog {
margin-top: 23.4rem;
max-width: 74.5rem;
margin: 16rem auto 3rem calc(50% - #{$left-menu-width});
}
}

View File

@ -130,5 +130,5 @@
"ResetPasswordToken": "Passwort-Token zurücksetzen",
"Role": "Rolle",
"New entry": "Neuer Eintrag",
"request.error.model.unknow": "Dieses Schema existiert nicht"
"request.error.model.unknown": "Dieses Schema existiert nicht"
}

View File

@ -156,7 +156,7 @@
"ResetPasswordToken": "Reset Password Token",
"Role": "Role",
"New entry": "New entry",
"request.error.model.unknow": "This model doesn't exist",
"request.error.model.unknown": "This model doesn't exist",
"Users": "Users",
"Analytics": "Analytics"
}

View File

@ -154,5 +154,5 @@
"ResetPasswordToken": "ResetPasswordToken",
"Role": "Rôle",
"New entry": "Nouvelle entrée",
"request.error.model.unknow": "Le model n'existe pas"
"request.error.model.unknown": "Le model n'existe pas"
}

View File

@ -133,5 +133,5 @@
"ResetPasswordToken": "Token resetu hasła",
"Role": "Rola",
"New entry": "Nowy wpis",
"request.error.model.unknow": "Ten model nie istnieje"
"request.error.model.unknown": "Ten model nie istnieje"
}

View File

@ -153,7 +153,7 @@
"ResetPasswordToken": "Сбросить токен пароля",
"Role": "Роль",
"New entry": "Новая запись",
"request.error.model.unknow": "Модель данных не существует",
"request.error.model.unknown": "Модель данных не существует",
"Users": "Пользователи",
"Analytics": "Аналитика"
}

View File

@ -156,7 +156,7 @@
"ResetPasswordToken": "Şifre sıfırlama anahtarı",
"Role": "Rol",
"New entry": "Yeni kayıt",
"request.error.model.unknow": "Bu model bulunmamaktadır.",
"request.error.model.unknown": "Bu model bulunmamaktadır.",
"Users": "Kullanıcılar",
"Analytics": "Analizler"
}

View File

@ -156,7 +156,7 @@
"ResetPasswordToken": "密码重置",
"Role": "角色",
"New entry": "新入口",
"request.error.model.unknow": "这个模型已不存在",
"request.error.model.unknown": "这个模型已不存在",
"Users": "用户",
"Analytics": "分析"
}

View File

@ -136,5 +136,5 @@
"ResetPasswordToken": "重設密碼的 Token",
"Role": "權限",
"New entry": "新入口",
"request.error.model.unknow": "這個資料不存在"
"request.error.model.unknown": "這個資料不存在"
}

View File

@ -1,5 +1,5 @@
.addon {
width: 5.9rem;
min-width: 5.9rem;
height: 3.4rem;
margin-top: .9rem;
background-color: rgba(16, 22, 34, 0.02);

View File

@ -19,7 +19,7 @@
.li {
margin-top: 0!important;
position: relative;
height: 5.4rem;
min-height: 5.4rem;
line-height: 5.4rem;
cursor: pointer;
&:hover {

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="41px" height="41px" viewBox="0 0 41 41" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Untitled 2</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="one_to_one">
<g id="Rectangle-13">
<g id="path-1-link" fill="#FFFFFF">
<rect id="path-1" x="0" y="0" width="41" height="41" rx="2"></rect>
</g>
<rect id="Rectangle-path" stroke-opacity="0.1" stroke="#101622" x="0.5" y="0.5" width="40" height="40" rx="2"></rect>
</g>
<rect id="Rectangle-15" stroke="#919BAE" x="14" y="21.25" width="14" height="1"></rect>
<rect id="Rectangle-14" stroke="#919BAE" x="7.5" y="18.5" width="6" height="6" rx="3"></rect>
</g>
<polygon id="Triangle" stroke="#919BAE" points="33.5 21.5 28.5 24.5 28.5 18.5"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="41px" height="41px" viewBox="0 0 41 41" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Untitled 3</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="one_to_one_selected">
<g id="Rectangle-13">
<g id="path-1-link" fill="#FFFFFF">
<rect id="path-1" x="0" y="0" width="41" height="41" rx="2"></rect>
</g>
<rect id="Rectangle-path" stroke="#1C5DE7" x="0.5" y="0.5" width="40" height="40" rx="2"></rect>
</g>
<rect id="Rectangle-15" stroke="#1C5DE7" x="14" y="21.25" width="14" height="1"></rect>
<rect id="Rectangle-14" stroke="#1C5DE7" x="7.5" y="18.5" width="6" height="6" rx="3"></rect>
</g>
<polygon id="Triangle" stroke="#1C5DE7" points="33.5 21.5 28.5 24.5 28.5 18.5"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,7 +1,7 @@
/* stylelint-disable */
.contentType, .attribute, .attributes {
> div {
width: 74.5rem;
width: 78.5rem;
}
}

View File

@ -107,6 +107,7 @@ class RelationBox extends React.Component { // eslint-disable-line react/prefer-
const content = isEmpty(this.props.input) ?
<div /> :
<Input
disabled={this.props.relationType === 'oneWay' && this.props.tabIndex === '2'}
tabIndex={this.props.tabIndex}
type={get(this.props.input, 'type')}
onChange={this.props.onChange}

View File

@ -12,6 +12,8 @@ import { FormattedMessage } from 'react-intl';
import RelationIco from 'components/RelationIco';
import OneWay from '../../assets/images/one_way.svg';
import OneWaySelected from '../../assets/images/one_way_selected.svg';
import ManyToMany from '../../assets/images/many_to_many.svg';
import ManyToManySelected from '../../assets/images/many_to_many_selected.svg';
import ManyToOne from '../../assets/images/many_to_one.svg';
@ -27,6 +29,11 @@ class RelationNaturePicker extends React.Component { // eslint-disable-line reac
constructor(props) {
super(props);
this.icos = [
{
name: 'oneWay',
ico: OneWay,
icoSelected: OneWaySelected,
},
{
name: 'oneToOne',
ico: OneToOne,
@ -54,6 +61,8 @@ class RelationNaturePicker extends React.Component { // eslint-disable-line reac
let contentTypeTarget = startCase(this.props.contentTypeTarget);
switch (this.props.selectedIco) {
case 'oneWay':
break;
case 'oneToMany':
contentTypeTarget = pluralize(contentTypeTarget);
break;

View File

@ -1,6 +1,6 @@
.relationNaturePicker { /* stylelint-disable */
display: flex;
width: 25.5rem;
width: 29.5rem;
position: relative;
padding-top: 4.5rem;
padding-left: 25px;

View File

@ -37,7 +37,7 @@
margin-top: 0!important;
}
> li:nth-child(2) {
height: 5.7rem;
min-height: 5.7rem;
padding-top: .3rem;
}
> li:last-child {

View File

@ -395,6 +395,12 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
this.props.changeInputAttribute('params.dominant', true);
}
if (target.name === 'params.nature' && target.value === "oneWay") {
this.props.changeInputAttribute('params.key', '-');
}else if (target.name === 'params.nature'){
this.props.changeInputAttribute('params.key', '');
}
} else {
this.props.changeInput(target.name, value, includes(this.props.hash, 'edit'));
}
@ -533,6 +539,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
}
render() {
// Ensure typeof(popUpFormType) is String
const popUpFormType = split(this.props.hash, '::')[1] || '';
const popUpTitle = this.generatePopUpTitle(popUpFormType);

View File

@ -169,7 +169,8 @@
"table.contentType.head.description": "Description",
"table.contentType.head.fields": "Fields",
"relation.oneToOne": "has one",
"relation.oneWay": "has one",
"relation.oneToOne": "has and belongs to one",
"relation.oneToMany": "belongs to many",
"relation.manyToOne": "has many",
"relation.manyToMany": "has and belongs to many",

View File

@ -167,6 +167,7 @@
"table.contentType.head.description": "Açıklama",
"table.contentType.head.fields": "Alanlar",
"relation.oneWay": "tek yönlü",
"relation.oneToOne": "biri var",
"relation.oneToMany": "Birçoğuna ait",
"relation.manyToOne": "Birçok var",

View File

@ -18,10 +18,10 @@ module.exports = {
model = _.toLower(model);
if (!source && !_.get(strapi.models, model)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
if (!source && !_.get(strapi.models, model)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknown' }] }]);
if (source && !_.get(strapi.plugins, [source, 'models', model])) {
return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknown' }] }]);
}
ctx.send({ model: Service.getModel(model, source) });
@ -91,7 +91,7 @@ module.exports = {
if (!name) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.name.missing' }] }]);
if (!_.includes(Service.getConnections(), connection)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.connection.unknow' }] }]);
if (strapi.models[_.toLower(name)] && name !== model) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.exist' }] }]);
if (!strapi.models[_.toLower(model)] && plugin && !strapi.plugins[_.toLower(plugin)].models[_.toLower(model)]) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
if (!strapi.models[_.toLower(model)] && plugin && !strapi.plugins[_.toLower(plugin)].models[_.toLower(model)]) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknown' }] }]);
if (!_.isNaN(parseFloat(name[0]))) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.name' }] }]);
if (plugin && !strapi.plugins[_.toLower(plugin)]) return ctx.badRequest(null, [{ message: [{ id: 'request.error.plugin.name' }] }]);
@ -161,7 +161,7 @@ module.exports = {
deleteModel: async ctx => {
const { model } = ctx.params;
if (!_.get(strapi.models, model)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
if (!_.get(strapi.models, model)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknown' }] }]);
strapi.reload.isWatching = false;

View File

@ -201,6 +201,7 @@ module.exports = {
};
switch (relation.nature) {
case 'oneWay':
case 'oneToOne':
case 'manyToOne':
attr.model = relation.target;
@ -212,7 +213,9 @@ module.exports = {
default:
}
if(relation.nature !== 'oneWay') {
attr.via = relation.key;
}
attr.dominant = relation.dominant;
if (_.trim(relation.pluginValue)) {
@ -353,6 +356,8 @@ module.exports = {
const attr = {};
switch (params.nature) {
case 'oneWay':
return;
case 'oneToOne':
case 'oneToMany':
attr.model = model.toLowerCase();

View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -33,7 +33,7 @@ module.exports = {
return lines
.map(line => {
if (['{', '}'].includes(line)) {
return ``;
return '';
}
const split = line.split(':');
@ -61,7 +61,7 @@ module.exports = {
return lines
.map((line, index) => {
if (['{', '}'].includes(line)) {
return ``;
return '';
}
const split = Object.keys(fields)[index - 1].split('(');
@ -90,7 +90,7 @@ module.exports = {
return lines
.map((line, index) => {
if ([0, lines.length - 1].includes(index)) {
return ``;
return '';
}
return line;
@ -105,9 +105,9 @@ module.exports = {
*/
getDescription: (description, model = {}) => {
const format = `"""\n`;
const format = '"""\n';
const str = _.get(description, `_description`) ||
const str = _.get(description, '_description') ||
_.isString(description) ? description : undefined ||
_.get(model, 'info.description');
@ -115,7 +115,7 @@ module.exports = {
return `${format}${str}\n${format}`;
}
return ``;
return '';
},
convertToParams: (params) => {
@ -167,7 +167,7 @@ module.exports = {
return globalId;
}
return definition.model ? `Morph` : `[Morph]`;
return definition.model ? 'Morph' : '[Morph]';
},
/**
@ -380,7 +380,7 @@ module.exports = {
// Retrieve generic service from the Content Manager plugin.
const resolvers = strapi.plugins['content-manager'].services['contentmanager'];
const initialState = { definition: ``, query: {}, resolver: { Query : {} } };
const initialState = { definition: '', query: {}, resolver: { Query : {} } };
if (_.isEmpty(models)) {
return initialState;
@ -398,7 +398,7 @@ module.exports = {
};
const globalId = model.globalId;
const _schema = _.cloneDeep(_.get(strapi.plugins, `graphql.config._schema.graphql`, {}));
const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
if (!acc.resolver[globalId]) {
acc.resolver[globalId] = {};
@ -407,15 +407,15 @@ module.exports = {
// Add timestamps attributes.
if (_.get(model, 'options.timestamps') === true) {
Object.assign(initialState, {
created_at: 'String',
updated_at: 'String'
createdAt: 'String',
updatedAt: 'String'
});
Object.assign(acc.resolver[globalId], {
created_at: (obj, options, context) => { // eslint-disable-line no-unused-vars
createdAt: (obj, options, context) => { // eslint-disable-line no-unused-vars
return obj.createdAt || obj.created_at;
},
updated_at: (obj, options, context) => { // eslint-disable-line no-unused-vars
updatedAt: (obj, options, context) => { // eslint-disable-line no-unused-vars
return obj.updatedAt || obj.updated_at;
}
});
@ -614,14 +614,14 @@ module.exports = {
const { definition, query, resolver } = this.shadowCRUD(Object.keys(strapi.plugins[plugin].models), plugin);
// We cannot put this in the merge because it's a string.
acc.definition += definition || ``;
acc.definition += definition || '';
return _.merge(acc, {
query,
resolver
});
}, this.shadowCRUD(models));
})() : {};
})() : { definition: '', query: '', resolver: '' };
// Extract custom definition, query or resolver.
const { definition, query, resolver = {} } = strapi.plugins.graphql.config._schema.graphql;
@ -659,7 +659,7 @@ module.exports = {
const typeDefs = `
${definition}
${shadowCRUD.definition}
type Query {${this.formatGQL(shadowCRUD.query, resolver.Query, null, 'query')}${query}}
type Query {${shadowCRUD.query && this.formatGQL(shadowCRUD.query, resolver.Query, null, 'query')}${query}}
${this.addCustomScalar(resolvers)}
${polymorphicDef}
`;
@ -687,7 +687,7 @@ module.exports = {
JSON: GraphQLJSON
});
return `scalar JSON`;
return 'scalar JSON';
},
/**
@ -698,9 +698,10 @@ module.exports = {
addPolymorphicUnionType: (customDefs, defs) => {
const types = graphql.parse(customDefs + defs).definitions
.filter(def => def.name.value !== 'Query')
.filter(def => def.kind === 'ObjectTypeDefinition' && def.name.value !== 'Query')
.map(def => def.name.value);
if (types.length > 0) {
return {
polymorphicDef: `union Morph = ${types.join(' | ')}`,
polymorphicResolver: {
@ -711,6 +712,12 @@ module.exports = {
}
}
};
}
return {
polymorphicDef: '',
polymorphicResolver: {}
};
},
/**

View File

@ -317,10 +317,12 @@ module.exports = {
initialize: async function (cb) {
const roles = await strapi.query('role', 'users-permissions').count();
// It's has been already initialized.
// It has already been initialized.
if (roles > 0) {
return await this.updatePermissions(async () => {
await this.removeDuplicate();
return await this.updatePermissions(cb);
cb();
});
}
// Create two first default roles.

View File

@ -124,13 +124,13 @@ module.exports = {
try {
if (this.config.uuid) {
const publicKey = fs.readFileSync(path.resolve(__dirname, 'resources', 'key.pub'));
const options = { timeout: 1000 };
const options = { timeout: 1500 };
const [usage, signedHash, required] = await Promise.all([
fetch('https://strapi.io/assets/images/usage.gif', options),
fetch('https://strapi.io/hash.txt', options),
fetch('https://strapi.io/required.txt', options)
]);
]).catch(err => {});
if (usage.status === 200 && signedHash.status === 200) {
const code = Buffer.from(await usage.text(), 'base64').toString();