Skip to content

Commit

Permalink
release: small fixes
Browse files Browse the repository at this point in the history
- fix: cached requests for wp options
- fix: php notice on failed xml feed
  • Loading branch information
preda-bogdan authored Dec 12, 2023
2 parents 8482209 + d3feb58 commit 8adf289
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 82 deletions.
3 changes: 2 additions & 1 deletion .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ node_modules
dist
artifact
.git
composer.json
composer.json
docs
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ README.md export-ignore
.github export-ignore
.eslintrc export-ignore
assets/js/src export-ignore
docs export-ignore
# Set the default behavior for all files.
* text=auto

Expand Down
30 changes: 14 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Themeisle SDK

ThemeIsle SDK used to register common features for products in the portfolio.
ThemeIsle SDK used to register common features for products in the portfolio.

Can be installed using composer:
`composer require codeinwp/themeisle-sdk`
and manually autoloading the load.php file in the composer.json file of your project:
Can be installed using composer:
`composer require codeinwp/themeisle-sdk`
and manually autoloading the load.php file in the composer.json file of your project:

```
Expand All @@ -13,20 +13,18 @@ and manually autoloading the load.php file in the composer.json file of your pr
"vendor/codeinwp/themeisle-sdk/load.php"
]
}
```



### Features

* Loads the most recent version of the library across all the products on the same wordpress instance. For instance if there is a theme which bundles v2.0.0 of the SDK and one plugin which bundles the v1.9.1, it will load on the most recent one, v2.0.0 for both products.
* If there are two products using the same version, it will load the first one that register the SDK, unless it's explicitly overwritten.
* Each functionality is bundled into modules, which are loaded based on the product type. Free/Pro, is available on wordpress or not.

- Loads the most recent version of the library across all the products on the same wordpress instance. For instance if there is a theme which bundles v2.0.0 of the SDK and one plugin which bundles the v1.9.1, it will load on the most recent one, v2.0.0 for both products.
- If there are two products using the same version, it will load the first one that register the SDK, unless it's explicitly overwritten.
- Each functionality is bundled into modules, which are loaded based on the product type. Free/Pro, is available on wordpress or not.
- Telemetry. Track the use of the feature. [Check the docs to learn more](./docs/telemetry.md).

### How to register product

* The library works out of the box by simply loading the autoloader into the plugin/theme files.
* Some modules are loaded only if the product is not available on WordPress.org ( licenser/review ). You can define if the product is available on wordpress.org by adding this file header `WordPress Available: <yes|no>` where `<yes|no>` will be replaced with the proper status.
* If the product requires is a premium one and requires a licesing mechanism, we can use `Requires License: <yes|no>` to specifically tell that the product requires license.


