Skip to content
Browse files

sync with source

Summary: Somewhat large commit to sync the source... we plan on pushing
this more often
  • Loading branch information...
1 parent 6b23497 commit dac136af6aba8be14a5bd6f4589b09e866bc9038 Brian Rosenthal committed
Showing with 4,182 additions and 1,572 deletions.
  1. +8 −0 ads/ads.css
  2. +10 −9 ads/controller/app.js
  3. +1 −1 ads/controller/bulkImport.js
  4. +183 −111 ads/controller/download.js
  5. +6 −32 ads/controller/downloadBCT.js
  6. +3 −0 ads/controller/duplicate.js
  7. +8 −0 ads/controller/export.js
  8. +81 −0 ads/controller/migrateDB/adAccountIDs.js
  9. +3 −0 ads/controller/migrateDB/textIDs.js
  10. +4 −1 ads/controller/mutator.js
  11. +2 −0 ads/controller/revert.js
  12. +16 −11 ads/controller/upload.js
  13. +128 −79 ads/db.js
  14. +1 −1 ads/en_US.js
  15. +3 −2 ads/job/adImporter.js
  16. +17 −2 ads/job/campImporter.js
  17. +4 −2 ads/job/downloadBCT.js
  18. +2 −1 ads/job/tabSeparatedParser.js
  19. +47 −24 ads/lib/adCreativeType.js
  20. +4 −4 ads/lib/completions.js
  21. +1 −1 ads/lib/countries.js
  22. +13 −7 ads/lib/creativeMap.js
  23. +118 −0 ads/lib/dateUtil.js
  24. +4 −1 ads/lib/estimateTargetingStats.js
  25. +129 −0 ads/lib/fetcher.js
  26. +105 −0 ads/lib/loggingState.js
  27. +113 −0 ads/lib/monkeyPatches.js
  28. +1 −1 ads/lib/prop/base.js
  29. +14 −1 ads/lib/prop/number.js
  30. +43 −6 ads/lib/prop/timestamp.js
  31. +15 −7 ads/lib/prop/userAdClusters.js
  32. +0 −98 ads/lib/typeahead/APIDataSource.js
  33. +1 −1 ads/lib/typeahead/DateDataSource.js
  34. +73 −24 ads/lib/typeahead/GraphAPIDataSource.js
  35. +47 −0 ads/lib/typeahead/graph/AccountsGraphAPIDataSource.js
  36. +47 −0 ads/lib/typeahead/graph/KeywordsGraphAPIDataSource.js
  37. +5 −8 ads/lib/unzipImages.js
  38. +39 −2 ads/model/account.js
  39. +56 −19 ads/model/ad.js
  40. +181 −7 ads/model/ad/props.js
  41. +65 −0 ads/model/baseModel.js
  42. +1 −2 ads/model/baseStat.js
  43. +14 −3 ads/model/bct.js
  44. +128 −43 ads/model/campaign.js
  45. +1 −1 ads/model/connectedObject.js
  46. +0 −5 ads/model/contract.js
  47. +10 −14 ads/model/image.js
  48. +61 −27 ads/model/topline.js
  49. +2 −5 ads/models.js
  50. +1 −6 ads/view/adEditor.js
  51. +13 −0 ads/view/adEditor/DSPricing.js
  52. +0 −4 ads/view/adEditor/DSTargeting.js
  53. +0 −1 ads/view/adEditor/adEditor.css
  54. +304 −109 ads/view/adEditor/creative.js
  55. +1 −1 ads/view/adEditor/creative/creative.html
  56. +15 −7 ads/view/adEditor/creative/creativeTypeSpecs.js
  57. +0 −3 ads/view/adEditor/eduWork.js
  58. +75 −73 ads/view/adEditor/interests.js
  59. +12 −4 ads/view/adEditor/locDem.js
  60. +170 −148 ads/view/adPane.js
  61. +10 −0 ads/view/adPane/adPane.css
  62. +69 −59 ads/view/adPane/formatters.js
  63. +24 −4 ads/view/adPreview.js
  64. +10 −2 ads/view/adPreview/adPreview.html
  65. +2 −2 ads/view/bulkImportDialog.js
  66. +248 −224 ads/view/campEditor.js
  67. +16 −9 ads/view/campEditor/campEditor.css
  68. +7 −1 ads/view/campEditor/campEditor.html
  69. +6 −6 ads/view/campPane.js
  70. BIN ads/view/campPane/completed.png
  71. +6 −2 ads/view/connectedObjectDialog.js
  72. +3 −4 ads/view/content.js
  73. +0 −3 ads/view/contractEditor.js
  74. +1 −9 ads/view/contractEditor/contractEditorLeft.html
  75. +56 −32 ads/view/contractPane.js
  76. +5 −0 ads/view/contractPane/dataTableList.css
  77. +1 −1 ads/view/contractPane/formatters.js
  78. +6 −0 ads/view/contractPane/pack.js
  79. +1 −1 ads/view/control/bct.js
  80. +260 −0 ads/view/control/pit.js
  81. BIN ads/view/control/pit/indicator_blue_small.gif
  82. +13 −14 ads/{model/adCreative.js → view/control/pit/pit.css}
  83. +9 −0 ads/view/control/pit/pit.html
  84. +2 −2 ads/view/control/radius.js
  85. +1 −0 ads/view/controls.js
  86. +61 −6 ads/view/downloadDialog.js
  87. +36 −6 ads/view/downloadProgress.js
  88. +15 −6 ads/view/downloadProgress/downloadProgress.css
  89. +9 −0 ads/view/downloadProgress/downloadProgress.html
  90. +0 −4 ads/view/head.js
  91. BIN ads/view/head/settings.png
  92. +2 −2 ads/view/imageSelector.js
  93. +2 −1 ads/view/logDialog.js
  94. +63 −0 ads/view/refreshDialog.js
  95. +2 −1 ads/view/selectDialog.js
  96. +2 −1 ads/view/settingsDialog.js
  97. +2 −1 ads/view/uploadDialog.js
  98. +1 −0 ads/views.js
  99. +1 −1 app.js
  100. +111 −0 lib/conflicter.js
  101. +93 −53 lib/connect.js
  102. +21 −9 lib/dateRange.js
  103. +85 −14 lib/errorReport.js
  104. +4 −0 lib/formatters.js
  105. +185 −42 lib/graphlink.js
  106. +54 −1 lib/utils.js
  107. +5 −3 storage/prop/timestamp.js
  108. +45 −53 storage/storage.js
  109. +1 −1 tx.js
  110. +4 −2 uki-core/observable.js
  111. +5 −0 uki-core/utils.js
  112. +3 −2 uki-fb/view/HTMLLayout.js
  113. +5 −0 uki-fb/view/checkbox/checkbox.css
  114. +3 −3 uki-fb/view/dataList.js
  115. +75 −28 uki-fb/view/dataTable.js
  116. +6 −0 uki-fb/view/dataTable/compare.js
  117. +1 −1 uki-fb/view/dataTable/header.html
  118. BIN uki-fb/view/selector/arrow.png
  119. +1 −1 uki-fb/view/selector/selector.css
  120. +16 −5 uki-fb/view/text.js
  121. +29 −11 uki-fb/view/text/text.css
  122. +57 −8 uki-fb/view/tokenizer.js
  123. +12 −12 uki-fb/view/typeahead/util.js
