webgriffe / syliusakeneoplugin Goto Github PK
View Code? Open in Web Editor NEWPlugin allowing to import data from Akeneo PIM to your Sylius store.
Home Page: https://webgriffe.github.io/SyliusAkeneoPlugin/
License: MIT License
Plugin allowing to import data from Akeneo PIM to your Sylius store.
Home Page: https://webgriffe.github.io/SyliusAkeneoPlugin/
License: MIT License
The Actual AttributeOptions/Importer will import attributes with configuration property populated by the 'choices' key, as follow:
array (
'choices' =>
array (
'value1' =>
array (
'it_IT' => 'Value 1',
),
'value2' =>
array (
'it_IT' => 'Value 2',
),
'value3' =>
array (
'it_IT' => 'Value 3',
),
),
)
This will throw an error when trying to store a new product with this attribute and also when trying to edit an existing product with an already defined value from these attributes. The error is the following:
Argument 1 passed to Sylius\Component\Attribute\Model\AttributeValue::setJson() must be of the type array or null, string given, called in AttributeValue.php on line 105
This is due to the attempt to pass, for example, the 'value1' value as an array to the setJson method.
Using the complete configuration generated by Sylius which includes the keys 'multiple', 'min', and 'max' apparently will solve this problem.
array (
'choices' =>
array (
'value1' =>
array (
'it_IT' => 'Value 1',
),
'value2' =>
array (
'it_IT' => 'Value 2',
),
'value3' =>
array (
'it_IT' => 'Value 3',
),
),
'multiple' => false,
'min' => NULL,
'max' => NULL,
)
To complete this configuration the importer needs to know these configurations by querying the Akeneo WS.
At the moment there's no way to find the source of a sylius.product.*
event.
In my use case I'd like to be able to know that the Akeneo importer dispatched it, so I can handle only those (especially ensuring some default values are set on import, which might get overridden by the user).
If you think this would be a good addition, I could provide a PR.
When product associations are initially created in Akeneo, they are correctly transferred to Sylius.
If a product association is subsequently removed in Akeneo, it still remains in Sylius.
IMHO it should go ahead without throwing any error. What do you think?
The MAX_DEDUPLICATION_INCREMENT
constant in the ImmutableSlugValueHandler should be injected into the class. So, for the stores where more than 100 products have the same name, could increment this constant without override the entire service.
Currently enqueuing configurable product from admin UI doesn't work properly. It enqueues the parent product code but it should enqueue all variants codes.
Currently if someone wants to add value handlers to the Webgriffe\SyliusAkeneoPlugin\PriorityValueHandlersResolver
has to redefine the whole webgriffe_sylius_akeneo.product.value_handlers_resolver
and the friendly configuration at webgriffe_sylius_akeneo.value_handlers
is not used anymore.
It should be easier to add more value handlers.
One solution could be to enrich the dependency injection extension to automatically inject value handlers inside the webgriffe_sylius_akeneo.product.value_handlers_resolver
with a specific value handler tag.
Method \Webgriffe\SyliusAkeneoPlugin\ApiClient::authenticatedRequest makes login only if token is not set. But the Akeneo API access token has a lifetime (3600 seconds) and when it is expired the request responds with a 401 error.
If you have hundreds of products on Akeneo, and you run all importers for all products, this is likely to happen. In this case the importer stops with this error:
The ApiClient should handle this case and login again, or even better, according to oauth protocol, il should refresh the access token using the refresh token.
Here:
SyliusAkeneoPlugin/src/Product/Importer.php
Lines 221 to 235 in 36d097d
When handling taxons we only add resolved taxons to the product without removing those that were previously associated.
IMHO it's a bug.
The ProductOptionValueHandler is unable to import a product option matrical value. You can replicate the error by launching the application test and trying to import the product variant TVSAM32
by the Akeneo demo, for example.
While importing products without a family set on Akeneo the following error occurs:
There has been an error importing Product entity with identifier XXX. The error was: Argument 2 passed to Webgriffe\SyliusAkeneoPlugin\Product\Importer::addMissingAttributes() must be of the type string, null given
Currently the enqueue command retrieves, from registered importers, entities modified since a given date and put them into the import queue.
This kind of architecture doesn't allow us to easily tell if a product has been deleted from Akeneo or if it's code/identifier has been changed.
It would be optimal to have a button on the product pages of the backoffice to add a product to the import queue from Akeneo.
Currently if on Akeneo there's a Product Model whose code is the same of a different Product which is not a variant of the Product Model itself, the importer puts everything inside the same Product on Sylius.
For example if on Akeneo you have:
A
A.1
(variant of A
)A.2
(variant of A
)A
On Sylius you'll have:
A
A
(variant of A
)A.1
(variant of A
)A.2
(variant of A
)Instead, expected result on Sylius should be:
A
A.1
(variant of A
)A.2
(variant of A
)?
A
(variant of ?
)The issue here is that Akeneo have Product Models only for configurable products but Sylius always have Product (which is the Product Model on Akeneo) and Product Variant (which is Product on Akeneo). When the product importer imports a simple Product from Akeneo it creates a the Product and Product Variant on Sylius with the same code. So there would be a code collision between Product on Sylius if on Akeneo a Product Model has the same code of a different Product.
How should we handle this?
Currently this plugin completely ignores the Akeneo product status (enabled/disabled).
Of course, this status should be mapped with the analog Sylius's product status. The problem is that in Sylius the product status is at product level and not (also) at product variant level; instead in Akeneo the status is only at product level and not at product model level.
So, in Akeneo, you could have only one disabled product variant for a parent product which have several other variants enabled. This situation couldn't be mapped currently on Sylius.
ImageValueHandler downloads product images from Akeneo, using method \Webgriffe\SyliusAkeneoPlugin\ApiClient::downloadFile. This method downloads the image files to the system temp dir, but the ImageValueHandler doesn't remove images from temp dir after using them, so that image files pile up and soon or later they end up filling up the disk.
All commands of this plugin should be "lockable" to not overlap. We could use the Symfony's lock component.
The URL resolver's urlencode() function to get product information will also encode the slash (/) causing a 404. The Akeneo APIs expect in fact the code with slash not encoded.
This plugin should provide an attribute options importer out of the box.
Indeed if there are simple select or multi select attributes on Akeneo and those attributes are mapped to the corresponding select attributes on Sylius (with the AttributeValueHandler
), an attribute options importer is a must-have to be able to synchronize attributes options.
After #60 empty attributes values are properly passed to all value handlers during product import. So now, for attributes that are empty on Akeneo, the AttributeValueHandler always sets empty product attributes on Sylius. Instead it should remove them from products.
Currently the "direct" parent of a product is created / retrieved when importing products with more than one variant.
Sylius doesn't support the two levels of Akeneo, but it does support multiple options, generating a variant of the combinations.
On import the options are resolved in ProductOptionsResolver
by only considering the first tier.
Likewise, in Product\Importer
and ProductAssociations\Importer
only the "direct" parent of a variant is considered (1st axis variant if 2nd axis) instead of the root product.
We've changed the importers / option resolver to check the parent of a parent and also to import all options locally. This works well because the 2nd tier variantCode matches the Akeneo code anyway.
What's your take on this?
Are you planning on supporting the second Akeneo variant axis level?
it is impossible to mass delete the elements of the queue starting from the current date. By entering 'days' 0 as the argument, the resulting list of items to be deleted is empty.
The problem is probably with this code line:
On busy sites a failure might actually be retryable (deadlocks come to mind) and could already work a few seconds later.
The current implementation will try forever (even for items that are likely to never succeed), which on the other hand makes sense considering that the enqueue command with since-file cannot check for "changed after date X or not yet imported".
In my opinion a retry stategy (something like Symfony's RetryStrategyInterface
) might still come in handy.
What do you think?
It seems that if someone modify a product model on Akeneo (without changing any variant-specific attribute, only model ones) the related variant products are not enqueued byt the Product importer.
Maybe is because in such situation Akeneo APIs do not return variants (products) as modified since the given date. Only parent product (product model). If this is the case, here:
SyliusAkeneoPlugin/src/Product/Importer.php
Lines 166 to 180 in 9782b54
We should also enqueue products of those product models modified since the given date.
As stated in #5, currently the only way for an importer to enqueue identifiers is to implement the \Webgriffe\SyliusAkeneoPlugin\ImporterInterface::getIdentifiersModifiedSince()
which assumes that you can get from the Akeneo API only those resources modified since a given date.
Unfortunately this is not always true. On the contrary this is true only for products and product models, all other resources cannot be fetched by "updated at" date/time.
So we need a different enqueuing logic.
Based on the fact that all these resources are "catalog structure", they should not change very often. So one solution could be to enqueue all identifiers for this resources.
Doing so will require a change in the webgriffe:akeneo:enqueue
CLI command to allow users to have a different cron schedule potentially for every resource/importer (or a completely separate command).
Sometimes there are failed items in queue that you know that will never be imported. In such cases it could be useful to remove them manually from the queue.
It would be very useful an admin grid of the queue items especially to examine error messages of not imported items.
The TranslatablePropertyValueHandler
currently sets property value on both ProductVariantTranslation
and ProductTranslation
.
Given the fact that there could be more than one value handler for each Akeneo value, it should be split into two separate value handlers: ProductTranslationPropertyValueHandler
and ProductVariantTranslationPropertyValueHandler
.
Since commit bbaecd1 import fails with:
[Doctrine\DBAL\Exception\NotNullConstraintViolationException]
An exception occurred while executing 'INSERT INTO sylius_product_translation (name, slug, description, meta_keywords, meta_description, short_description, locale, translatable_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' with params [null, null, null, null, null, null, "en_US", 3755]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
Reverting bbaecd1 "fixes" the problem.
Any ideas what's going on?
The same that has been done in #40 for the TranslatablePropertyValueHandler
should be done also for the ProductOptionValueHandler
.
If, for whatever reason, an import from a single Akeneo channel should be split for different Sylius channels (e.g. via attribute channel
) there's currently no way to have different products land in different channels.
What's your opinion on this?
Shouldn't the ChannelsResolverInterface
rather be a single ChannelResolverInterface
operating per product instead of on all?
I might be able to draft a PR next week, if you agree.
(This is probably related to #2.)
What happens if:
The sync will update automatically taxon association with the new data?
Hey there!
I am dealing with the following situation: In Akeneo I have a product model with a variant of 1 axis. The product model has a common attribute named Image
, and the axis has another attribute named ImageVariant
.
When I import them to Sylius, the common Image
gets imported n
times as existent variants.
Shouldn't it get imported only once, as it is a common attribute?
This is how I configured the import of those attributes in webgriffe_sylius_akeneo_plugin.yaml
image:
type: 'image'
options:
akeneo_attribute_code: 'image'
sylius_image_type: 'image'
image_variant:
type: 'image'
options:
akeneo_attribute_code: 'variation_image'
sylius_image_type: 'variation_image'`
Have I mistaken something in my configuration? Or is it a matter of enhancement of the plugin. Thanks in advance.
Best regards.
The same for #59 but for files handled by the FileAttributeValueHandler
.
While I'm not especially fond of special characters in an SKU, if customer requires it they shouldn't break API calls nevertheless.
Currently, an SKU 123#456
will fail:
Client error: `GET http://pim/api/rest/v1/products/123#456` resulted in a `404 Not Found` response:
{"code":404,"message":"Product \"123\" does not exist or you do not have permission to access it."}
Solution might be to simply urlencode()
each code or, even better, use the official client.
Is there a plan to switch to it or should I provide a PR for the current implementation?
Currently the responsibility to find which are the identifiers to enqueue is given to the importer which have to implement the method \Webgriffe\SyliusAkeneoPlugin\ImporterInterface::getIdentifiersModifiedSince()
.
So if one wants to customize that logic (for example adding some kind of filter) he needs to replace the entire importer.
So I suggest to isolate the responsibility of finding identifiers that have been modified since a given date to a dedicated interface.
Thanks to #32 we could now import metrical attributes on Akeneo to Sylius attributes.
Anyway, at the moment the metrical attributes could not be imported as Sylius generic properties.
For example, if I have a metrical attribute 'Weight' on Akeneo I could import it as a weight attribute, but this would be limiting for the reasons that that attribute would be of type text containing the unit measurement and all the logic of shipping rules must be changed to use that attribute instead of the native property of the product. The same for the length, height, and width properties.
A solution could be to edit the GenericPropertyValueHandler to handle the metrical data type, but it could be risky because of the mixed type returned by the getValue method (it must remain compatible with other properties).
So, the solution that seems to be better is adding a MetricPropertyValueHandler that accepts 'attribute_code_on_akeneo' and 'property_path_on_sylius' to map the metric value of the attribute from Akeneo on the property path specified on Sylius. However, this assumes that the value will be imported in the unit of measurement used in Akeneo.
What do you think @webgriffe/wg-devs, @aleho?
When images are created in Akeneo and transferred via the plugin, they can no longer be removed.
Despite deletion in Akeneo this is still present after an import in Sylius
Given on Akeneo there is a Product Model with one or more Product Variants and on Sylius those Product Variant have been already imported as simple products before. Trying to import the new Product Model, to have the corresponding configurable product on Sylius where already existent variants are moved under the new parent product, the following error occurs:
A new entity was found through the relationship 'App\Entity\Product\ProductVariant#product' that was not configured to cascade persist operations for entity: . To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"})
The culprit seems to be in this line:
Because the add
call will flush the entity manager which tries to flush also the Product Variant with the new not-managed associated Product.
That line seems to be unnecessary. Indeed the final Product flush should also persist the new/updated Product Option Values.
Hello, I upgraded my version of Sylius to 1.9 and when I tried to install the SyliusAkeneoPlugin by composer require webgriffe/SyliusAkeneoPlugin
, the following Exception is thrown:
Executing script cache:clear [KO]
[KO]
Script cache:clear returned with error code 255
!! PHP Fatal error: Access level to Webgriffe\SyliusAkeneoPlugin\Command\QueueCleanupCommand::SUCCESS must be public (as in class Symfony\Component\Console\Command\Command) in /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/webgriffe/sylius-akeneo-plugin/src/Command/QueueCleanupCommand.php on line 101
!! PHP Stack trace:
!! PHP 1. {main}() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/bin/console:0
!! PHP 2. Symfony\Bundle\FrameworkBundle\Console\Application->run() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/bin/console:38
!! PHP 3. Symfony\Bundle\FrameworkBundle\Console\Application->doRun() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/console/Application.php:166
!! PHP 4. Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/framework-bundle/Console/Application.php:74
!! PHP 5. App\Kernel->boot() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/framework-bundle/Console/Application.php:168
!! PHP 6. App\Kernel->preBoot() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/http-kernel/Kernel.php:121
!! PHP 7. App\Kernel->initializeContainer() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/http-kernel/Kernel.php:780
!! PHP 8. Symfony\Component\DependencyInjection\ContainerBuilder->compile() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/http-kernel/Kernel.php:541
!! PHP 9. Symfony\Component\DependencyInjection\Compiler\Compiler->compile() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/ContainerBuilder.php:736
!! PHP 10. Symfony\Component\DependencyInjection\Compiler\AutowirePass->process() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/Compiler.php:91
!! PHP 11. Symfony\Component\DependencyInjection\Compiler\AutowirePass->process() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:54
!! PHP 12. Symfony\Component\DependencyInjection\Compiler\AutowirePass->processValue() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php:46
!! PHP 13. Symfony\Component\DependencyInjection\Compiler\AutowirePass->doProcessValue() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:72
!! PHP 14. Symfony\Component\DependencyInjection\Compiler\AutowirePass->processValue() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:103
!! PHP 15. Symfony\Component\DependencyInjection\Compiler\AutowirePass->processValue() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php:81
!! PHP 16. Symfony\Component\DependencyInjection\Compiler\AutowirePass->doProcessValue() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:72
!! PHP 17. Symfony\Component\DependencyInjection\Compiler\AutowirePass->autowireCalls() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:126
!! PHP 18. Symfony\Component\DependencyInjection\Compiler\AutowirePass->autowireMethod() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:171
!! PHP 19. Symfony\Component\DependencyInjection\Compiler\AutowirePass->Symfony\Component\DependencyInjection\Compiler\{closure:/Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:229-241}() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:261
!! PHP 20. Symfony\Component\DependencyInjection\Exception\AutowiringFailedException->__construct() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:236
!! PHP 21. Symfony\Component\DependencyInjection\Compiler\AutowirePass->Symfony\Component\DependencyInjection\Compiler\{closure:/Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:387-389}() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Exception/AutowiringFailedException.php:29
!! PHP 22. Symfony\Component\DependencyInjection\Compiler\AutowirePass->createTypeNotFoundMessage() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:388
!! PHP 23. Symfony\Component\DependencyInjection\Compiler\AutowirePass->createTypeAlternatives() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:407
!! PHP 24. Symfony\Component\DependencyInjection\Compiler\AutowirePass->populateAvailableTypes() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:433
!! PHP 25. Symfony\Component\DependencyInjection\Compiler\AutowirePass->populateAvailableType() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:323
!! PHP 26. Symfony\Component\DependencyInjection\ContainerBuilder->getReflectionClass() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:337
!! PHP 27. Symfony\Component\Config\Resource\ClassExistenceResource->isFresh() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/dependency-injection/ContainerBuilder.php:348
!! PHP 28. class_exists() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/config/Resource/ClassExistenceResource.php:84
!! PHP 29. spl_autoload_call() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/config/Resource/ClassExistenceResource.php:84
!! PHP 30. Composer\Autoload\ClassLoader->loadClass() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/symfony/config/Resource/ClassExistenceResource.php:84
!! PHP 31. Composer\Autoload\includeFile() /Users/jorge/Coding/Bitbucket/babyq-shop-sylius/vendor/composer/ClassLoader.php:322
Is it absolutely not possible to install it with that version of Sylius? Or is there any dependencies conflict?
Thanks in advance
In #43 we kept duplicated resources definition and some services aliases for backwards compatibility. We can remove those duplications in the next major release.
Currently the Attribute Options Importer does not sort options by their sort order. So on Sylius product attributes options do not have the same sort order they have on Akeneo.
At the moment there isn't any logic about Akeneo channels. The plugin assumes that there's only one channel on Akeneo. If there was multiple channels some value handlers could throw exceptions.
Currently there are some value handlers assuming a specific value array. For example all the TVs on the Akeneo demo cannot be imported with this plugin because its variant axe is the attribute display_diagonale
which is an Akeneo pim_catalog_metric
attribute. In this case the data
key in the value array is like {"amount":"-12.78","unit":"KILOWATT"}
but the ProductOptionValueHandler
assume that this data is a plain string.
The full list of possible data types could be found here: https://api.akeneo.com/documentation/resources.html#product
It could be useful to have CLI command that removes old imported queue items.
If one tries to install the plugin using composer require as indicated in the documentation with the Symfony Flex enabled, then the following error is generated:
The service "webgriffe_sylius_akeneo.command.queue_cleanup" has a dependency on a non-existent service "webgriffe_sylius_akeneo.manager.queue_item".
As a workaround, one can manually execute steps 2 and 3 of the installation guide, which should solve the problem. But still it would be nice to have the installation succeed without having to do this.
There is a BUG in ImmutableSlugValueHandler on generating slug for a product with the same name as another.
For example:
If I have three products named "T-shirt banana", the importer will generate a "t-shirt-banana" slug for the first one, "t-shirt-banana-1" for the second one, and "t-shirt-banana-1-2" for the third instead of "t-shirt-banana-2".
The same that has been done in #40 for the TranslatablePropertyValueHandler
should be done also for the AttributeValueHandler
.
Has stated here, currently the product status is set to disabled only if the Akeneo product is disabled and doesn't belong to a parent product model.
The problem is that in Sylius the product status is at product level and not (also) at product variant level; instead in Akeneo the status is only at product level and not at product model level.
So, in Akeneo, you could have only one disabled product variant for a parent product which have several other variants enabled. This situation couldn't be mapped currently on Sylius.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.