commit 1f4b8d75e7b64b785eb50af5994b60713412f318
parent e39c5f87f9ad44c7b7d4ffb521178f492761320d
Author: Christian <github@christianwolf.email>
Date: Sat, 7 Nov 2020 11:41:47 +0100
Merge pull request #1150 from sibbi77/octopart_v4
Octopart v4 API
Diffstat:
10 files changed, 432 insertions(+), 179 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## Unreleased
+
+Bugfixes and other features:
+ * Updated to the most recent Octopart API version 4 [#1150](https://github.com/partkeepr/PartKeepr/pull/1150)
+
## PartKeepr 1.4.0
New Features:
diff --git a/app/config/parameters.php.dist b/app/config/parameters.php.dist
@@ -259,12 +259,17 @@ $container->setParameter('partkeepr.filesystem.data_directory', '%kernel.root_di
$container->setParameter('partkeepr.cronjob.check', true);
/**
- * Specifies which API key PartKeepr should use to talk to OctoPart. You can get an API key by registering at
- * https://octopart.com/api/home and then registering an application.
+ * Specifies which v4 API key PartKeepr should use to talk to OctoPart. You can get an API key by registering at
+ * https://octopart.com/api and then registering an application.
*/
$container->setParameter('partkeepr.octopart.apikey', '');
/**
+ * The number of returned parts from API calls is limited. Try to keep this value low
+ */
+$container->setParameter('partkeepr.octopart.limit', '3');
+
+/**
* Specifies which URL contains the patreon status. If you do not wish to display the patreon status,
* set this parameter to false. Please note that we rely on your Patreon pledges to ensure further
* development of PartKeepr.
diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/OctoPart/DataApplicator.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/OctoPart/DataApplicator.js
@@ -8,9 +8,8 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
{
this.setImport("parameters", true);
this.setImport("distributors", true);
- this.setImport("datasheets", true);
+ this.setImport("bestDatasheet", true);
this.setImport("cadModels", true);
- this.setImport("complianceDocuments", true);
this.setImport("referenceDesigns", true);
this.setImport("images", true);
@@ -24,9 +23,9 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
this.part = part;
},
/**
- * Loads the data via the PartKeepr API from OctoPart.
+ * Loads the data via the PartKeepr API from Octopart.
*
- * @param {String} id The OctoPart UID to load
+ * @param {String} id The Octopart UID to load
*/
loadData: function (id)
{
@@ -37,7 +36,7 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
});
},
/**
- * Called after the OctoPart Part data has been loaded.
+ * Called after the Octopart Part data has been loaded.
*
* @param {Object} response The response data
*/
@@ -74,18 +73,22 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (this.import.parameters)
{
- for (i in this.data.specs)
+ for (i in this.data["specs"])
{
- if (this.data.specs[i].metadata.unit !== null)
+ var q_value, q_unit, q_siPrefix;
+ [q_value,q_unit] = this.parseQuantity( this.data.specs[i].display_value );
+ [q_value,q_unit2,q_siPrefix] = this.SIUnitPrefix(q_value, q_unit);
+ if (q_unit2 === null && q_unit)
{
+ // there is a unit (q_unit), but we do not know about it or the prefix of the unit is disabled
unit = PartKeepr.getApplication().getUnitStore().findRecord("symbol",
- this.data.specs[i].metadata.unit.symbol, 0, false, true, true);
+ q_unit, 0, false, true, true);
if (unit === null)
{
- this.displayWaitWindow(i18n("Creating Unit…"), this.data.specs[i].metadata.unit.name);
+ this.displayWaitWindow(i18n("Creating Unit…"), q_unit);
unit = Ext.create("PartKeepr.UnitBundle.Entity.Unit");
- unit.set("name", this.data.specs[i].metadata.unit.name);
- unit.set("symbol", this.data.specs[i].metadata.unit.symbol);
+ unit.set("name", q_unit); // v4 API does not have that anymore
+ unit.set("symbol", q_unit);
unit.save({
success: function ()
{
@@ -105,17 +108,17 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (this.import.distributors)
{
- for (i in this.data.offers)
+ for (i in this.data['sellers'])
{
distributor = PartKeepr.getApplication().getDistributorStore().findRecord("name",
- this.data.offers[i].seller.name, 0, false, true, true);
+ this.data.sellers[i].company.name, 0, false, true, true);
if (distributor === null)
{
- this.displayWaitWindow(i18n("Creating Distributor…"), this.data.offers[i].seller.name);
+ this.displayWaitWindow(i18n("Creating Distributor…"), this.data.sellers[i].company.name);
distributor = Ext.create("PartKeepr.DistributorBundle.Entity.Distributor");
- distributor.set("name", this.data.offers[i].seller.name);
- distributor.set("website", this.data.offers[i].seller.homepage_url);
+ distributor.set("name", this.data.sellers[i].company.name);
+ distributor.set("url", this.data.sellers[i].company.homepage_url);
distributor.save({
success: function ()
{
@@ -132,11 +135,12 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
}
}
- if (this.import.datasheets)
+ if (this.import.bestDatasheet)
{
- if (this.data.datasheets.length > 0)
+ if (this.data['best_datasheet'])
{
- file = this.data.datasheets.shift();
+ file = this.data.best_datasheet;
+ delete this.data.best_datasheet;
this.displayWaitWindow(i18n("Uploading datasheet…"), file.url);
if (!this.checkIfAttachmentFilenameExists(file.url))
@@ -154,32 +158,14 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (this.import.cadModels)
{
- if (this.data.cad_models.length > 0)
+ if (this.data['cad'])
{
- file = this.data.cad_models.shift();
- this.displayWaitWindow(i18n("Uploading CAD Model…"), file.url);
- if (!this.checkIfAttachmentFilenameExists(file.url))
+ file = this.data.cad;
+ delete this.data.cad;
+ this.displayWaitWindow(i18n("Uploading CAD Model…"), file.add_to_library_url);
+ if (!this.checkIfAttachmentFilenameExists(file.add_to_library_url))
{
- PartKeepr.getApplication().uploadFileFromURL(file.url, i18n("CAD Model"), this.onFileUploaded,
- this);
- } else
- {
- this.applyData();
- }
- return false;
- }
- }
-
- if (this.import.complianceDocuments)
- {
- if (this.data.compliance_documents.length > 0)
- {
- file = this.data.compliance_documents.shift();
- this.displayWaitWindow(i18n("Uploading Compliance Document…"), file.url);
- if (!this.checkIfAttachmentFilenameExists(file.url))
- {
- PartKeepr.getApplication().uploadFileFromURL(file.url, i18n("Compliance Document"),
- this.onFileUploaded,
+ PartKeepr.getApplication().uploadFileFromURL(file.add_to_library_url, i18n("CAD Model"), this.onFileUploaded,
this);
} else
{
@@ -210,42 +196,19 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (this.import.images)
{
- if (this.data.imagesets.length > 0)
+ if (this.data['best_image'])
{
- file = this.data.imagesets.shift();
- image = null;
-
- if (file.swatch_image !== null)
- {
- image = file.swatch_image;
- }
+ image = this.data.best_image;
+ delete this.data.best_image;
- if (file.small_image !== null)
+ this.displayWaitWindow(i18n("Uploading Image…"), image.url);
+ if (!this.checkIfAttachmentFilenameExists(image.url))
{
- image = file.small_image;
- }
-
- if (file.medium_image !== null)
- {
- image = file.medium_image;
- }
-
- if (file.large_image !== null)
- {
- image = file.large_image;
- }
-
- if (image !== null)
+ PartKeepr.getApplication().uploadFileFromURL(image.url, i18n("Image"), this.onFileUploaded,
+ this);
+ } else
{
- this.displayWaitWindow(i18n("Uploading Image…"), image.url);
- if (!this.checkIfAttachmentFilenameExists(image.url))
- {
- PartKeepr.getApplication().uploadFileFromURL(image.url, i18n("Image"), this.onFileUploaded,
- this);
- } else
- {
- this.applyData();
- }
+ this.applyData();
}
return false;
@@ -346,26 +309,26 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (this.import.distributors)
{
- for (i = 0; i < this.data.offers.length; i++)
+ for (i in this.data['sellers'])
{
distributor = PartKeepr.getApplication().getDistributorStore().findRecord("name",
- this.data.offers[i].seller.name, 0, false, true, true);
+ this.data.sellers[i].company.name, 0, false, true, true);
if (distributor === null)
{
// @todo put out error message
continue;
}
- for (currency in this.data.offers[i].prices)
+ for (o in this.data.sellers[i]['offers'])
{
- for (j = 0; j < this.data.offers[i].prices[currency].length; j++)
+ for (p in this.data.sellers[i].offers[o].prices)
{
partDistributor = Ext.create("PartKeepr.PartBundle.Entity.PartDistributor");
partDistributor.setDistributor(distributor);
- partDistributor.set("sku", this.data.offers[i].sku);
- partDistributor.set("packagingUnit", this.data.offers[i].prices[currency][j][0]);
- partDistributor.set("currency", currency);
- partDistributor.set("price", this.data.offers[i].prices[currency][j][1]);
+ partDistributor.set("sku", this.data.sellers[i].offers[o].sku);
+ partDistributor.set("packagingUnit", this.data.sellers[i].offers[o].prices[p].quantity);
+ partDistributor.set("currency", this.data.sellers[i].offers[o].prices[p].currency);
+ partDistributor.set("price", this.data.sellers[i].offers[o].prices[p].price);
found = null;
@@ -382,7 +345,7 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (found !== null)
{
- found.set("price", this.data.offers[i].prices[currency][j][1]);
+ found.set("price", this.data.sellers[i].offers[o].prices[p].price);
} else
{
this.part.distributors().add(partDistributor);
@@ -394,54 +357,47 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
if (this.import.parameters)
{
- for (i in this.data.specs)
+ for (i in this.data["specs"])
{
spec = Ext.create("PartKeepr.PartBundle.Entity.PartParameter");
- spec.set("name", this.data.specs[i].metadata.name);
+ spec.set("name", this.data.specs[i].attribute.name);
- if (this.data.specs[i].metadata.unit !== null)
- {
- unit = PartKeepr.getApplication().getUnitStore().findRecord("symbol",
- this.data.specs[i].metadata.unit.symbol, 0, false, true, true);
+ var q_value, q_unit;
+ [q_value,q_unit] = this.parseQuantity( this.data.specs[i].display_value );
- spec.setUnit(unit);
+ // some fields need to be treated as strings
+ if (this.data.specs[i].attribute.name == "Case/Package" ||
+ this.data.specs[i].attribute.name == "Case Code (Imperial)" ||
+ this.data.specs[i].attribute.name == "Case Code (Metric)") {
+ q_value = null; // force string interpretation
+ q_unit = null; // force string interpretation
}
-
- switch (this.data.specs[i].metadata.datatype)
+
+ if (q_value != null && q_unit != null)
{
- case "string":
- spec.set("valueType", "string");
- spec.set("stringValue", this.data.specs[i].value[0]);
- break;
- case "decimal":
- case "integer":
- spec.set("valueType", "numeric");
-
- if (this.data.specs[i].min_value !== null)
- {
- value = parseFloat(this.data.specs[i].min_value);
- siPrefix = this.findSiPrefixForValueAndUnit(value, unit);
- spec.set("minValue", this.applySiPrefix(value, siPrefix));
- spec.setMinSiPrefix(siPrefix);
- }
-
- if (this.data.specs[i].max_value !== null)
- {
- value = parseFloat(this.data.specs[i].max_value);
- siPrefix = this.findSiPrefixForValueAndUnit(value, unit);
- spec.set("maxValue", this.applySiPrefix(value, siPrefix));
- spec.setMaxSiPrefix(siPrefix);
- }
-
- if (this.data.specs[i].value.length === 1)
- {
- value = parseFloat(this.data.specs[i].value[0]);
- siPrefix = this.findSiPrefixForValueAndUnit(value, unit);
- spec.set("value", this.applySiPrefix(value, siPrefix));
- spec.setSiPrefix(siPrefix);
- }
-
- break;
+ [value,unit,siPrefix] = this.SIUnitPrefix(q_value, q_unit);
+ if (value && unit && siPrefix) {
+ spec.setUnit(unit);
+ spec.set("value", value);
+ spec.setSiPrefix(siPrefix);
+ } else {
+ unit = PartKeepr.getApplication().getUnitStore().findRecord("symbol",
+ q_unit, 0, false, true, true);
+ spec.setUnit(unit);
+ siPrefix = this.findSiPrefixForValueAndUnit(q_value, unit);
+ spec.set("value", this.applySiPrefix(q_value, siPrefix));
+ spec.setSiPrefix(siPrefix);
+ }
+ spec.set("valueType", "numeric");
+ }
+ else if (q_value != null) {
+ spec.set("valueType", "numeric");
+ spec.set("value", q_value);
+ }
+ else
+ {
+ spec.set("valueType", "string");
+ spec.set("stringValue", this.data.specs[i].display_value);
}
found = null;
@@ -495,5 +451,66 @@ Ext.define("PartKeepr.Components.OctoPart.DataApplicator", {
}
return siPrefix;
+ },
+ parseQuantity: function( quantity )
+ {
+ try {
+ quantity = quantity.trim();
+ const regex = /[^\d+-.]/g;
+ var idx = quantity.search(regex);
+ if (idx == -1) {
+ // no unit, but maybe value only
+ var value = parseFloat(quantity);
+ if (!isNaN(value)) {
+ return [value,null];
+ }
+ } else {
+ var value = parseFloat(quantity.slice(0,idx));
+ if (isNaN(value))
+ return [null,null];
+ var unit = quantity.slice(idx).trim();
+ return [value,unit];
+ }
+ }
+ finally {}
+ return [null,null];
+ },
+ SIUnitPrefix: function( q_value, q_unit )
+ {
+ // the new Octopart API returns quantities as display strings: e.g. "12 mm"
+ // try to recognize SI-unit and SI-prefix
+
+ // check if the unit as a whole is already known
+ var unit = PartKeepr.getApplication().getUnitStore().findRecord("symbol", q_unit, 0, false, true, true);
+ if (unit) {
+ var siPrefix = PartKeepr.getApplication().getSiPrefixStore().findRecord("exponent", 0, 0, false, false, true);
+ return [q_value, unit, siPrefix];
+ }
+
+ // assume the first character is an SI-prefix
+ if (q_unit && q_unit.length >= 2) {
+ unit = PartKeepr.getApplication().getUnitStore().findRecord("symbol", q_unit.substring(1), 0, false, true, true);
+ if (unit) {
+ // now check that the first character is a valid SI-prefix
+ console.log(unit);
+ var siPrefix;
+ for (var i=0; i<unit.prefixes().getData().length; i++) {
+ var temp = unit.prefixes().getData().getAt(i);
+ var prefixChar = temp.get("symbol");
+ if (prefixChar == "μ")
+ prefixChar = "µ"; // convert upper case µ to lower case µ
+ if (q_unit[0] == prefixChar) {
+ siPrefix = temp;
+ break;
+ }
+ }
+ if (siPrefix) {
+ return [q_value, unit, siPrefix];
+ }
+ }
+ }
+
+ // no matching unit found
+ return [null, null, null];
}
});
diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/OctoPart/SearchPanel.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/OctoPart/SearchPanel.js
@@ -25,7 +25,8 @@ Ext.define("PartKeepr.Components.OctoPart.SearchPanel", {
reader: {
type: 'json',
totalProperty: 'hits',
- rootProperty: 'results'
+ rootProperty: 'results',
+ transform: this.checkForErrors
}
},
autoLoad: false
@@ -97,9 +98,9 @@ Ext.define("PartKeepr.Components.OctoPart.SearchPanel", {
boxLabel: i18n("Parameters")
}, {
xtype: 'checkbox',
- itemId: "importDatasheets",
- checked: PartKeepr.getApplication().getUserPreference("partkeepr.octopart.importDatasheets", true),
- boxLabel: i18n("Datasheets")
+ itemId: "importBestDatasheet",
+ checked: PartKeepr.getApplication().getUserPreference("partkeepr.octopart.importBestDatasheet", true),
+ boxLabel: i18n("Best Datasheet")
}, {
xtype: 'checkbox',
itemId: "importCADModels",
@@ -107,11 +108,6 @@ Ext.define("PartKeepr.Components.OctoPart.SearchPanel", {
boxLabel: i18n("CAD Models")
}, {
xtype: 'checkbox',
- itemId: "importComplianceDocuments",
- checked: PartKeepr.getApplication().getUserPreference("partkeepr.octopart.importComplianceDocuments", true),
- boxLabel: i18n("Compliance Documents")
- }, {
- xtype: 'checkbox',
itemId: "importReferenceDesigns",
checked: PartKeepr.getApplication().getUserPreference("partkeepr.octopart.importReferenceDesigns", true),
boxLabel: i18n("Reference Designs")
@@ -158,6 +154,8 @@ Ext.define("PartKeepr.Components.OctoPart.SearchPanel", {
this.onSelectChange,
this);
+ this.store.on("load", this.checkForApiError, this);
+
this.callParent(arguments);
},
@@ -186,9 +184,8 @@ Ext.define("PartKeepr.Components.OctoPart.SearchPanel", {
j.setImport("distributors", this.down("#importDistributors").getValue());
j.setImport("parameters", this.down("#importParameters").getValue());
- j.setImport("datasheets", this.down("#importDatasheets").getValue());
+ j.setImport("bestDatasheet", this.down("#importBestDatasheet").getValue());
j.setImport("cadModels", this.down("#importCADModels").getValue());
- j.setImport("complianceDocuments", this.down("#importComplianceDocuments").getValue());
j.setImport("referenceDesigns", this.down("#importReferenceDesigns").getValue());
j.setImport("images", this.down("#importImages").getValue());
@@ -206,5 +203,19 @@ Ext.define("PartKeepr.Components.OctoPart.SearchPanel", {
);
this.store.load();
this.searchBar.setValue(query);
+ },
+ checkForErrors: function (data)
+ {
+ if (data.results.length == 0 && data.errors.length > 0) {
+ Ext.Msg.alert(i18n("Octopart Error"), data.errors.map(e => e.message).join());
+ }
+
+ return data;
+ },
+ checkForApiError: function (store, records, successful, eOpts )
+ {
+ if (!successful) {
+ Ext.Msg.alert(i18n("Octopart Error"), "PartKeepr cannot access the Octopart API");
+ }
}
});
diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/OctoPart/SearchWindow.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/OctoPart/SearchWindow.js
@@ -1,6 +1,6 @@
Ext.define("PartKeepr.Components.OctoPart.SearchWindow", {
extend: "Ext.window.Window",
- title: i18n("OctoPart Search"),
+ title: i18n("Octopart Search"),
iconCls: "partkeepr-icon octopart",
width: 750,
height: 300,
diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Editor/PartEditorWindow.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Editor/PartEditorWindow.js
@@ -62,7 +62,7 @@ Ext.define('PartKeepr.PartEditorWindow', {
this.editor.on("itemSaved", this.onItemSaved, this);
this.octoPartButton = Ext.create("Ext.button.Button", {
- text: i18n("OctoPart…"),
+ text: i18n("Octopart…"),
iconCls: 'partkeepr-icon octopart',
handler: Ext.bind(this.onOctoPartClick, this)
});
@@ -143,7 +143,7 @@ Ext.define('PartKeepr.PartEditorWindow', {
this.octoPartQueryWindow.startSearch(this.editor.nameField.getValue());
this.octoPartQueryWindow.on("refreshData", this.onRefreshData, this);
} else {
- Ext.MessageBox.alert(i18n("OctoPart is not configured"), i18n("Your administrator needs to configure the API key for OctoPart in the parameters.php file - see parameters.php.dist for instructions"));
+ Ext.MessageBox.alert(i18n("Octopart is not configured"), i18n("Your administrator needs to configure the API key for Octopart in the parameters.php file - see parameters.php.dist for instructions"));
}
},
onRefreshData: function () {
diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/UserPreferences/Preferences/OctoPartConfiguration.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/UserPreferences/Preferences/OctoPartConfiguration.js
@@ -100,7 +100,7 @@ Ext.define('PartKeepr.Components.UserPreferences.Preferences.OctoPartConfigurati
},
statics: {
iconCls: 'partkeepr-icon octopart',
- title: i18n('OctoPart'),
+ title: i18n('Octopart'),
menuPath: [{iconCls: 'fugue-icon ui-scroll-pane-image', text: i18n("User Interface")}]
}
});
diff --git a/src/PartKeepr/OctoPartBundle/Controller/DefaultController.php b/src/PartKeepr/OctoPartBundle/Controller/DefaultController.php
@@ -38,7 +38,7 @@ class DefaultController extends FOSRestController
*/
public function getPartsByQueryAction(Request $request)
{
- $start = 0;
+ $start = 1;
$responseData = [];
@@ -50,20 +50,27 @@ class DefaultController extends FOSRestController
$data = $this->get("partkeepr.octopart_service")->getPartyByQuery($query, $start);
+ $errors = $data["errors"];
+ $data = $data["data"]["search"];
+
$responseData["hits"] = $data["hits"];
$responseData["results"] = [];
+ $responseData["errors"] = $errors;
- foreach ($data["results"] as $result) {
- $responseItem = [];
- $responseItem["mpn"] = $result["item"]["mpn"];
- $responseItem["title"] = $result["snippet"];
- $responseItem["manufacturer"] = $result["item"]["manufacturer"]["name"];
- $responseItem["numOffers"] = count($result["item"]["offers"]);
- $responseItem["numSpecs"] = count($result["item"]["specs"]);
- $responseItem["numDatasheets"] = count($result["item"]["datasheets"]);
- $responseItem["url"] = $result["item"]["octopart_url"];
- $responseItem["uid"] = $result["item"]["uid"];
- $responseData["results"][] = $responseItem;
+ if ($data) {
+ foreach ($data["results"] as $result) {
+ $part = $result["part"];
+ $responseItem = [];
+ $responseItem["mpn"] = $part["mpn"];
+ $responseItem["title"] = $part["short_description"];
+ $responseItem["manufacturer"] = $part["manufacturer"]["name"];
+ $responseItem["numOffers"] = count($part["sellers"]);
+ $responseItem["numSpecs"] = count($part["specs"]);
+ $responseItem["numDatasheets"] = count($part["document_collections"]);
+ $responseItem["url"] = "https://octopart.com".$part["slug"];
+ $responseItem["uid"] = $part["id"];
+ $responseData["results"][] = $responseItem;
+ }
}
return $responseData;
diff --git a/src/PartKeepr/OctoPartBundle/Resources/config/services.xml b/src/PartKeepr/OctoPartBundle/Resources/config/services.xml
@@ -7,6 +7,7 @@
<services>
<service id="partkeepr.octopart_service" class="PartKeepr\OctoPartBundle\Services\OctoPartService">
<argument type="string">%partkeepr.octopart.apikey%</argument>
+ <argument type="string">%partkeepr.octopart.limit%</argument>
</service>
</services>
</container>
diff --git a/src/PartKeepr/OctoPartBundle/Services/OctoPartService.php b/src/PartKeepr/OctoPartBundle/Services/OctoPartService.php
@@ -3,53 +3,260 @@
namespace PartKeepr\OctoPartBundle\Services;
use Guzzle\Http\Client;
+use Predis\Client as PredisClient;
class OctoPartService
{
- const OCTOPART_ENDPOINT = "http://octopart.com/api/v3/";
+ const OCTOPART_ENDPOINT = "https://octopart.com/api/v4/endpoint";
+
+ const OCTOPART_QUERY = <<<'EOD'
+ query MyPartSearch($q: String!, $filters: Map, $limit: Int!, $start: Int, $country: String = "DE", $currency: String = "EUR") {
+ search(q: $q, filters: $filters, limit: $limit, start: $start, country: $country, currency: $currency) {
+ hits
+
+ results {
+ part {
+ id
+ mpn
+ slug
+ short_description
+ counts
+ manufacturer {
+ name
+ }
+ best_datasheet {
+ name
+ url
+ credit_string
+ credit_url
+ page_count
+ mime_type
+ }
+ best_image {
+ url
+ }
+ specs {
+ attribute {
+ name
+ group
+ }
+ display_value
+ }
+ document_collections {
+ name
+ documents {
+ name
+ url
+ credit_string
+ credit_url
+ }
+ }
+ descriptions {
+ credit_string
+ text
+ }
+ cad {
+ add_to_library_url
+ }
+ reference_designs {
+ name
+ url
+ }
+ sellers {
+ company {
+ homepage_url
+ is_verified
+ name
+ slug
+ }
+ is_authorized
+ is_broker
+ is_rfq
+ offers {
+ click_url
+ inventory_level
+ moq
+ packaging
+ prices {
+ conversion_rate
+ converted_currency
+ converted_price
+ currency
+ price
+ quantity
+ }
+ sku
+ updated
+ }
+ }
+ }
+ }
+ }
+ }
+EOD;
+
+ const OCTOPART_PARTQUERY = <<<'EOD'
+ query MyPartSearch($id: String!, $country: String = "DE", $currency: String = "EUR") {
+ parts(ids: [$id], country: $country, currency: $currency) {
+ id
+ mpn
+ slug
+ short_description
+ counts
+ manufacturer {
+ name
+ }
+ best_datasheet {
+ name
+ url
+ credit_string
+ credit_url
+ page_count
+ mime_type
+ }
+ best_image {
+ url
+ }
+ specs {
+ attribute {
+ name
+ group
+ }
+ display_value
+ }
+ document_collections {
+ name
+ documents {
+ name
+ url
+ credit_string
+ credit_url
+ }
+ }
+ descriptions {
+ credit_string
+ text
+ }
+ cad {
+ add_to_library_url
+ }
+ reference_designs {
+ name
+ url
+ }
+ sellers {
+ company {
+ homepage_url
+ is_verified
+ name
+ slug
+ }
+ is_authorized
+ is_broker
+ is_rfq
+ offers {
+ click_url
+ inventory_level
+ moq
+ packaging
+ prices {
+ conversion_rate
+ converted_currency
+ converted_price
+ currency
+ price
+ quantity
+ }
+ sku
+ updated
+ }
+ }
+ }}
+EOD;
private $apiKey;
+ private $limit = "3";
- public function __construct($apiKey)
+ public function __construct($apiKey, $limit)
{
$this->apiKey = $apiKey;
+ $this->limit = $limit;
}
public function getPartByUID($uid)
{
+ try {
+ $redisclient = new PredisClient();
+ $redisclient->connect();
+ $part = $redisclient->get($uid);
+ if ($part) {
+ return json_decode($part, true);
+ }
+ $redisclient->disconnect();
+ } catch (\Exception $e) {
+ }
+
$client = new Client();
- $request = $client->createRequest('GET', self::OCTOPART_ENDPOINT."parts/".$uid);
-
- $request->getQuery()->add("apikey", $this->apiKey);
- $request->getQuery()->add("include", [
- "short_description",
- "datasheets",
- "compliance_documents",
- "descriptions",
- "imagesets",
- "specs",
- "reference_designs",
- "cad_models",
- ]);
+ $request = $client->createRequest('POST', self::OCTOPART_ENDPOINT);
+ $request->setHeader('Content-Type', 'application/json');
+ $request->getQuery()->add("token", $this->apiKey);
+
+ $graphql = [
+ "query" => self::OCTOPART_PARTQUERY,
+ "operationName" => "MyPartSearch",
+ "variables" => [
+ "id" => $uid,
+ ],
+ ];
+ $request->setBody(json_encode($graphql));
$request->send();
- return json_decode($request->getResponse()->getBody(), true);
+ $body = $request->getResponse()->getBody();
+
+ $data = json_decode($body, true);
+
+ return $data["data"]["parts"][0];
}
- public function getPartyByQuery($query, $start = 0)
+ public function getPartyByQuery($q, $startpage = 1)
{
$client = new Client();
- $request = $client->createRequest('GET', self::OCTOPART_ENDPOINT."parts/search");
- $request->getQuery()->add("apikey", $this->apiKey);
- $request->getQuery()->add("q", $query);
- $request->getQuery()->add("start", ($start - 1) * 20);
- $request->getQuery()->add("include", ["short_description", "specs", "datasheets"]);
- $request->getQuery()->add("limit", 20);
+ $request = $client->createRequest('POST', self::OCTOPART_ENDPOINT);
+ $request->setHeader('Content-Type', 'application/json');
+ $request->getQuery()->add("token", $this->apiKey);
+
+ $graphql = [
+ "query" => self::OCTOPART_QUERY,
+ "operationName" => "MyPartSearch",
+ "variables" => [
+ "q" => $q,
+ "limit" => $this->limit,
+ "start" => ($startpage - 1) * $this->limit, // "start" is 0-based
+ ],
+ ];
+ $request->setBody(json_encode($graphql));
$request->send();
- return json_decode($request->getResponse()->getBody(), true);
+ $body = $request->getResponse()->getBody();
+
+ $parts = json_decode($body, true);
+
+ // work around the low number of allowed accesses to octopart's api
+ try {
+ $redisclient = new PredisClient();
+ $redisclient->connect();
+ $results = $parts["data"]["search"]["results"];
+ foreach ($results as $result) {
+ $id = $result["part"]["id"];
+ $redisclient->set($id, json_encode($result["part"]));
+ }
+ $redisclient->disconnect();
+ } catch (\Exception $e) {
+ }
+
+ return $parts;
}
}