View
8 ads/ads.css
@@ -43,3 +43,11 @@ body {
.intern {
background: #FFF9D7;
}
+
+/**
+ * Fix the min-width of the "content" pane, so that the UI
+ * form widgets don't overlap
+ */
+div#content {
+ min-width: 790px;
+}
View
19 ads/controller/app.js
@@ -27,6 +27,7 @@ requireCss("../spacing.css");
+require("../lib/monkeyPatches");
var dom = require("../../uki-core/dom"),
env = require("../../uki-core/env"),
fun = require("../../uki-core/function"),
@@ -102,6 +103,8 @@ function checkInstalled() {
* @namespace
*/
function init(uid) {
+ var flow_name = 'app_init';
+ require("../lib/loggingState").startFlow(flow_name);
builder.namespaces.unshift(require("../views"));
env.doc.body.style.cssText += 'overflow: hidden';
@@ -110,13 +113,9 @@ function init(uid) {
layout();
if (checkInstalled()) {
- db.init(uid, dbInitCallback);
+ db.init(uid, initList);
}
-}
-
-function dbInitCallback() {
- require("./downloadBCT")
- .DownloadBCT.download(initList);
+ require("../lib/loggingState").endFlow(flow_name);
}
/**
@@ -256,7 +255,7 @@ function _buildContractToplineMap(contracts, toplines, campaigns) {
campaigns.forEach(function(c) {
if (c.isFromTopline()) {
- toplines_map[c.line_id()].children().push(c);
+ toplines_map[c.idx_line_id()].children().push(c);
}
});
}
@@ -300,7 +299,8 @@ function _initHandler() {
if (this.selectedIndexes().length > 1) {
// is several ads are selected proxy props
// through ad.Group
- var group = new models.AdGroup(this.selectedRows());
+ var AdGroup = require("../model/ad/group").Group;
+ var group = new AdGroup(this.selectedRows());
view.byId('adEditor').model(group);
} else {
view.byId('adEditor').model(this.selectedRow() || null);
@@ -311,7 +311,8 @@ function _initHandler() {
if (this.selectedIndexes().length > 1) {
// is several camps are selected proxy props
// through campaign.Group
- var group = new models.CampGroup(this.selectedRows());
+ var CampGroup = require("../model/campaign/group").Group;
+ var group = new CampGroup(this.selectedRows());
view.byId('campEditor').model(group);
} else {
view.byId('campEditor').model(this.selectedRow() || null);
View
2 ads/controller/bulkImport.js
@@ -159,12 +159,12 @@ function initErrors() {
BulkImport.importInto = function(account, text, imageLookup) {
require("../lib/completions").dialog = BulkImport.progressDialog();
+ var formatter = require("../../lib/formatters").createPercentFormatter(2);
var message = BulkImport.log(tx(
'ads:pe:import-parsets-message',
{ percent: formatter(0) }));
var parser = new ParserJob(account, text, imageLookup);
- var formatter = require("../../lib/formatters").createPercentFormatter(2);
parser.oncomplete(function() {
if (parser.errors().length) {
View
294 ads/controller/download.js
@@ -1,36 +1,40 @@
/**
-* Copyright 2011 Facebook, Inc.
-*
-* You are hereby granted a non-exclusive, worldwide, royalty-free license to
-* use, copy, modify, and distribute this software in source code or binary
-* form for use in connection with the web services and APIs provided by
-* Facebook.
-*
-* As with any software that integrates with the Facebook platform, your use
-* of this software is subject to the Facebook Developer Principles and
-* Policies [http://developers.facebook.com/policy/]. This copyright notice
-* shall be included in all copies or substantial portions of the software.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-* DEALINGS IN THE SOFTWARE.
-*
-*
-*/
+ * Copyright 2011 Facebook, Inc.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to
+ * use, copy, modify, and distribute this software in source code or binary
+ * form for use in connection with the web services and APIs provided by
+ * Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use
+ * of this software is subject to the Facebook Developer Principles and
+ * Policies [http://developers.facebook.com/policy/]. This copyright notice
+ * shall be included in all copies or substantial portions of the software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ *
+ */
var utils = require("../../uki-core/utils"),
fun = require("../../uki-core/function"),
evt = require("../../uki-core/event"),
env = require("../../uki-core/env"),
+ Dialog = require("../../uki-fb/view/dialog").Dialog,
+
storage = require("../../storage/storage"),
libUtils = require("../../lib/utils"),
asyncUtils = require("../../lib/async"),
graphlink = require("../../lib/graphlink").gl,
+ pathUtils = require("../../lib/pathUtils"),
+ conflicter = require("../../lib/conflicter").cc,
App = require("./app").App,
DownloadDialog = require("../view/downloadDialog").DownloadDialog,
@@ -38,15 +42,12 @@ var utils = require("../../uki-core/utils"),
Account = require("../model/account").Account,
Ad = require("../model/ad").Ad,
- AdCreative = require("../model/adCreative").AdCreative,
Img = require("../model/image").Image,
Campaign = require("../model/campaign").Campaign,
ConnectedObject = require("../model/connectedObject").ConnectedObject,
Contract = require("../model/contract").Contract,
Topline = require("../model/topline").Topline;
-var META_REFRESH_TIMEOUT = 1000 * 60 * 60 * 24 * 7; // once per week
-
/**
* Download ads and campaigns from server
* @namespace
@@ -94,9 +95,15 @@ Download._ondownload = function(e) {
Download.loadModels(
progress,
function() {
- progress.visible(false);
require("./downloadBCT")
- .DownloadBCT.download(function() {
+ .DownloadBCT.download(progress, function() {
+
+ // activate dismiss button
+ progress.disableDismiss(false);
+ if (!progress.conflicts) {
+ progress.triggerDismiss({ type: 'click' });
+ }
+
// force app update upon completion
App.reload();
});
@@ -104,7 +111,20 @@ Download._ondownload = function(e) {
};
-// --- Download Flow ---
+/** --- Download Flow ---
+ *
+ * HERE BE
+ *
+ * .==. .==.
+ * //`^\\ //^`\\
+ * // ^ ^\(\__/)/^ ^^\\
+ * //^ ^^ ^/6 6\ ^^ ^ \\
+ * //^ ^^ ^/( .. )\^ ^ ^ \\
+ * // ^^ ^/\| v""v |/\^ ^ ^\\
+ * // ^^/\/ / `~~` \ \/\^ ^\\
+ * -----------------------------
+ * WARNING FOR THE WISE!
+ */
/**
* handler to start downloading including contract/topline
@@ -119,6 +139,7 @@ Download.loadModels = function(progress, cb, account_ids) {
// unset progress handler
graphlink.removeListener('progress');
graphlink.removeListener('error');
+ conflicter.removeListener('conflict');
cb && cb();
};
@@ -128,9 +149,12 @@ Download.loadModels = function(progress, cb, account_ids) {
});
graphlink.on('error', function(e) {
var err = e.error;
- alert(err.message);
+ Dialog.alert(err.message);
callback();
});
+ conflicter.on('conflict', function(e) {
+ progress.conflictsUpdate(e.numconflicts);
+ });
var state = null;
@@ -152,13 +176,10 @@ Download.loadModels = function(progress, cb, account_ids) {
// Special treatment since we need the account_ids for everything
// subsequently, and the download may be invoked without explicitly
// specifying ids
- loadAccounts(account_ids, null, progress, function(used_account_ids) {
+ loadAccounts(account_ids, null, progress, function(used_accounts) {
- account_ids = libUtils.wrapArray(used_account_ids).filter(
- function(acc_id) {
- return !!acc_id;
- }
- );
+ used_accounts = used_accounts.filter(Boolean);
+ account_ids = utils.pluck(used_accounts, 'id');
// numerous checks for valid account_ids since things can go wrong
// server or client side
@@ -167,8 +188,9 @@ Download.loadModels = function(progress, cb, account_ids) {
return;
}
- var loaders = [removeOldData, loadObjects, loadContracts, loadToplines,
- loadCampaigns, loadAds, loadAdCreatives, loadAdImages];
+ state = used_accounts;
+ var loaders = [loadTimezoneOffsets, removeOldData, loadObjects,
+ loadContracts, loadToplines, loadCampaigns, loadAds];
asyncUtils.forEach(loaders,
function(loader, i, iterCallback) {
@@ -202,17 +224,67 @@ Download.loadModels = function(progress, cb, account_ids) {
*/
function loadAccounts(account_ids, state, progress, callback) {
progress.setStep('accounts');
- var allAccounts = [];
Account.loadFromIds(
account_ids,
function(accounts) {
progress.completeStep('accounts');
- allAccounts.push.apply(allAccounts, accounts);
- callback(utils.pluck(allAccounts, 'id'));
+ callback(accounts);
}
);
}
+/**
+ * Download account timezone offsets
+ *
+ * @param account_ids array of account ids being dl'd
+ * @param list of accounts
+ * @param progress DownloadProgress instance
+ * @param callback called on finish
+ * @return none
+ */
+function loadTimezoneOffsets(account_ids, accounts, progress, callback) {
+ progress.setStep('timezones');
+ this._updatedAccounts = 0;
+ this._totalAccounts = accounts.length;
+ for (var i = 0; i < accounts.length; i++) {
+ var act_id = accounts[i].id();
+ var path = pathUtils.join('act_' + act_id, 'timezoneoffsets');
+
+ // TODO use graphlink.batchFetchEdges when it's implemented
+ var offsets = [i]; // pass array index to the callback for fetchEdge
+ graphlink.fetchEdge(path, {},
+ fun.bind(function(fetched) {
+ var index = fetched[0];
+ fetched = fetched.slice(1);
+ accounts[index]
+ .fromRemoteObject({ 'timezone_offsets': fetched });
+ _updateTZProgress(accounts, progress, callback);
+ }, this),
+ null, offsets
+ );
+ }
+}
+
+/**
+ * Update the progress of the job when the timezone offsets for an account is
+ * fetched.
+ * TODO replace with graphlink.batchFetchEdges
+ *
+ * @param accounts to update
+ * @param progress DownloadProgress instance
+ * @param callback function after progress is complete
+ * @return none
+ */
+function _updateTZProgress(accounts, progress, callback) {
+ this._updatedAccounts++;
+ if (this._updatedAccounts == this._totalAccounts) {
+ progress.completeStep('timezones');
+ this._updatedAccounts = 0;
+ Account.storeMulti(accounts, function() {
+ callback(null);
+ });
+ }
+}
/**
* removing data associated with accounts being refreshed
@@ -234,26 +306,15 @@ function removeOldData(account_ids, state, progress, callback) {
});
});
- // clean up all previous campaigns and ads
- Campaign.findAllBy(
+ // clean up all previous campaigns
+ Campaign.deleteBy(
'account_id',
account_ids,
- function(campaigns) {
- Ad.deleteBy(
- 'campaign_id',
- utils.pluck(campaigns, 'id'),
- function() {
- Campaign.deleteBy(
- 'account_id',
- account_ids,
- function() {
- callback(null);
- });
- });
+ function() {
+ callback(null);
});
}
-
/**
* Download ConnectedObjects
*
@@ -349,23 +410,21 @@ function loadToplines(acc_ids, contracts, progress, callback, _totalToplines) {
function loadCampaigns(account_ids, state, progress, callback) {
progress.setStep('campaigns');
- var totalCampaigns = [];
Campaign.loadFromAccountIds(
account_ids,
function(campaigns) {
-
progress.completeStep('campaigns');
- totalCampaigns = totalCampaigns.concat(campaigns);
-
- Campaign.prepare(function(campaigns) {
- totalCampaigns.prefetch && totalcampaigns.prefetch();
- callback(totalCampaigns);
- }, true);
+ Campaign.prepare(callback, true);
}
);
}
+
+// ---
+// Ads from here on
+// Sigh, they are complicated
+
/**
* Download ads by campaigns
* Downloads ads from up to 10 campaigns from a single account
@@ -379,67 +438,82 @@ function loadCampaigns(account_ids, state, progress, callback) {
function loadAds(account_ids, state, progress, callback) {
progress.setStep('ads');
- var totalAds = [];
- Ad.loadFromAccountIds(account_ids,
- function(ads, isDone) {
+ var paths = Ad.pathsFromAccountIds(account_ids);
+ graphlink.serialFetchEdges(paths, { 'include_demolink_hashes': true },
+ function(fetched) {
+ // sometimes, we will receive current ads in deleted campaigns.
+ // remove those here.
+ for (var i = fetched.length - 1; i >= 0; i--) {
+ var campaign_id = fetched[i].campaign_id;
+ if (!Campaign.byId(campaign_id)) {
+ fetched.splice(i, 1);
+ }
+ }
+ var createdAds = Ad.createMultipleFromRemote(fetched);
progress.completeStep('ads');
- totalAds.push.apply(totalAds, ads);
-
- totalAds.prefetch && totalAds.prefetch();
- callback(totalAds);
- }
- );
+ fetchAdCreatives(account_ids, createdAds, progress, function(adimages) {
+ // store all ads now, after adcreatives and adimages are done
+ createdAds.forEach(function(ad) {
+ ad.initChangeable();
+ ad.validateAll();
+ });
+ Ad.checkAllForConflicts(createdAds, function() {
+ Ad.deleteBy('account_id', account_ids, function() {
+ Ad.storeMulti(createdAds, callback);
+ });
+ }, this);
+ });
+ });
}
/**
- * load ad creatives
+ * fetch ad creatives
*/
-function loadAdCreatives(account_ids, ads, progress, callback) {
+function fetchAdCreatives(account_ids, ads, progress, callback) {
progress.setStep('adcreatives');
if (!ads.length) {
- callback([]);
+ progress.completeStep('adcreatives');
+ updateAdImages(account_ids, ads, progress, callback);
return;
}
- var adMapByCreative = {};
- var creative_ids = [];
- libUtils.wrapArray(ads).map(function(ad) {
- var creativeId = (ad.creative_ids() || [])[0];
- if (creativeId) {
- adMapByCreative[creativeId] = adMapByCreative[creativeId] || [];
- adMapByCreative[creativeId].push(ad);
- creative_ids.push(creativeId);
- }
+ var creative_ids = utils.pluck(ads, 'creative_ids');
+ creative_ids = creative_ids.map(function(creative_id) {
+ return utils.isArray(creative_id) ? creative_id[0] : creative_id;
});
-
- creative_ids = utils.unique(creative_ids);
+ // apparently ads don't have creatives
+ creative_ids = utils.unique(creative_ids).filter(Boolean);
// load creatives all together
- AdCreative.loadFromIds(creative_ids,
- function(data) {
- libUtils.wrapArray(data).map(function(creative) {
- var creativeId = String(creative.creative_id);
- if (adMapByCreative[creativeId]) {
+
+ graphlink.fetchObjectsById(creative_ids, {},
+ function(adcreatives) {
+ var fetchedIdMap = {};
+ adcreatives.forEach(function(creative) {
+ // do not use creative_id for indexing
+ fetchedIdMap[creative.id * 1] = creative;
+ });
+
+ ads.forEach(function(ad) {
+ var creative_id = ad.creative_ids();
+ utils.isArray(creative_id) ? creative_id[0] : creative_id;
+ var creative = fetchedIdMap[creative_id];
+ if (creative) {
delete creative.name;
- libUtils.wrapArray(adMapByCreative[creativeId]).map(function(ad) {
- ad
- .muteChanges(true)
- .fromRemoteObject(creative)
- .muteChanges(false);
- });
+ ad
+ .muteChanges(true)
+ .fromRemoteObject(creative)
+ .muteChanges(false);
}
});
progress.completeStep('adcreatives');
-
- // commit the ad changes back to db
- storage.Storage.storeMulti.call(Ad, ads, function(data) {
- callback(data);
- });
+ // finish with Ad Images before storing ads
+ updateAdImages(account_ids, ads, progress, callback);
}
);
// end load creatives
@@ -449,21 +523,19 @@ function loadAdCreatives(account_ids, ads, progress, callback) {
* Ultimately load adimages distinctly
* for now, update images / hashes in ads from the creatives
*/
-function loadAdImages(account_ids, ads, progress, callback) {
+function updateAdImages(account_ids, ads, progress, callback) {
progress.setStep('adimages');
// when you're done loading ads
// populate image lookup table with newly downloaded ads
if (!ads.length) {
- callback(null);
+ callback();
return;
}
- Img.updateImagesInAllAccounts(account_ids, ads, function() {
- // commit the ad changes back to db
- storage.Storage.storeMulti.call(Ad, ads, function(data) {
- progress.statusUpdate(ads.length);
- progress.completeStep('adimages');
- callback(null);
- });
+ Img.updateImagesInAllAccounts(account_ids, ads, function(adimages) {
+ // finally, save the ads to db
+ progress.statusUpdate(ads.length);
+ progress.completeStep('adimages');
+ callback(adimages);
});
}
View
38 ads/controller/downloadBCT.js
@@ -22,48 +22,22 @@
*
*/
-var fun = require("../../uki-core/function");
-var build = require("../../uki-core/builder").build;
+
var Job = require("../job/downloadBCT").DownloadBCT;
-var Mustache = require("../../uki-core/mustache").Mustache;
var DownloadBCT = {
- download: function(callback) {
+
+ download: function(dialog, callback) {
+ dialog.bct = tx('ads:pe:bct-needs-update');
+ dialog.statusUpdate();
var job = new Job();
- var dialog = DownloadBCT.dialog();
job
- .onprogress(function(e) {
- var status = e.status;
- dialog.visible(true);
- })
.oncomplete(function() {
- dialog.visible(false);
callback();
})
.start();
- },
-
- clearLastSync: function() {
- new Job().clearLastSync();
- },
-
- dialog: function() {
- if (!this._dialog) {
- var col = build({ view: 'Dialog', modal: true, childViews: [
- { view: 'DialogHeader', text: "Downloading BCT" },
- { view: 'DialogContent', childViews: [
- { view: 'DialogBody', childViews: [
- { view: 'Text', text: tx('ads:pe:bct-needs-update') },
- { view: 'Text', as: 'progress' }
- ] }
- ] }
- ]});
- this._dialog = col[0];
- this._dialog.progress = col.view('progress');
- }
- return this._dialog;
}
-};
+};
exports.DownloadBCT = DownloadBCT;
View
3 ads/controller/duplicate.js
@@ -63,10 +63,12 @@ Duplicate.duplicateAdsHandler = function() {
};
Duplicate.duplicateCampsHandler = function() {
+ require("../lib/loggingState").startFlow('duplicate_campaigns');
var camps = view.byId('campPane-data').selectedRows();
if (!camps.length) {
require("../../uki-fb/view/dialog").Dialog
.alert(tx('ads:pe:no-camps-to-duplicate'));
+ require("../lib/loggingState").endFlow('duplicate_campaigns');
return;
}
@@ -83,6 +85,7 @@ Duplicate.duplicateCampsHandler = function() {
.useNameMatching(false)
.oncomplete(function() {
require("./app").App.reload();
+ require("../lib/loggingState").endFlow('duplicate_campaigns');
})
.start();
};
View
8 ads/controller/export.js
@@ -34,6 +34,7 @@ var Export = {};
Export.handleCampaigns = function() {
+ require("../lib/loggingState").startFlow('export_campaigns');
var campaigns = view.byId('campPane-data').selectedRows();
var text = [Export.exportHeader(isCorpActSelected())];
function processChunk() {
@@ -59,13 +60,17 @@ Export.handleCampaigns = function() {
processChunk();
});
} else {
+ require("../lib/loggingState").startFlow('export_send_file_campaigns');
Export.sendFile(text);
+ require("../lib/loggingState").endFlow('export_send_file_campaigns');
+ require("../lib/loggingState").endFlow('export_campaigns');
}
}
processChunk();
};
Export.handleAds = function() {
+ require("../lib/loggingState").startFlow('export_ads');
var ads = view.byId('adPane-data').selectedRows();
var adIds = utils.pluck(ads, 'id');
var text = [Export.exportHeader(isCorpActSelected())];
@@ -93,7 +98,10 @@ Export.handleAds = function() {
});
});
} else {
+ require("../lib/loggingState").startFlow('export_send_file_ads');
Export.sendFile(text);
+ require("../lib/loggingState").endFlow('export_send_file_ads');
+ require("../lib/loggingState").endFlow('export_ads');
}
}
processChunk();
View
81 ads/controller/migrateDB/adAccountIDs.js
@@ -0,0 +1,81 @@
+/**
+* Copyright 2011 Facebook, Inc.
+*
+* You are hereby granted a non-exclusive, worldwide, royalty-free license to
+* use, copy, modify, and distribute this software in source code or binary
+* form for use in connection with the web services and APIs provided by
+* Facebook.
+*
+* As with any software that integrates with the Facebook platform, your use
+* of this software is subject to the Facebook Developer Principles and
+* Policies [http://developers.facebook.com/policy/]. This copyright notice
+* shall be included in all copies or substantial portions of the software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+* DEALINGS IN THE SOFTWARE.
+*
+*
+* @providesModule ads-controller-migrateDB-ad-accountIDs
+*
+* @author zahanm
+*/
+
+var fun = require("../../../uki-core/function"),
+ utils = require("../../../uki-core/utils"),
+
+ storage = require("../../../storage/storage"),
+
+ Ad = require("../../model/ad").Ad;
+
+/**
+ * Recreates the Ad table because
+ * account_id has been added as an indexed property
+ * naturally need to use raw SQL as Storage methods are %^&*'d
+ */
+function migrateWebSql(uid, DB, callback) {
+ DB.transaction(function(tx1) {
+ tx1.executeSql('SELECT data FROM ' + Ad.tableName(), [],
+ function(tx2, r) {
+ if (r.rows && r.rows.length && !r.rows.item(0).account_id) {
+ var ads = [];
+ for (var i = 0; i < r.rows.length; i++) {
+ var obj = JSON.parse(r.rows.item(i).data);
+ var ad = new Ad();
+ ad.fromDBObject(obj)
+ .id(obj.id);
+ ads.push(ad);
+ }
+ Ad.withTransaction(tx2, function() {
+ this.dbDrop(function() {
+ DB.transaction(function(tx3) {
+ Ad.withTransaction(tx3, function() { this.dbInit(); });
+ }, storage.errorCallback, function() {
+ Ad.storeMulti(ads, callback || fun.FT);
+ });
+ });
+ });
+ } else {
+ callback && callback();
+ }
+ });
+ }, storage.errorCallback);
+}
+
+function migrateIDB(uid, DB, tx) {
+ var adStore = tx.objectStore(Ad.objectStoreName());
+ adStore.createIndex(
+ 'account_id', // index name
+ 'account_id', // keyPath
+ { unique: false } // unique index
+ );
+ // this was hard, wasn't it?
+ // God, WebSQL is idiotic.
+}
+
+exports.migrateWebSql = migrateWebSql;
+exports.migrateIDB = migrateIDB;
View
3 ads/controller/migrateDB/textIDs.js
@@ -22,6 +22,9 @@
*
*/
+var fun = require("../../../uki-core/function");
+var storage = require("../../../storage/storage");
+
function migrate(uid, DB, callback) {
DB.transaction(function(tx) {
tx.executeSql('SELECT id FROM account LIMIT 1', [],
View
5 ads/controller/mutator.js
@@ -58,7 +58,9 @@ Mutator.initAdInCampaign = function(camp, ad) {
ad.muteChanges(false);
};
+var FLOW_CREATE_CAMPAIGN = 'create_campaign';
Mutator.createCampaignHandler = function() {
+ require("../lib/loggingState").startFlow(FLOW_CREATE_CAMPAIGN);
Mutator.createCampaign(function(campCreated) {
if (campCreated) {
campCreated.validateAll().store(function() {
@@ -66,6 +68,7 @@ Mutator.createCampaignHandler = function() {
App.reload();
});
}
+ require("../lib/loggingState").endFlow(FLOW_CREATE_CAMPAIGN);
});
};
@@ -129,7 +132,7 @@ Mutator.selectToplineDialog = function() {
{ view: 'DialogFooter', childViews: [
{ view: 'Button', label: tx('sh:ok-button'), large: true, as: 'ok',
use: 'confirm' },
- { view: 'Button', label: tx('sh:confirm-button'), large: true,
+ { view: 'Button', label: tx('sh:cancel-button'), large: true,
on: { click: function() {
Mutator.selectToplineDialog().visible(false);
} } }
View
2 ads/controller/revert.js
@@ -70,8 +70,10 @@ Revert.revertAdsHandler = function() {
};
Revert.revertCampsHandler = function() {
+ require("../lib/loggingState").startFlow('revert');
revert('campPane');
App.reload();
+ require("../lib/loggingState").endFlow('revert');
};
View
27 ads/controller/upload.js
@@ -28,7 +28,8 @@ var view = require("../../uki-core/view"),
build = require("../../uki-core/builder").build,
asyncUtils = require("../../lib/async"),
- FB = require("../../lib/connect").FBWithErrors,
+ adsConnect = require("../../lib/connect"),
+ FB = adsConnect.FB,
graphlink = require("../../lib/graphlink").gl,
pathUtils = require("../../lib/pathUtils"),
@@ -114,8 +115,8 @@ function start() {
for (var i = 0; i < changedCamps.length; i++) {
var c = changedCamps[i];
- if (c.isCorporate() && !c.isFromTopline()) {
- // corp account's campaign needs to be attached to a topline
+ if (c.hasContract() && !c.isFromTopline()) {
+ // IO-backed account's campaign needs to be attached to a topline
var message = 'Campaigns of ' + c.account().name() +
' must belong to a line. Please try to ' +
'select a line for the campaign.';
@@ -187,10 +188,10 @@ function uploadCampsResponse(camp, camps, callback, result) {
Upload.uploaded++;
updateProgress();
// find errors
- if (result.error) {
+ if (adsConnect.isError(result)) {
var data = {
name: camp.name(),
- message: result.error.message || ''
+ message: adsConnect.getErrorMessage(result).msg || ''
};
var message = camp.isNew() ?
@@ -201,7 +202,7 @@ function uploadCampsResponse(camp, camps, callback, result) {
next();
} else {
// remove old camp before creating/updating new one
- camp.remove(function() {
+ camp.removeSelf(function() {
graphlink.fetchObject(
'/' + (result.id || camp.id()),
{},
@@ -274,12 +275,13 @@ function uploadImages(ads, callback) {
'POST', {
bytes: image.url().split(',')[1]
}, function(result) {
- if (result.error || !result.images || !result.images.bytes) {
+ if (adsConnect.isError(result) ||
+ !result.images || !result.images.bytes) {
// if image uploading failed, fail the whole upload
dialog.logError(tx(
'ads:pe:upload-fail-image', {
name: ads[0].name(),
- message: result.error ? result.error.message : ''
+ message: adsConnect.getErrorMessage(result).msg || ''
}));
complete();
return;
@@ -316,7 +318,10 @@ function uploadImages(ads, callback) {
function startAds(camps) {
models.Ad.findAllBy('campaign_id', utils.pluck(camps, 'id'), function(ads) {
ads = ads.filter(function(a) {
- return a.isChanged() && !a.hasErrors();
+ // only upload new ads without errors in real campaigns. if campaign
+ // creation failed, don't upload the associated ads.
+ return a.campaign_id() > 0 && a.isChanged() &&
+ (!a.hasErrors() || a.isDeleted());
});
Upload.ads = ads.length;
@@ -372,10 +377,10 @@ function uploadAdsResponse(ad, ads, originalAds, result) {
Upload.uploaded++;
updateProgress();
- if (result.error) {
+ if (adsConnect.isError(result)) {
var data = {
name: ad.name(),
- message: result.error.message || ''
+ message: adsConnect.getErrorMessage(result).msg || ''
};
var message = ad.isNew() ?
View
207 ads/db.js
@@ -25,16 +25,50 @@
*/
var fun = require("../uki-core/function"),
+ utils = require("../uki-core/utils"),
- storage = require("../storage/storage");
+ storage = require("../storage/storage"),
+ models = require("./models");
-var IDB_VERSION = '1.1';
+/**
+ * YOU HAVE TO CHANGE THE DB_VERSION IF YOU CHANGE THE SCHEMA
+ * AND WANT TO RUN A MIGRATION
+ *
+ * , A {}
+ * / \, | , .--.
+ *| =|= > /.--.\
+ * \ /` | ` |====|
+ * ` | |`::`|
+ * | .-;`\..../`;-.
+ * /\\/ / |...::...| \
+ * |:'\ | /'''::'''\ |
+ * \ /\;-,/\ :: /\--;
+ * |\ <` > >._::_.<,<__>
+ * | `""` / ^^ \| |
+ * | | |\::/
+ * | | |/|||
+ * | |___/\___| '''
+ * | \_ || _/
+ * | <_ >< _>
+ * | | || |
+ * | | || |
+ * | _\.:||:./_
+ * | /____/\____\
+ *
+ * STUFF WILL BREAK IF YOU DON'T. IRREPARABLY. I'M NOT JOKING
+ *
+ */
+
+var DB_VERSION = '1.2';
var DB;
var init, drop;
if (storage.impl === 'IndexedDB') {
+// IndexedDB docs: https://developer.mozilla.org/en/IndexedDB
+// w3c spec: http://www.w3.org/TR/IndexedDB/
+
init = function(uid, callback) {
var req = global.IndexedDB.open(
uid + '_powereditor',
@@ -42,54 +76,59 @@ if (storage.impl === 'IndexedDB') {
req.onsuccess = function(e) {
DB = e.result || e.target.result;
- var models = require("./models");
- [models.Account, models.Ad, models.Image, models.AdStat,
- models.Campaign, models.CampStat, models.Contract,
- models.Topline, models.ConnectedObject,
- models.BCT].forEach(function(m) {
- m.db(DB);
+ utils.forEach(models, function(model) {
+ model.db(DB);
});
- if (DB.version != IDB_VERSION) {
- DB.setVersion(IDB_VERSION).onsuccess = function() {
- req.result.oncomplete = callback;
- models.Account.dbInit();
- models.Ad.dbInit();
- models.Image.dbInit();
- models.AdStat.dbInit();
- models.Campaign.dbInit();
- models.CampStat.dbInit();
- models.Contract.dbInit();
- models.Topline.dbInit();
- models.ConnectedObject.dbInit();
- models.BCT.dbInit();
- require("./controller/migrateDB/removeCompletions")
- .migrateIndexDB(uid, DB);
+ if (DB.version != DB_VERSION) {
+ var oldVersion = DB.version,
+ changeReq = DB.setVersion(DB_VERSION);
+ changeReq.onerror = storage.errorCallback;
+ changeReq.onsuccess = function() {
+ // tx is a non-blocking transaction to the DB with VERSION_CHANGE
+ // priviledges to change objectStores and indices
+ var tx = changeReq.result;
+ switch (oldVersion) {
+ case '':
+ case '0.0':
+ // if it's a new or dropped db
+ utils.forEach(models, function(model) {
+ model.dbInit();
+ });
+ break;
+ case '1.0':
+ // TODO remove
+ require("./controller/migrateDB/removeCompletions")
+ .migrateIndexDB(uid, DB);
+ // break intentionally left out
+ case '1.1':
+ require("./controller/migrateDB/adAccountIDs")
+ .migrateIDB(uid, DB, tx);
+ break;
+ default:
+ // Not exactly sure how it would get here
+ utils.forEach(models, function(model) {
+ model.dbInit();
+ });
+ }
+ tx.oncomplete = callback || fun.FT;
};
} else {
- callback();
+ callback && callback();
}
};
req.onerror = storage.errorCallback;
};
drop = function(soft, callback) {
- var models = require("./models");
var req = DB.setVersion('0.0');
req.onsuccess = function(e) {
e.target.result.oncomplete = callback;
- models.Account.dbDrop();
- models.Ad.dbDrop();
- models.Image.dbDrop();
- models.AdStat.dbDrop();
- models.Campaign.dbDrop();
- models.CampStat.dbDrop();
- models.ConnectedObject.dbDrop();
- models.Contract.dbDrop();
- models.Topline.dbDrop();
- if (!soft) {
- models.BCT.dbDrop();
- }
+ utils.forEach(models, function(model) {
+ if (model.softDrop() || !soft) {
+ model.dbDrop();
+ }
+ });
};
req.onerror = storage.errorCallback;
@@ -97,59 +136,69 @@ if (storage.impl === 'IndexedDB') {
} else {
+
+// WebSQL docs: http://www.w3.org/TR/webdatabase/#dom-opendatabase
+
init = function(uid, callback) {
+
DB = global.openDatabase(
uid + '_powereditor',
- // 'bamboo',
- '1.0',
+ '', // open with whatever version there is
'PowerEditor main storage',
- 100 * 1000 * 1000);
-
- var models = require("./models");
- [models.Account, models.Ad, models.Image, models.AdStat,
- models.Campaign, models.CampStat, models.Contract,
- models.Topline, models.ConnectedObject,
- models.BCT].forEach(function(m) {
- m.db(DB);
+ 100 * 1000 * 1000 // esimated size in bytes
+ );
+
+ utils.forEach(models, function(model) {
+ model.db(DB);
});
- // init everythign in one transaction
- DB.transaction(function(tx) {
- models.Account.withTransaction(tx, function() { this.dbInit(); });
- models.Ad.withTransaction(tx, function() { this.dbInit(); });
- models.Image.withTransaction(tx, function() { this.dbInit(); });
- models.AdStat.withTransaction(tx, function() { this.dbInit(); });
- models.Campaign.withTransaction(tx, function() { this.dbInit(); });
- models.CampStat.withTransaction(tx, function() { this.dbInit(); });
- models.ConnectedObject.withTransaction(tx, function() { this.dbInit(); });
- models.Contract.withTransaction(tx, function() { this.dbInit(); });
- models.Topline.withTransaction(tx, function() { this.dbInit(); });
- models.BCT.withTransaction(tx, function() { this.dbInit(); });
- }, storage.errorCallback, function() {
- require("./controller/migrateDB/textIDs")
- .migrate(uid, DB, function() {
- require("./controller/migrateDB/removeCompletions")
- .migrate(uid, DB, callback || fun.FT);
+ if (DB.version === '' || DB.version === '0.0') {
+ // control comes here if the db is brand, spanking new
+ // well then, create the models, and run no migrations
+ // OR, should create tables using dbInit if it was explicit drop
+ DB.changeVersion(DB.version, DB_VERSION, function(tx) {
+ utils.forEach(models, function(model) {
+ model.withTransaction(tx, function() { this.dbInit(); });
});
- });
+ }, storage.errorCallback, callback || fun.FT);
+ return;
+ }
+
+ if (DB.version !== DB_VERSION) {
+ // if we've reached here, the db existed before
+ // and user did not hit 'drop'
+ DB.changeVersion(DB.version, DB_VERSION, function(tx) {
+ // nothing to do
+ // migration should take into account assumptions of old schema
+ // ironically dbInit itself makes assumption about schema
+
+ // TODO use transaction (tx) for migration
+ // this is where migration should take place, *synchronously* in 1 tx
+ // success callback is the XXX wrong place for migration
+ }, storage.errorCallback, function() {
+ // now, we're guaranteed that it's an old valid db
+ require("./controller/migrateDB/adAccountIDs")
+ .migrateWebSql(uid, DB, function() {
+ require("./controller/migrateDB/textIDs")
+ .migrate(uid, DB, function() {
+ require("./controller/migrateDB/removeCompletions")
+ .migrate(uid, DB, callback || fun.FT);
+ });
+ });
+ });
+ } else {
+ callback && callback();
+ }
};
drop = function(soft, callback) {
- var models = require("./models");
// drop everything in one transaction
- DB.transaction(function(tx) {
- models.Account.withTransaction(tx, function() { this.dbDrop(); });
- models.Ad.withTransaction(tx, function() { this.dbDrop(); });
- models.Image.withTransaction(tx, function() { this.dbDrop(); });
- models.AdStat.withTransaction(tx, function() { this.dbDrop(); });
- models.Campaign.withTransaction(tx, function() { this.dbDrop(); });
- models.CampStat.withTransaction(tx, function() { this.dbDrop(); });
- models.ConnectedObject.withTransaction(tx, function() { this.dbDrop(); });
- models.Contract.withTransaction(tx, function() { this.dbDrop(); });
- models.Topline.withTransaction(tx, function() { this.dbDrop(); });
- if (!soft) {
- models.BCT.withTransaction(tx, function() { this.dbDrop(); });
- }
+ DB.changeVersion(DB.version, '0.0', function(tx) {
+ utils.forEach(models, function(model) {
+ if (model.softDrop() || !soft) {
+ model.withTransaction(tx, function() { this.dbDrop(); });
+ }
+ }); // utils.forEach
}, storage.errorCallback, callback || fun.FT);
};
}
View
2 ads/en_US.js
@@ -25,4 +25,4 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-module.exports = {"ads:pe:please-install-chrome":"Please install Power Editor Chrome Extension before using.","ads:pe:chrome-install-button":"Install","ads:pe:bct-needs-update":"Your Broad Category Targeting data is out of date. Updating. This may take around a minute on a slow connection.","ads:pe:select-one-campaign-for-paste":"More than one campaign selected in the left panel. Please select the one you want to paste to.","ads:pe:paste-title":"Paste Progress","ads:pe:paste-garbage-ads-error":"Pasted text does not look like ads.","ads:pe:paste-campaign-into-ads-warning":"Pasted text contains campaign data. Ignoring.","ads:pe:paste-garbage-camps-error":"Pasted text does not look like campaign.","ads:pe:select-one-campaign-for-create":"More than one campaign selected in the left panel. Please select the one you want to create ad in.","ads:pe:delivery-info-na":"N\/A (no budget)","ads:pe:campaign-name-required":"Name of campaign is required","ads:pe:start-time-before-flight-time":"Start time can not be eariler than the flight start time","ads:pe:stop-time-in-the-past":"Stop time can not be in the past","ads:pe:stop-time-after-flight-time":"Stop time can not be later than the flight end time","ads:pe:creating-camp-for-ended-topline":"Can not create campaigns for an ended topline","ads:pe:parse-not-enough-rows":"Invalid data. Not enough rows.","ads:pe:parse-invalid-header":"Invalid data. Cannot parse header.","ads:pe:import-missing-camp-update-error":"Trying to update campaign ({id}) that does not exist","ads:pe:import-ad-cannot-be-moved-error":"Ad (id: {ad_id}, name: \"{ad_name}\") cannot be moved between campaigns: from {old_id} to {new_id}","ads:pe:import-ad-cannot-be-moved-to-nowhere-error":"Ad (id: {ad_id}, name: \"{ad_name}\") cannot be moved between campaigns: from {old_id} to non-existent campaign {new_id}","ads:pe:import-missing-ad-update-error":"Trying to update ad ({id}) that does not exist","ads:pe:import-cannot-create-ad-without-camp-error":"Cannot create ad (name: \"{ad_name}\", index: {index}) without campaign id or campaign name","ads:pe:bid-type:cpc":"CPC","ads:pe:bid-type:cpm":"CPM","ads:pe:bid-type:fcpm":"FCPM","ads:pe:bid-type:multi":"Multi","ads:pe:title-or-destination-required":"Title or Destination required","ads:pe:title-limit":"Title should be less than {limit} characters long","ads:pe:body-limit":"Body should be less than {limit} characters long","ads:pe:link-or-destination-required":"Link or Destination required","ads:pe:image-required":"Image required","ads:pe:unowned-content":"You do not own this facebook content","ads:pe:countries-required":"Countries required","ads:pe:creative-type:standard":"Standard","ads:pe:creative-type:fan":"Fan","ads:pe:creative-type:rsvp":"RSVP","ads:pe:creative-type:platform-context":"Platform Context","ads:pe:creative-type:social-poll":"Social Poll","ads:pe:creative-type:comment":"Comment","ads:pe:creative-type:sample":"Sample","ads:pe:creative-type:bass-platform-story":"Bass Platform Story","ads:pe:creative-type:bass-page-connections":"Bass Page Connections","ads:pe:creative-type:bass-page-checkins":"Bass Page Checkins","ads:pe:creative-type:bass-page-posts":"Bass Page Posts","ads:pe:creative-type:premium-standard":"Premium Standard","ads:pe:creative-type:premium-fan":"Premium Fan","ads:pe:creative-type:premium-event":"Premium Event","ads:pe:creative-type:premium-platform":"Premium Platform","ads:pe:creative-type:bass-app-connection":"Bass App Connection","ads:pe:creative-type:bass-engagement":"Bass Engagement","ads:pe:creative-type:bass-buy-with-friend":"Bass Buy With Friend","ads:pe:creative-type:bass-link-share":"Bass Link Share","ads:pe:creative-type:bass-questions-vote":"Bass Questions Vote","ads:pe:creative-type:questions":"Questions","ads:pe:creative-type:bass-sparkbox":"Bass Sparkbox","ads:pe:creative-type:bass-sparkbox-comment":"Bass Sparkbox Comment","ads:pe:creative-type:page-endorsement":"Page Endorsement","ads:pe:creative-type:invalid":"Invalid","ads:pe:revert-nothing-selected":"Nothing to revert.","ads:pe:no-ads-to-duplicate":"Please select ads to duplicate.","ads:pe:no-camps-to-duplicate":"Please select camps to duplicate.","ads:pe:select-line-number-option":"Select line number.","ads:pe:select-line-number-title":"Select target line number","ads:pe:select-line-number-text":"Please select the line for the campaign.","sh:ok-button":"Okay","sh:confirm-button":"Confirm","ads:pe:import-nonts-error":"Please select file with tab separated data.","ads:pe:import-nozip-error":"Please select zip file with images.","ads:pe:import-zip-upload-message":"Sending zip file to the server to unpack.","ads:pe:import-parsets-message":"Parsing tab separated text - {percent}.","ads:pe:import-nocampaigns-error":"Data does not contain campaigns info.","ads:pe:import-create-camps-message":"Creating campaigns - {percent}.","ads:pe:import-create-ads-message":"Creating ads - {percent}.","ads:pe:import-finished-message":"Finished importing","ads:pe:import-title":"Importing","ads:pe:upload-fail-create-camp":"Failed to create campaign \"{name}\": {message}","ads:pe:upload-fail-update-camp":"Failed to update campaign \"{name}\": {message}","ads:pe:upload-fail-download-updates-camp":"Failed to download campaign updates for \"{name}\"","ads:pe:upload-fail-image":"Failed to update ad \"{name}\": {message}","ads:pe:upload-fail-create-ad":"Failed to create ad \"{name}\": {message}","ads:pe:upload-fail-update-ad":"Failed to update ad \"{name}\": {message}","ads:pe:upload-fail-download-updates-ad":"Failed to download ad updates for \"{name}\""};
+module.exports = {"ads:pe:please-install-chrome":"Please install Power Editor Chrome Extension before using.","ads:pe:chrome-install-button":"Install","ads:pe:select-one-campaign-for-paste":"More than one campaign selected in the left panel. Please select the one you want to paste to.","ads:pe:paste-title":"Paste Progress","ads:pe:paste-garbage-ads-error":"Pasted text does not look like ads.","ads:pe:paste-campaign-into-ads-warning":"Pasted text contains campaign data. Ignoring.","ads:pe:paste-garbage-camps-error":"Pasted text does not look like campaign.","ads:pe:select-one-campaign-for-create":"More than one campaign selected in the left panel. Please select the one you want to create ad in.","ads:pe:dragon:line-price-na":"No price. Cannot calculate unallocated imps.","ads:pe:delivery-info-na":"N\/A (no budget)","ads:pe:dragon:campaign-budget-over-line-budget":"Total campaign budget [{camp_budget}] is over the topline budget [{line_budget}]","ads:pe:dragon:over-delivery-info":"Over [{delivered_perc}] [{completed_perc}] completed","ads:pe:dragon:under-delivery-info":"Under [{delivered_perc}] [{completed_perc}] completed","ads:pe:campaign-name-required":"Name of campaign is required","ads:pe:start-time-before-flight-time":"Start time can not be earlier than the flight start time","ads:pe:stop-time-in-the-past":"Stop time can not be in the past","ads:pe:stop-time-after-flight-time":"Stop time can not be later than the flight end time","ads:pe:creating-camp-for-ended-topline":"Can not create campaigns for an ended topline","ads:pe:validate-tokenizer-header":"Validating tokens","ads:pe:validate-tokenizer":"Please wait while your input is being validated.","ads:pe:validate-dismiss":"Dismiss","cs:require-login-title":"Connect with Facebook to Continue","ads:pe:dialog-logged-out":"You have signed out of Facebook, or your session has expired. Please refresh this page and login to continue using the Powereditor.","ads:pe:dragon:contract-na":"No contract to sync.","ads:pe:parse-not-enough-rows":"Invalid data. Not enough rows.","ads:pe:parse-invalid-header":"Invalid data. Cannot parse header.","ads:pe:import-missing-camp-update-error":"Trying to update campaign ({id}) that does not exist","ads:pe:import-ad-cannot-be-moved-error":"Ad (id: {ad_id}, name: \"{ad_name}\") cannot be moved between campaigns: from {old_id} to {new_id}","ads:pe:import-ad-cannot-be-moved-to-nowhere-error":"Ad (id: {ad_id}, name: \"{ad_name}\") cannot be moved between campaigns: from {old_id} to non-existent campaign {new_id}","ads:pe:import-missing-ad-update-error":"Trying to update ad ({id}) that does not exist","ads:pe:import-cannot-create-ad-without-camp-error":"Cannot create ad (name: \"{ad_name}\", index: {index}) without campaign id or campaign name","ads:pe:fbrequestfail":"Facebook API request failed","ads:pe:bid-type:cpc":"CPC","ads:pe:bid-type:cpm":"CPM","ads:pe:bid-type:fcpm":"FCPM","ads:pe:bid-type:multi":"Multi","ads:pe:creative-type:standard":"Standard","ads:pe:creative-type:fan":"Fan","ads:pe:creative-type:rsvp":"RSVP","ads:pe:creative-type:platform-context":"Platform Context","ads:pe:creative-type:social-poll":"Social Poll","ads:pe:creative-type:comment":"Comment","ads:pe:creative-type:sample":"Sample","ads:pe:creative-type:bass-platform-story":"App Share Story","ads:pe:creative-type:bass-page-connections":"Page Like Story","ads:pe:creative-type:bass-page-checkins":"Check-In Story","ads:pe:creative-type:bass-page-posts":"Sponsored Page Post","ads:pe:creative-type:premium-standard":"Premium Standard","ads:pe:creative-type:premium-fan":"Premium Fan","ads:pe:creative-type:premium-event":"Premium Event","ads:pe:creative-type:premium-platform":"Premium Platform","ads:pe:creative-type:bass-app-connection":"App Used Story","ads:pe:creative-type:bass-engagement":"Page Post Like Story","ads:pe:creative-type:bass-buy-with-friend":"Buy with Friends Story","ads:pe:creative-type:bass-link-share":"Domain Story","ads:pe:creative-type:bass-questions-vote":"Questions Vote Story","ads:pe:creative-type:questions":"Questions","ads:pe:creative-type:bass-sparkbox":"Comment","ads:pe:creative-type:bass-sparkbox-comment":"Comment Story","ads:pe:creative-type:page-endorsement":"Page Endorsement","ads:pe:creative-type:query-based":"Query Based","ads:pe:creative-type:ego-house-ad":"Ego House Ad","ads:pe:creative-type:page-posts-pinned":"Page Posts Pinned","ads:pe:creative-type:invalid":"Invalid","ads:pe:title-or-destination-required":"Title or Destination required","ads:pe:title-limit":"Title should be less than {limit} characters long","ads:pe:body-limit":"Body should be less than {limit} characters long","ads:pe:link-or-destination-required":"Link or Destination required","ads:pe:image-required":"Image required","ads:pe:unowned-content":"You do not own this Facebook content","ads:pe:countries-required":"Countries required","ads:pe:revert-nothing-selected":"Nothing to revert.","ads:pe:no-ads-to-duplicate":"Please select ads to duplicate.","ads:pe:no-camps-to-duplicate":"Please select camps to duplicate.","ads:pe:select-line-number-option":"Select line number.","ads:pe:select-line-number-title":"Select target line number","ads:pe:select-line-number-text":"Please select the line for the campaign.","sh:ok-button":"Okay","sh:cancel-button":"Cancel","ads:pe:import-nonts-error":"Please select file with tab separated data.","ads:pe:import-nozip-error":"Please select zip file with images.","ads:pe:import-zip-upload-message":"Sending zip file to the server to unpack.","ads:pe:import-parsets-message":"Parsing tab separated text - {percent}.","ads:pe:import-nocampaigns-error":"Data does not contain campaigns info.","ads:pe:import-create-camps-message":"Creating campaigns - {percent}.","ads:pe:import-create-ads-message":"Creating ads - {percent}.","ads:pe:import-finished-message":"Finished importing","ads:pe:import-title":"Importing","ads:pe:upload-fail-create-camp":"Failed to create campaign \"{name}\": {message}","ads:pe:upload-fail-update-camp":"Failed to update campaign \"{name}\": {message}","ads:pe:upload-fail-download-updates-camp":"Failed to download campaign updates for \"{name}\"","ads:pe:upload-fail-image":"Failed to update ad \"{name}\": {message}","ads:pe:upload-fail-create-ad":"Failed to create ad \"{name}\": {message}","ads:pe:upload-fail-update-ad":"Failed to update ad \"{name}\": {message}","ads:pe:upload-fail-download-updates-ad":"Failed to download ad updates for \"{name}\"","ads:pe:pit-placeholder":"Enter an interest","ads:pe:pit-suggestions":"Suggested Likes & Interests","ads:pe:pit-refresh":"Refresh Suggested Interests","ads:pe:bct-needs-update":"Broad Category Targeting data is out of date. Updating."};
View
5 ads/job/adImporter.js
@@ -32,7 +32,8 @@ var Campaign = require("../model/campaign").Campaign;
var Ad = require("../model/ad").Ad;
var AdError = require("../lib/error").Error;
-var Importer = fun.newClass(Job, {
+var Importer = fun.newClass(Job,
+ require("../lib/loggingState").getMixinForJob('campaign_importer'), {
// in params
account: fun.newProp('account'),
@@ -299,7 +300,7 @@ var Importer = fun.newClass(Job, {
if (updated) {
existingAd
- .resetCampaign()
+ .updateCampaign()
.validateAll()
.store(callback);
} else {
View
19 ads/job/campImporter.js
@@ -34,7 +34,8 @@ var AdError = require("../lib/error").Error;
var AdImporter = require("./adImporter").Importer;
-var Importer = fun.newClass(Job, {
+var Importer = fun.newClass(Job,
+ require("../lib/loggingState").getMixinForJob('campaign_importer'), {
// in params
account: fun.newProp('account'),
@@ -233,7 +234,21 @@ var Importer = fun.newClass(Job, {
if (newCamp.line_number()) {
var line_id =
Topline.getIdbyLineNumber(newCamp.account_id(), newCamp.line_number());
- newCamp.line_id(line_id);
+
+ if (line_id) {
+ newCamp.line_id(line_id);
+ // adjusted the camp end_time to the end of last minute
+ // of topline flight end date when the new camp end_time
+ // is later than the topline flight end date.
+ var shifted_flight_end_time =
+ Topline.byId(line_id).shifted_flight_end_date();
+ if (newCamp.adjusted_end_time() > shifted_flight_end_time) {
+ newCamp.adjusted_end_time(shifted_flight_end_time);
+ }
+ } else {
+ // reset the line_number to be empty since the line_id is not found
+ newCamp.line_number('');
+ }
}
if (!this.useNameMatching()) {
newCamp.name(uniqName(newCamp.name(), this.mapByName()));
View
6 ads/job/downloadBCT.js
@@ -28,10 +28,12 @@ var Account = require("../model/account").Account;
var BCT = require("../model/bct").BCT;
var Job = require("./base").Job;
-var REFRESH_TIMEOUT = 86400 * 7; // once per week
+var REFRESH_TIMEOUT = 0; // always download
var CACHE_KEY = 'model:bct:1';
-var DownloadBCT = fun.newClass(Job, {
+var DownloadBCT = fun.newClass(Job,
+ require("../lib/loggingState").getMixinForJob('campaign_importer'), {
+
userStorage: fun.newProp('userStorage'),
init: function() {
Job.prototype.init.call(this);
View
3 ads/job/tabSeparatedParser.js
@@ -98,7 +98,8 @@ function mapProps(props, header) {
return { found: found, missed: missed };
}
-var Parser = fun.newClass(Job, {
+var Parser = fun.newClass(Job,
+ require("../lib/loggingState").getMixinForJob('campaign_importer'), {
// in params
imageLookup: fun.newProp('imageLookup'),
View
71 ads/lib/adCreativeType.js
@@ -36,23 +36,27 @@ var AD_CREATIVE_TYPE_TS = {
5: 'Social Poll',
6: 'Comment',
7: 'Sample',
- 8: 'Bass Platform Story',
- 9: 'Bass Page Connections',
- 10: 'Bass Page Checkins',
- 11: 'Bass Page Posts',
+ 8: 'App Share Story',
+ 9: 'Page Like Story',
+ 10: 'Check-In Story',
+ 11: 'Sponsored Page Post',
12: 'Premium Standard',
13: 'Premium Fan',
14: 'Premium Event',
15: 'Premium Platform',
- 16: 'Bass App Connection',
- 17: 'Bass Engagement',
- 18: 'Bass Buy With Friend',
- 19: 'Bass Link Share',
- 20: 'Bass Questions Vote',
+ 16: 'App Used Story',
+ 17: 'Page Post Like Story',
+ 18: 'Buy with Friends Story',
+ 19: 'Domain Story',
+ 20: 'Questions Vote Story',
21: 'Questions',
- 22: 'Bass Sparkbox',
- 23: 'Bass Sparkbox Comment',
+ 22: 'Comment',
+ 23: 'Comment Story',
24: 'Page Endorsement',
+ 25: 'Query Based',
+ 26: 'Ego House Ad',
+ 27: 'Page Posts Pinned',
+
999: 'Invalid'
};
@@ -81,6 +85,10 @@ var AD_CREATIVE_TYPE_MAP = {
22: tx('ads:pe:creative-type:bass-sparkbox'),
23: tx('ads:pe:creative-type:bass-sparkbox-comment'),
24: tx('ads:pe:creative-type:page-endorsement'),
+ 25: tx('ads:pe:creative-type:query-based'),
+ 26: tx('ads:pe:creative-type:ego-house-ad'),
+ 27: tx('ads:pe:creative-type:page-posts-pinned'),
+
999: tx('ads:pe:creative-type:invalid')
};
@@ -109,20 +117,34 @@ var AD_CREATIVE_TYPE = {
BASS_SPARKBOX: 22,
BASS_SPARKBOX_COMMENT: 23,
PAGE_ENDORSEMENT: 24,
+ QUERY_BASED: 25,
+ EGO_HOUSE_AD: 26,
+ PAGE_POSTS_V2: 27,
+
EXOTIC_OR_INVALID: 999
};
-var AD_CREATIVE_BASS_TYPE_MAP = {
- BASS_PLATFORM_STORY: 8,
- BASS_PAGE_CONNECTIONS: 9,
- BASS_PAGE_CHECKINS: 10,
- BASS_PAGE_POSTS: 11,
- BASS_APP_CONNECTIONS: 16,
- BASS_ENGAGEMENT: 17,
- BASS_BUY_WITH_FRIENDS: 18,
- BASS_LINK_SHARE: 19,
- BASS_QUESTIONS_VOTE: 20
-};
+var AD_CREATIVE_BASS_TYPE_ARR = [
+ 8, //BASS_PLATFORM_STORY: 8,
+ 9, //BASS_PAGE_CONNECTIONS: 9,
+ 10, //BASS_PAGE_CHECKINS: 10,
+ 11, //BASS_PAGE_POSTS: 11,
+ 16, //BASS_APP_CONNECTIONS: 16,
+ 17, //BASS_ENGAGEMENT: 17,
+ 18, //BASS_BUY_WITH_FRIENDS: 18,
+ 19, //BASS_LINK_SHARE: 19,
+ 20, //BASS_QUESTIONS_VOTE: 20,
+ // BASS_SPARKBOX is a very misleading name
+ // since it is not a bass type ad.
+ 23, //BASS_SPARKBOX_COMMENT: 23,
+ 25 //QUERY_BASED: 25
+];
+
+var AD_NO_TITLE_SS_ARR = [
+ 2, // INLINE_FAN
+ 3, // INLINE_RSVP
+ 4 // PLATFORM_CONTEXT
+];
function id2string(id) {
return AD_CREATIVE_TYPE_TS[id] || AD_CREATIVE_TYPE_TS[1];
@@ -145,7 +167,7 @@ function string2id(string) {
}
function is_bass_type(creative_type) {
- return AD_CREATIVE_BASS_TYPE_MAP[creative_type];
+ return AD_CREATIVE_BASS_TYPE_ARR.indexOf(1 * creative_type) != -1;
};
function getCategoryByCreativeType(creative_type) {
@@ -165,7 +187,7 @@ function getDefaultCreativeTypeByAnchor(
AD_CREATIVE_TYPE.STANDARD;
var type = require("../model/connectedObject").OBJECT_TYPE;
- if (premium) {
+ if (premium && !bass) {
if (anchor_type == type.EXTERNAL_WEBPAGE) {
default_type = AD_CREATIVE_TYPE.PREMIUM_STANDARD;
return;
@@ -237,5 +259,6 @@ exports.getCategoryByCreativeType = getCategoryByCreativeType;
exports.id2string = id2string;
exports.string2id = string2id;
exports.AD_CREATIVE_TYPE = AD_CREATIVE_TYPE;
+exports.AD_NO_TITLE_SS_ARR = AD_NO_TITLE_SS_ARR;
exports.AD_CREATIVE_TYPE_MAP = AD_CREATIVE_TYPE_MAP;
exports.AD_CREATIVE_CATEGORY = AD_CREATIVE_CATEGORY;
View
8 ads/lib/completions.js
@@ -22,6 +22,7 @@
*
*/
+var Graphlink = require("../../lib/graphlink").Graphlink;
/**
* Search in countires and locales
@@ -81,7 +82,7 @@ var BEST_MATCH_LIMIT = 7;
exports.findBest = function(type, query, callback) {
var key = type + ':' + query;
- var options = { type: 'ad' + type, q: query, limit: BEST_MATCH_LIMIT };
+ var options = { q: query, limit: BEST_MATCH_LIMIT };
if (type == 'country') {
callback(
bestMatch(query, this.searchCountries(query, BEST_MATCH_LIMIT)));
@@ -104,9 +105,8 @@ exports.findBest = function(type, query, callback) {
var log = exports.dialog.visible(true).append(
{ view: 'Text', text: 'Looking for ' + type + ' "' + options.q + '"' });
}
- require("../../lib/connect").FB.api(
- '/search',
- options,
+ var graphlink = new Graphlink();
+ graphlink.querysearch('ad' + type, options,
function(r) {
cache[key] = bestMatch(options.q, r.data || []);
if (log) {
View
2 ads/lib/countries.js
@@ -28,7 +28,7 @@
*/
var countries =
-{"US":"United States","CA":"Canada","GB":"United Kingdom","AR":"Argentina","AU":"Australia","AT":"Austria","BE":"Belgium","BR":"Brazil","CL":"Chile","CN":"China","CO":"Colombia","HR":"Croatia","DK":"Denmark","DO":"Dominican Republic","EG":"Egypt","FI":"Finland","FR":"France","DE":"Germany","GR":"Greece","HK":"Hong Kong","IN":"India","ID":"Indonesia","IE":"Ireland","IL":"Israel","IT":"Italy","JP":"Japan","JO":"Jordan","KW":"Kuwait","LB":"Lebanon","MY":"Malaysia","MX":"Mexico","NL":"Netherlands","NZ":"New Zealand","NG":"Nigeria","NO":"Norway","PK":"Pakistan","PA":"Panama","PE":"Peru","PH":"Philippines","PL":"Poland","RU":"Russia","SA":"Saudi Arabia","RS":"Serbia","SG":"Singapore","ZA":"South Africa","KR":"South Korea","ES":"Spain","SE":"Sweden","CH":"Switzerland","TW":"Taiwan","TH":"Thailand","TR":"Turkey","AE":"United Arab Emirates","VE":"Venezuela","PT":"Portugal","LU":"Luxembourg","BG":"Bulgaria","CZ":"Czech Republic","SI":"Slovenia","IS":"Iceland","SK":"Slovakia","LT":"Lithuania","TT":"Trinidad and Tobago","BD":"Bangladesh","LK":"Sri Lanka","KE":"Kenya","HU":"Hungary","MA":"Morocco","CY":"Cyprus","JM":"Jamaica","EC":"Ecuador","RO":"Romania","BO":"Bolivia","GT":"Guatemala","CR":"Costa Rica","QA":"Qatar","SV":"El Salvador","HN":"Honduras","NI":"Nicaragua","PY":"Paraguay","UY":"Uruguay","PR":"Puerto Rico","BA":"Bosnia and Herzegovina","PS":"Palestine","TN":"Tunisia","BH":"Bahrain","VN":"Vietnam","GH":"Ghana","MU":"Mauritius","UA":"Ukraine","MT":"Malta","BS":"The Bahamas","MV":"Maldives","OM":"Oman","MK":"Macedonia","LV":"Latvia","EE":"Estonia","IQ":"Iraq","DZ":"Algeria","AL":"Albania","NP":"Nepal","MO":"Macau","ME":"Montenegro","SN":"Senegal","GE":"Georgia","BN":"Brunei","UG":"Uganda","GP":"Guadeloupe","BB":"Barbados","AZ":"Azerbaijan","TZ":"Tanzania","LY":"Libya","MQ":"Martinique","CM":"Cameroon","BW":"Botswana","ET":"Ethiopia","KZ":"Kazakhstan","NA":"Namibia","AN":null,"MG":"Madagascar","NC":"New Caledonia","MD":"Moldova","FJ":"Fiji","BY":"Belarus","JE":"Jersey","GU":"Guam","YE":"Yemen","ZM":"Zambia","IM":"Isle Of Man","HT":"Haiti","KH":"Cambodia","AW":"Aruba","PF":"French Polynesia","AF":"Afghanistan","BM":"Bermuda","GY":"Guyana","AM":"Armenia","MW":"Malawi","AG":"Antigua","RW":"Rwanda","GG":"Guernsey","GM":"The Gambia","FO":"Faroe Islands","LC":"St. Lucia","KY":"Cayman Islands","BJ":"Benin","AD":"Andorra","GD":"Grenada","VI":"US Virgin Islands","BZ":"Belize","VC":"Saint Vincent and the Grenadines","MN":"Mongolia","MZ":"Mozambique","ML":"Mali","AO":"Angola","GF":"French Guiana","UZ":"Uzbekistan","DJ":"Djibouti","BF":"Burkina Faso","MC":"Monaco","TG":"Togo","GL":"Greenland","GA":"Gabon","GI":"Gibraltar","CD":"Democratic Republic Congo","KG":"Kyrgyzstan","PG":"Papua New Guinea","BT":"Bhutan","KN":"Saint Kitts and Nevis","SZ":"Swaziland","LS":"Lesotho","LA":"Laos","LI":"Liechtenstein","MP":"Northern Mariana Islands","SR":"Suriname","SC":"Seychelles","VG":"British Virgin Islands","TC":"Turks and Caicos Islands","DM":"Dominica","MR":"Mauritania","AX":"Aland Islands","SM":"San Marino","SL":"Sierra Leone","NE":"Niger","CG":"Republic of the Congo","AI":"Anguilla","YT":"Mayotte","CV":"Cape Verde","GN":"Guinea","TM":"Turkmenistan","BI":"Burundi","TJ":"Tajikistan","VU":"Vanuatu","SB":"Solomon Islands","ER":"Eritrea","WS":"Samoa","AS":"American Samoa","FK":"Falkland Islands","GQ":"Equatorial Guinea","TO":"Tonga","KM":"Comoros","PW":"Palau","FM":"Federated States of Micronesia","CF":"Central African Republic","SO":"Somalia","MH":"Marshall Islands","VA":"Vatican City","TD":"Chad","KI":"Kiribati","ST":"Sao Tome and Principe","TV":"Tuvalu","NR":"Nauru","RE":"R\u00e9union"};
+{"US":"United States","CA":"Canada","GB":"United Kingdom","AR":"Argentina","AU":"Australia","AT":"Austria","BE":"Belgium","BR":"Brazil","CL":"Chile","CN":"China","CO":"Colombia","HR":"Croatia","DK":"Denmark","DO":"Dominican Republic","EG":"Egypt","FI":"Finland","FR":"France","DE":"Germany","GR":"Greece","HK":"Hong Kong","IN":"India","ID":"Indonesia","IE":"Ireland","IL":"Israel","IT":"Italy","JP":"Japan","JO":"Jordan","KW":"Kuwait","LB":"Lebanon","MY":"Malaysia","MX":"Mexico","NL":"Netherlands","NZ":"New Zealand","NG":"Nigeria","NO":"Norway","PK":"Pakistan","PA":"Panama","PE":"Peru","PH":"Philippines","PL":"Poland","RU":"Russia","SA":"Saudi Arabia","RS":"Serbia","SG":"Singapore","ZA":"South Africa","KR":"South Korea","ES":"Spain","SE":"Sweden","CH":"Switzerland","TW":"Taiwan","TH":"Thailand","TR":"Turkey","AE":"United Arab Emirates","VE":"Venezuela","PT":"Portugal","LU":"Luxembourg","BG":"Bulgaria","CZ":"Czech Republic","SI":"Slovenia","IS":"Iceland","SK":"Slovakia","LT":"Lithuania","TT":"Trinidad and Tobago","BD":"Bangladesh","LK":"Sri Lanka","KE":"Kenya","HU":"Hungary","MA":"Morocco","CY":"Cyprus","JM":"Jamaica","EC":"Ecuador","RO":"Romania","BO":"Bolivia","GT":"Guatemala","CR":"Costa Rica","QA":"Qatar","SV":"El Salvador","HN":"Honduras","NI":"Nicaragua","PY":"Paraguay","UY":"Uruguay","PR":"Puerto Rico","BA":"Bosnia and Herzegovina","PS":"Palestine","TN":"Tunisia","BH":"Bahrain","VN":"Vietnam","GH":"Ghana","MU":"Mauritius","UA":"Ukraine","MT":"Malta","BS":"The Bahamas","MV":"Maldives","OM":"Oman","MK":"Macedonia","LV":"Latvia","EE":"Estonia","IQ":"Iraq","DZ":"Algeria","AL":"Albania","NP":"Nepal","MO":"Macau","ME":"Montenegro","SN":"Senegal","GE":"Georgia","BN":"Brunei","UG":"Uganda","GP":"Guadeloupe","BB":"Barbados","AZ":"Azerbaijan","TZ":"Tanzania","LY":"Libya","MQ":"Martinique","CM":"Cameroon","BW":"Botswana","ET":"Ethiopia","KZ":"Kazakhstan","NA":"Namibia","MG":"Madagascar","NC":"New Caledonia","MD":"Moldova","FJ":"Fiji","BY":"Belarus","JE":"Jersey","GU":"Guam","YE":"Yemen","ZM":"Zambia","IM":"Isle Of Man","HT":"Haiti","KH":"Cambodia","AW":"Aruba","PF":"French Polynesia","AF":"Afghanistan","BM":"Bermuda","GY":"Guyana","AM":"Armenia","MW":"Malawi","AG":"Antigua","RW":"Rwanda","GG":"Guernsey","GM":"The Gambia","FO":"Faroe Islands","LC":"St. Lucia","KY":"Cayman Islands","BJ":"Benin","AD":"Andorra","GD":"Grenada","VI":"US Virgin Islands","BZ":"Belize","VC":"Saint Vincent and the Grenadines","MN":"Mongolia","MZ":"Mozambique","ML":"Mali","AO":"Angola","GF":"French Guiana","UZ":"Uzbekistan","DJ":"Djibouti","BF":"Burkina Faso","MC":"Monaco","TG":"Togo","GL":"Greenland","GA":"Gabon","GI":"Gibraltar","CD":"Democratic Republic Congo","KG":"Kyrgyzstan","PG":"Papua New Guinea","BT":"Bhutan","KN":"Saint Kitts and Nevis","SZ":"Swaziland","LS":"Lesotho","LA":"Laos","LI":"Liechtenstein","MP":"Northern Mariana Islands","SR":"Suriname","SC":"Seychelles","VG":"British Virgin Islands","TC":"Turks and Caicos Islands","DM":"Dominica","MR":"Mauritania","AX":"Aland Islands","SM":"San Marino","SL":"Sierra Leone","NE":"Niger","CG":"Republic of the Congo","AI":"Anguilla","YT":"Mayotte","CV":"Cape Verde","GN":"Guinea","TM":"Turkmenistan","BI":"Burundi","TJ":"Tajikistan","VU":"Vanuatu","SB":"Solomon Islands","ER":"Eritrea","WS":"Samoa","AS":"American Samoa","FK":"Falkland Islands","GQ":"Equatorial Guinea","TO":"Tonga","KM":"Comoros","PW":"Palau","FM":"Federated States of Micronesia","CF":"Central African Republic","SO":"Somalia","MH":"Marshall Islands","VA":"Vatican City","TD":"Chad","KI":"Kiribati","ST":"Sao Tome and Principe","TV":"Tuvalu","NR":"Nauru","RE":"R\u00e9union"};
var countriesWithRegions =
["US","CA","AU","GB"];
var countriesWithCities =
View
20 ads/lib/creativeMap.js
@@ -20,20 +20,23 @@
* DEALINGS IN THE SOFTWARE.
*
*
+* This file is automatically generated by
+* scripts/admanager/powereditor/generate_js/creative_map.php
+* do not modify
+*
*/
-
var creativeTypesByAnchor_map =
-{"external_webpage":{"facebook_ads":{"standard":[1],"premium":[12]}},"1":{"facebook_ads":{"standard":[2],"premium":[5,6,7,13,21,22,24]},"sponsored_stories":{"standard":[9,11,17],"premium":[20,23]}},"4":{"facebook_ads":{"standard":[2]}},"5":{"facebook_ads":{"standard":[2]},"sponsored_stories":{"standard":[10]}},"6":{"facebook_ads":{"standard":[2],"premium":[5,6,13,21,22,24]},"sponsored_stories":{"standard":[9,10,11,17],"premium":[20,23]}},"3":{"facebook_ads":{"standard":[3],"premium":[14]}},"2":{"facebook_ads":{"standard":[4],"premium":[15]},"sponsored_stories":{"standard":[16,8,18]}},"7":{"sponsored_stories":{"standard":[19]}}};
+{"external_webpage":{"facebook_ads":{"standard":[1],"premium":[12,26]}},"1":{"facebook_ads":{"standard":[27,2],"premium":[5,6,7,13,22,24]},"sponsored_stories":{"standard":[9,11,17,25],"premium":[20,23]}},"6":{"facebook_ads":{"standard":[27,2],"premium":[5,6,13,22,24]},"sponsored_stories":{"standard":[9,10,11,17,25],"premium":[20,23]}},"5":{"facebook_ads":{"standard":[2]},"sponsored_stories":{"standard":[10]}},"3":{"facebook_ads":{"standard":[3],"premium":[14]}},"2":{"facebook_ads":{"standard":[4],"premium":[15]},"sponsored_stories":{"standard":[16,8,18]}},"7":{"sponsored_stories":{"standard":[19]}}};
var fieldsByCreativeType_standard_map =
-{"1":["link_url","title","body"],"2":["title","body","link_url"],"3":["title","body"],"4":["link_url","title","body"],"5":["title","body","link_url","poll_id","poll_question","poll_answer_0","poll_answer_1","poll_answer_2","poll_publish_to_page"],"6":["title","body","link_url"],"7":["title","body","sample_id","sample_name","sample_starting_inventory","sample_target_country","sample_visible_to_friends","sample_link_text","sample_flavour_category","sample_flavour_0","sample_flavour_1","sample_flavour_2","sample_flavour_3","sample_flavour_4"],"16":["url_tags"],"8":["url_tags"],"9":[],"10":[],"11":["url_tags"],"12":["title","body","link_url"],"13":["title","body","link_url"],"14":["title","body","link_url"],"15":["title","body","link_url"],"17":[],"18":["url_tags"],"19":["url_tags"],"21":["link_url","title","body","question_id","show_creative"],"20":["question_id"],"22":["story_id","prompt"],"23":["thread_id","prompt"],"24":["liker_page_id","liked_page_id","ab_test_id"]};
+{"1":["link_url","title","body","related_fan_page"],"27":["url_tags","story_id"],"2":["title","body","link_url"],"3":["title","body"],"4":["link_url","title","body"],"5":["title","body","link_url","poll_id","poll_question","poll_answer_0","poll_answer_1","poll_answer_2","poll_publish_to_page"],"6":["title","body","link_url"],"7":["title","body","sample_id","sample_name","sample_starting_inventory","sample_target_country","sample_visible_to_friends","sample_link_text","sample_flavour_category","sample_flavour_0","sample_flavour_1","sample_flavour_2","sample_flavour_3","sample_flavour_4"],"16":["url_tags"],"8":["url_tags"],"9":[],"10":[],"11":["url_tags"],"12":["title","body","link_url"],"13":["title","body","link_url"],"14":["title","body","link_url"],"15":["title","body","link_url"],"17":[],"18":["url_tags"],"19":["url_tags"],"20":["question_id"],"22":["story_id","prompt"],"23":["thread_id","prompt"],"24":["liker_page_id","liked_page_id","ab_test_id"],"25":["action_spec"],"26":[],"28":["title","body","link_url"]};
var fieldsByCreativeType_premium_map =
-{"1":["link_url","title","body","video_id","video_hd"],"2":["title","body","link_url","video_id","video_hd"],"3":["title","body"],"4":["link_url","title","body","video_id","video_hd"],"5":["title","body","link_url","video_id","video_hd","poll_id","poll_question","poll_answer_0","poll_answer_1","poll_answer_2","poll_publish_to_page"],"6":["title","body","link_url","video_id","video_hd"],"7":["title","body","sample_id","sample_name","sample_starting_inventory","sample_target_country","sample_visible_to_friends","sample_link_text","sample_flavour_category","sample_flavour_0","sample_flavour_1","sample_flavour_2","sample_flavour_3","sample_flavour_4"],"16":["url_tags"],"8":["url_tags"],"9":[],"10":[],"11":["url_tags"],"12":["title","body","link_url","video_id","video_hd"],"13":["title","body","link_url","video_id","video_hd"],"14":["title","body","link_url","video_id","video_hd"],"15":["title","body","link_url","video_id","video_hd"],"17":[],"18":["url_tags"],"19":["url_tags"],"21":["link_url","title","body","video_id","video_hd","question_id","show_creative"],"20":["question_id"],"22":["story_id","prompt"],"23":["thread_id","prompt"],"24":["liker_page_id","liked_page_id","ab_test_id"]};
+{"1":["link_url","title","body","video_id","video_hd","related_fan_page"],"27":["url_tags","story_id"],"2":["title","body","link_url","video_id","video_hd"],"3":["title","body"],"4":["link_url","title","body","video_id","video_hd"],"5":["title","body","link_url","video_id","video_hd","poll_id","poll_question","poll_answer_0","poll_answer_1","poll_answer_2","poll_publish_to_page"],"6":["title","body","link_url","video_id","video_hd"],"7":["title","body","sample_id","sample_name","sample_starting_inventory","sample_target_country","sample_visible_to_friends","sample_link_text","sample_flavour_category","sample_flavour_0","sample_flavour_1","sample_flavour_2","sample_flavour_3","sample_flavour_4"],"16":["url_tags"],"8":["url_tags"],"9":[],"10":[],"11":["url_tags"],"12":["title","body","link_url","video_id","video_hd"],"13":["title","body","link_url","video_id","video_hd"],"14":["title","body","link_url","video_id","video_hd"],"15":["title","body","link_url","video_id","video_hd"],"17":[],"18":["url_tags"],"19":["url_tags"],"20":["question_id"],"22":["story_id","prompt"],"23":["thread_id","prompt"],"24":["liker_page_id","liked_page_id","ab_test_id"],"25":["action_spec"],"26":[],"28":["title","body","link_url","video_id","video_hd"]};
var api_fields =
-["object_id","creative_id","type","name","url_tags","link_url","title","body","image_path","image_volume","image_hash","image_url","video_id","video_hd","view_tag","alt_view_tags","poll_id","poll_question","poll_answer_0","poll_answer_1","poll_answer_2","poll_publish_to_page","question_id","show_creative","prompt","story_id","thread_id","sample_name","sample_starting_inventory","sample_target_country","sample_visible_to_friends","sample_link_text","sample_flavour_category","sample_flavour_0","sample_flavour_1","sample_flavour_2","sample_flavour_3","sample_flavour_4","sample_dialog_title","sample_dialog_description","sample_id","liker_page_id","liked_page_id","ab_test_id"];
+["object_id","creative_id","type","name","url_tags","link_url","title","body","image_path","image_volume","image_hash","image_url","related_fan_page","video_id","video_hd","view_tag","alt_view_tags","poll_id","poll_question","poll_answer_0","poll_answer_1","poll_answer_2","poll_publish_to_page","question_id","show_creative","prompt","story_id","thread_id","sample_name","sample_starting_inventory","sample_target_country","sample_visible_to_friends","sample_link_text","sample_flavour_category","sample_flavour_0","sample_flavour_1","sample_flavour_2","sample_flavour_3","sample_flavour_4","sample_dialog_title","sample_dialog_description","sample_id","liker_page_id","liked_page_id","ab_test_id","link_target","conversion_fbid","action_spec"];
// premium ==> true
var COMMON_FIELDS = {
@@ -48,14 +51,16 @@ var SUPPRESSED_CREATIVE_TYPE_MAP = {
// SAMPLE being deprecated (rarely used)
// Bass Sparkbox not support for non-bass (should be enabled after Bass)
// Page Endorsement used as a testing type for now
+ // Action Query is still under testing
7: true,
22: true,
- 24: true
+ 24: true,
+ 25: true
};
function getFieldsByType(type, is_premium) {
var fields = [];
- if (is_premium === undefined) {
+ if (!is_premium) {
// go with the standard map
fields =
fieldsByCreativeType_standard_map[type];
@@ -86,3 +91,4 @@ function getTypesByAnchor(anchor, is_bass, is_premium) {
exports.getFieldsByType = getFieldsByType;
exports.getTypesByAnchor = getTypesByAnchor;
+
View
118 ads/lib/dateUtil.js
@@ -0,0 +1,118 @@
+/**
+ * Copyright 2011 Facebook, Inc.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to
+ * use, copy, modify, and distribute this software in source code or binary
+ * form for use in connection with the web services and APIs provided by
+ * Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use
+ * of this software is subject to the Facebook Developer Principles and
+ * Policies [http://developers.facebook.com/policy/]. This copyright notice
+ * shall be included in all copies or substantial portions of the software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ *
+ */
+
+
+var DateUtil = {
+ /** PRIVATE FUNCTIONS **/
+
+
+ /**
+ * Find the actual offset of a given Unix timestamp for the account's timezone
+ *
+ * @param account to find timezone offsets for
+ * @param Unix timestamp in milliseconds
+ * @return offset from UTC in milliseconds for account timezone
+ */
+ _getActualOffset: function(account, t) {
+ if (!account || !account.timezone_offsets()) {
+ return new Date(t).getTimezoneOffset() * 1000 * 60 * -1;
+ } // if no offsets exist return local offset
+
+ var offsets = account.timezone_offsets();
+ t = parseInt(t / 1000, 10); // temporarily convert to seconds
+
+ for (var i = 0; i < offsets.length - 1; i++) {
+ if (t >= offsets[i].ts && t < offsets[i + 1].ts) {
+ return offsets[i].offset * 1000;
+ }
+ }
+
+ return offsets.length && offsets[offsets.length - 1].offset * 1000;
+ },
+
+ /**
+ * Helper function for public DateUtil functions
+ *
+ * @param account
+ * @param date
+ * @param true if adjusting local -> account
+ * false if adjusting account -> local
+ */
+ _convertOffsets: function(account, date, localToAccount) {
+ var output = new Date();
+ var actualOffset = this._getActualOffset(account, date.getTime());
+ var localOffset = date.getTimezoneOffset() * 60 * 1000 * -1;
+ var offset = localOffset - actualOffset;
+ if (!localToAccount) { offset *= -1; }
+ output.setTime(date.getTime() - offset);
+
+ return output;
+ }
+};
+
+
+/** PUBLIC FUNCTIONS **/
+
+
+
+
+/**
+ * Should be used to convert a stored Date object to display in the
+ * timezone of the given account.
+ *
+ * @param account with timezone to adjust to
+ * @param date object to adjust for correct display
+ *
+ * @return date object adjusted for correct display
+ */
+DateUtil.fromLocalToAccountOffset = function(account, time) {
+ return this._convertOffsets(account, time, true);
+};
+
+/**
+ * Should be used to convert a Date object used to display date
+ * in correct timezone back to local timezone for proper storage.
+ *
+ * @param account with timezone to adjust to
+ * @param date object to adjust for correct storage
+ *
+ * @return date object adjusted for correct storage
+ */
+DateUtil.fromAccountToLocalOffset = function(account, time) {
+ return this._convertOffsets(account, time, false);
+};
+
+/**
+ * Converts the current time to the timezone of the given account.
+ *
+ * @param account with timezone to adjust to
+ *
+ * @return date object adjusted for account timezone
+ */
+DateUtil.fromNowToAccountOffset = function(account) {
+ return this._convertOffsets(account, new Date(), true);
+};
+
+
+exports.DateUtil = DateUtil;
View
5 ads/lib/estimateTargetingStats.js
@@ -24,12 +24,15 @@
var fun = require("../../uki-core/function");
-var graphlink = require("../../lib/graphlink").gl,
+var GL = require("../../lib/graphlink").Graphlink,
pathUtils = require("../../lib/pathUtils");
var cache = {};
var CACHE_TTL = 1000 * 60 * 60 * 24; // day
+// so as not to mess with progress handlers on static version
+var graphlink = new GL();
+
function buildKey(account, targeting_spec) {
return account.id() + ' ' + JSON.stringify(targeting_spec);
}
View
129 ads/lib/fetcher.js
@@ -0,0 +1,129 @@
+/**
+* Copyright 2011 Facebook, Inc.
+*
+* You are hereby granted a non-exclusive, worldwide, royalty-free license to
+* use, copy, modify, and distribute this software in source code or binary
+* form for use in connection with the web services and APIs provided by
+* Facebook.
+*
+* As with any software that integrates with the Facebook platform, your use
+* of this software is subject to the Facebook Developer Principles and
+* Policies [http://developers.facebook.com/policy/]. This copyright notice
+* shall be included in all copies or substantial portions of the software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+* DEALINGS IN THE SOFTWARE.
+*
+*
+*/
+
+var fun = require("../../uki-core/function");
+
+var GL = require("../../lib/graphlink").Graphlink;
+
+// so as not to mess with progress handlers on static version
+var graphlink = new GL();
+
+var Fetcher = fun.newClass(
+{
+ cache: {},
+ cacheTTL: 1000 * 60 * 60 * 24, // day
+
+ buildKey: function(input_data) {