- The library works out of the box by simply loading the autoloader into the plugin/theme files.
- Some modules are loaded only if the product is not available on WordPress.org ( licenser/review ). You can define if the product is available on wordpress.org by adding this file header `WordPress Available: <yes|no>` where `<yes|no>` will be replaced with the proper status.
- If the product requires is a premium one and requires a licesing mechanism, we can use `Requires License: <yes|no>` to specifically tell that the product requires license.
2 changes: 1 addition & 1 deletion assets/js/build/promos/index.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('wp-api', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-hooks', 'wp-plugins'), 'version' => '5985a79052f7bcc2b5f6');
<?php return array('dependencies' => array('wp-block-editor', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-hooks', 'wp-plugins'), 'version' => 'bae1a40c3811e093a7be');
2 changes: 1 addition & 1 deletion assets/js/build/promos/index.js

Large diffs are not rendered by default.

102 changes: 46 additions & 56 deletions assets/js/src/common/useSettings.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* WordPress dependencies.
*/
import api from '@wordpress/api';

import { dispatch } from '@wordpress/data';
import {
dispatch,
useSelect
} from '@wordpress/data';

import {
useEffect,
useState
} from '@wordpress/element';

Expand All @@ -32,78 +32,68 @@ const useSettings = () => {
const [ settings, setSettings ] = useState({});
const [ status, setStatus ] = useState( 'loading' );

const getSettings = () => {
api.loadPromise.then( async() => {
try {
const settings = new api.models.Settings();
const response = await settings.fetch();
setSettings( response );
} catch ( error ) {
setStatus( 'error' );
} finally {
setStatus( 'loaded' );
}
});
};

useEffect( () => {
getSettings();
useSelect( select => {

// Bail out if settings are already loaded.
if ( Object.keys( settings ).length ) {
return;
}

const { getEntityRecord } = select( 'core' );
const request = getEntityRecord( 'root', 'site' );

if ( request ) {
setStatus( 'loaded' );
setSettings( request );
}
}, []);

const getOption = option => {
return settings?.[option];
};

const updateOption = ( option, value, success = 'Settings saved.' ) => {
setStatus( 'saving' );

const save = new api.models.Settings({ [option]: value }).save();

save.success( ( response, status ) => {
if ( 'success' === status ) {
setStatus( 'loaded' );

createNotice(
'success',
success,
{
isDismissible: true,
type: 'snackbar'
}
);
}

if ( 'error' === status ) {
const getOption = option => settings?.[option];

const updateWPOption = async (optionName, optionValue, success = 'Settings saved.') => {
const data = { [optionName]: optionValue };

try {
const response = await fetch( '/wp-json/wp/v2/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce,
},
body: JSON.stringify(data),
});

if (!response.ok) {
setStatus( 'error' );

createNotice(
'error',
'An unknown error occurred.',
'Could not save the settings.',
{
isDismissible: true,
type: 'snackbar'
}
);
}

getSettings();
});

save.error( ( response ) => {
setStatus( 'error' );

const settings = await response.json();

setStatus( 'loaded' );
createNotice(
'error',
response.responseJSON.message ? response.responseJSON.message : 'An unknown error occurred.',
'success',
success,
{
isDismissible: true,
type: 'snackbar'
}
);
});

setSettings( settings );
} catch (error) {
console.error('Error updating option:', error);
}
};

return [ getOption, updateOption, status ];
return [ getOption, updateWPOption, status ];
};

export default useSettings;
152 changes: 152 additions & 0 deletions docs/TELEMETRY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Telemetry Module

> The telemetry module help up to observe the usage of our features.
The products that allow telemetry will load a special script used for registering events and a global object with settings.

To load the telemetry module you need to add this to the project: `add_filter( 'themeisle_sdk_enable_telemetry', '__return_true' );`. This command will make the SDK to load the telemetry script into the page.

> [!NOTE]
> If multiple products are available, only the product with latest SDK will do the loading of the script.
The script will create another global object named `tiTrk` that can be used to track events. Also, you can create an alias like `oTrk = tiTrk.with('otter')` which make a small wrapper that adds `otter` as a product slug to all event created from it.

## Example of usage:

### `add` function

```javascript
window.oTrk?.add({
feature: "stripe-checkout",
featureComponent: "price-changed",
});
```
This will record that the user has changed the price into a Stripe Checkout block. This will indicate the usage of a feature.
To not create too many events, the `add` uses the object hash as the key. In the above example, the next calls of the function will not be registered since we already have it, and we are not interested in how many times the user did it. The checking it at the local scope. `tiTrk` is an event accumulator; once the limit has been reached or the user exits the page, the events from the buffer will be sent.
It will be something like this:
```javascript
// First Call
window.oTrk?.add({
feature: "stripe-checkout",
featureComponent: "price-changed",
}); // Registered.

// Second Call
window.oTrk?.add({
feature: "stripe-checkout",
featureComponent: "price-changed",
}); // Ignored.
```
Another example with `add` is this:
```javascript
window.oTrk?.add({
feature: "marketing",
featureComponent: "api-key",
groupID: attributes.id,
});
```
This will register the action at the individual level. In the example above, the `groupID` is used to track the evolution of a Gutenberg block made with Otter. You can also put the page url to track features at page level and it is evolution.
As a whole, `groupID` allows us to observe the evolution of a feature values. Like when users switch to other vendors (Mailchimp to Sendinblue).
```javascript
window.oTrk?.add({
feature: "marketing",
featureComponent: "api-key",
groupID: "form-1",
}); // Registered.

window.oTrk?.add({
feature: "marketing",
featureComponent: "api-key",
groupID: "form-2",
}); // Registered.

window.oTrk?.add({
feature: "marketing",
featureComponent: "api-key",
groupID: "form-1",
}); // Ignored.
```
_So `add` will register a unique event in the page session or events that we do not care about how many times they were accessed._
So this method is used to collect data that answer to the questions like: _Did the user used this?_
### `set` function
The set function solves the problem of component that requires user input. Things like text input, choices, etc.
When we register the event, we care about the final state of the input value. We do not care about typing process, etc.
```javascript
window.oTrk?.set(`${attributes.id}_size`, {
feature: "form-file",
featureComponent: "file-size",
featureValue: maxFileSize,
groupID: attributes.id,
});
```
In the above case, we override the event with id `${attributes.id}_size` each type the user types in the Text Component and register the value of the max file size inside the Form File Input. This will help us to see if our users use huge files with our form; if yes, then we can improve the process.
So this method is used to collect data that answer to the questions like: _What was the value used for this setting/feature?_
ℹ️ Under the hood `add` function uses the `set` function, and the key is the object hash.
### Global settings.
The SDK will load a global object named `tiTelemetry`:
```javascript
{
"products": [
{
"slug": "neve",
"trackHash": "free",
"consent": true
},
{
"slug": "otter",
"consent": false,
"trackHash": "8a224e33bb0b1c6a65df8be2960e6ef2"
}
],
"endpoint": "http://localhost:3000/bulk-tracking"
}
```
> [!IMPORTANT]
> There are some cases when products inherit the license from other products. This can be handled with the `'themeisle_sdk_telemetry_products'` hook which change the `tiTelemetry`. [Example](https://github.com/Codeinwp/otter-blocks/blob/fa0295a962f70f1bb9cb83bbbf539402c64bea49/otter-blocks.php#L77-L105).
In the above example, we have the consent to track the Neve user, which is a Free user. For Otter, we have a Pro user identified by its hash which is derived from the license using `wp_hash`. This one did not allow consent, so we will reject every event for that product if the exception is not explicit.
Example with explicit consent:
```javascript
window.oTrk?.add(
{
feature: "ai-generation",
featureComponent: "ai-toolbar",
featureValue: actionKey,
},
{ consent: true }
);
```
Since in AI Block, we are allowed to track, we can bypass the consent and register which type of action the user used in the AI Toolbar for Paragraph.
## Other
Otter implementation: https://github.com/Codeinwp/otter-blocks/pull/1919
Neve implementation: https://github.com/Codeinwp/neve/pull/4131
> [!IMPORTANT]
> The tracking on the PHP side is a part of the products and not the SDK.
10 changes: 4 additions & 6 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites name="General Unit tests ( Requires PHP 5.4) ">
<testsuite>
<testsuites>
<testsuite name="General Unit tests ( Requires PHP 5.4) ">
<directory phpVersion="5.4.0" phpVersionOperator=">=" suffix="-test.php">./tests/</directory>
<exclude>./tests/old/</exclude>
</testsuite>
</testsuites>
<testsuites name="Bail lower php versions( For PHP lower than 5.4) ">
<testsuite>
<testsuite name="Bail lower php versions( For PHP lower than 5.4) ">
<directory phpVersion="5.4.0" phpVersionOperator="lt" suffix="-test.php">./tests/old/</directory>
</testsuite>
</testsuites>
</phpunit>
</phpunit>
Loading

0 comments on commit 8adf289

Please sign in to comment.