diff --git a/app/directives/challenge-tile/challenge-tile.directive.jade b/app/directives/challenge-tile/challenge-tile.directive.jade
index 278be3ff2..3ec2e022d 100644
--- a/app/directives/challenge-tile/challenge-tile.directive.jade
+++ b/app/directives/challenge-tile/challenge-tile.directive.jade
@@ -32,7 +32,8 @@
// Only show if not data science track
p.roles
span(ng-hide="challenge.track === 'DATA_SCIENCE'")
- #[span Role: ] #[span {{challenge.userDetails.roles | listRoles}}]
+ span Role:
+ span {{challenge.userDetails.roles | listRoles}}
.completed-challenge(
ng-show="challenge.status === 'COMPLETED' || challenge.status === 'PAST'",
@@ -66,7 +67,8 @@
// Only show if not data science track
p.roles
span(ng-hide="challenge.track === 'DATA_SCIENCE'")
- #[span Role: ] #[span {{challenge.userDetails.roles | listRoles}}]
+ span Role:
+ span {{challenge.userDetails.roles | listRoles}}
.challenge.list-view(ng-show="view=='list'", ng-class="challenge.track")
.active-challenge(ng-show="challenge.status === 'ACTIVE'")
diff --git a/app/directives/external-account/external-account.directive.js b/app/directives/external-account/external-account.directive.js
index 832a100d3..162f232a9 100644
--- a/app/directives/external-account/external-account.directive.js
+++ b/app/directives/external-account/external-account.directive.js
@@ -5,11 +5,10 @@
{ provider: "linkedin", className: "fa-linkedin", displayName: "LinkedIn", disabled: true, order: 5, colorClass: 'el-linkedin', featured: true},
{ provider: "stackoverflow", className: "fa-stack-overflow", displayName: "Stack Overflow", disabled: false, order: 3, colorClass: 'el-stackoverflow'},
{ provider: "behance", className: "fa-behance", displayName: "Behance", disabled: true, order: 2, colorClass: 'el-behance'},
- // { provider: "google-oauth2", className: "fa-google-plus", displayName: "Google+", disabled: true, order: }, colorClass: 'el-dribble',
{ provider: "github", className: "fa-github", displayName: "Github", disabled: false, order: 1, colorClass: 'el-github', featured: true},
{ provider: "bitbucket", className: "fa-bitbucket", displayName: "Bitbucket", disabled: false, order: 7, colorClass: 'el-bitbucket'},
{ provider: "twitter", className: "fa-twitter", displayName: "Twitter", disabled: true, order: 4, colorClass: 'el-twitter'},
- { provider: "weblinks", className: "fa-globe", displayName: "Web Links", disabled: true, order: 8, colorClass: 'el-weblinks'}
+ { provider: "weblink", className: "fa-globe", displayName: "Web Links", disabled: true, order: -1, colorClass: 'el-weblinks'}
// TODO add more
];
@@ -20,28 +19,36 @@
templateUrl: 'directives/external-account/external-account.directive.html',
scope: {
linkedAccounts: '=',
- linksData: '=',
readOnly: '='
},
controller: ['$log', '$scope', 'ExternalAccountService', 'toaster',
function($log, $scope, ExternalAccountService, toaster) {
+
$log = $log.getInstance("ExtAccountDirectiveCtrl")
- $scope.accountList = _.clone(_supportedAccounts, true);
+
+ var _accountList = _.clone(_supportedAccounts, true);
+ _.remove(_accountList, function(al) { return al.order < 0});
$scope.$watchCollection('linkedAccounts', function(newValue, oldValue) {
- for (var i=0;i<$scope.accountList.length;i++) {
- var _idx = _.findIndex(newValue, function(a) {
- return $scope.accountList[i].provider === a.providerType;
- });
- if (_idx == -1) {
- $scope.accountList[i].status = 'unlinked';
- } else {
- // check if data
- if ($scope.linksData[$scope.accountList[i].provider]) {
- $scope.accountList[i].status = 'linked';
+ if (newValue) {
+ angular.forEach(_accountList, function(account) {
+ var _linkedAccount = _.find(newValue, function(p) {
+ return p.provider === account.provider
+ });
+ var accountStatus = _.get(_linkedAccount, 'data.status', null);
+ if (!_linkedAccount) {
+ account.status = 'unlinked';
+ } else if(accountStatus && accountStatus.toLowerCase() === 'pending') {
+ account.status = 'pending';
} else {
- $scope.accountList[i].status = 'pending';
+ account.status = 'linked';
}
- }
+ });
+ $scope.accountList = _accountList;
+ } else {
+ // reset the status for all accounts
+ angular.forEach(_accountList, function(account) {
+ delete account.status;
+ });
}
});
@@ -62,7 +69,7 @@
ExternalAccountService.linkExternalAccount(provider.provider, null)
.then(function(resp) {
$log.debug("Social account linked: " + JSON.stringify(resp));
- $scope.linkedAccounts.push(resp.profile);
+ $scope.linkedAccounts.push(resp.linkedAccount);
toaster.pop('success', "Success",
String.supplant(
"Your {provider} account has been linked. Data from your linked account will be visible on your profile shortly.",
@@ -92,11 +99,12 @@
.then(function(resp) {
$log.debug("Social account unlinked: " + JSON.stringify(resp));
var toRemove = _.findIndex($scope.linkedAccounts, function(la) {
- return la.providerType === provider.provider;
+ return la.provider === provider.provider;
});
- // remove from both links array and links data array
- $scope.linkedAccounts.splice(toRemove, 1);
- delete $scope.linksData[provider.provider];
+ if (toRemove > -1) {
+ // remove from the linkedAccounts array
+ $scope.linkedAccounts.splice(toRemove, 1);
+ }
toaster.pop('success', "Success",
String.supplant(
"Your {provider} account has been unlinked.",
@@ -120,60 +128,6 @@
]
};
})
- .directive('externalLinksData', function() {
- return {
- restrict: 'E',
- templateUrl: 'directives/external-account/external-link-data.directive.html',
- scope: {
- linkedAccountsData: '=',
- externalLinks: '='
- },
- controller: ['$log', '$scope', 'ExternalAccountService',
- function($log, $scope, ExternalAccountService) {
- $log = $log.getInstance('ExternalLinksDataDirective');
- var validProviders = _.pluck(_supportedAccounts, 'provider');
- function reCalcData(links, data) {
- $scope.linkedAccounts = [];
- angular.forEach(links, function(link) {
-
- var provider = link.providerType;
- var isValidProviderIdx = _.findIndex(validProviders, function(p) {
- return p === provider;
- });
- // skip if we dont care about this provider
- if (isValidProviderIdx == -1)
- return;
-
- if (!data[provider]) {
- $scope.linkedAccounts.push({
- provider: provider,
- data: {
- handle: link.name,
- status: 'PENDING'
- }
- });
- } else {
- // add data
- $scope.linkedAccounts.push({
- provider: provider,
- data: data[provider]
- });
- }
- });
- }
-
-
- $scope.$watch('linkedAccountsData', function(newValue, oldValue) {
- reCalcData($scope.externalLinks, newValue);
- });
-
- $scope.$watchCollection('externalLinks', function(newValue, oldValue) {
- reCalcData(newValue, $scope.linkedAccountsData);
- });
- }
- ]
- }
- })
.filter('providerData', function() {
return function(input, field) {
return _.result(_.find(_supportedAccounts, function(s) {
diff --git a/app/directives/external-account/external-account.directive.spec.js b/app/directives/external-account/external-account.directive.spec.js
index 0698616a5..4d512f131 100644
--- a/app/directives/external-account/external-account.directive.spec.js
+++ b/app/directives/external-account/external-account.directive.spec.js
@@ -2,138 +2,266 @@
describe('External Accounts Directive', function() {
var scope;
var element;
+ var toasterSvc;
+ var extAccountSvc;
+ var mockLinkedAccounts = [
+ {
+ provider: 'linkedin',
+ data: {
+ // don't care about other details
+ }
+ },
+ {
+ provider: 'github',
+ data: {
+ // don't care about other details
+ }
+ }
+ ];
beforeEach(function() {
bard.appModule('topcoder');
- bard.inject(this, '$compile', '$rootScope');
+ bard.inject(this, '$compile', '$rootScope', 'toaster', 'ExternalAccountService', '$q');
+
+ extAccountSvc = ExternalAccountService;
+ // mock external account service
+ sinon.stub(extAccountSvc, 'linkExternalAccount', function(provider) {
+ var $deferred = $q.defer();
+ if (provider === 'twitter') {
+ $deferred.reject({
+ status: 'SOCIAL_PROFILE_ALREADY_EXISTS',
+ msg: 'profile already exists'
+ });
+ } else if(provider === 'weblink') {
+ $deferred.reject({
+ status: 'FATAL_ERROR',
+ msg: 'fatal error'
+ });
+ } else {
+ $deferred.resolve({
+ status: 'SUCCESS',
+ linkedAccount : {
+ data: {
+ status: 'PENDING'
+ },
+ provider: provider
+ }
+ });
+ }
+ return $deferred.promise;
+ });
+ sinon.stub(extAccountSvc, 'unlinkExternalAccount', function(provider) {
+ var $deferred = $q.defer();
+ if (provider === 'twitter') {
+ $deferred.reject({
+ status: 'SOCIAL_PROFILE_NOT_EXIST',
+ msg: 'profile not exists'
+ });
+ } else if(provider === 'weblink') {
+ $deferred.reject({
+ status: 'FATAL_ERROR',
+ msg: 'fatal error'
+ });
+ } else {
+ $deferred.resolve({
+ status: 'SUCCESS'
+ });
+ }
+ return $deferred.promise;
+ });
+
+ toasterSvc = toaster;
+ bard.mockService(toaster, {
+ pop: $q.when(true),
+ default: $q.when(true)
+ });
+
scope = $rootScope.$new();
});
bard.verifyNoOutstandingHttpRequests();
describe('Linked external accounts', function() {
- var linkedAccounts = [
- {
- providerType: 'linkedin',
- // don't care about other details
- },
- {
- providerType: 'github'
- }
- ];
- var linksData = {
- 'linkedin' : {provider: 'linkedin', name: 'name-linkedin'},
- 'github' : {provider: 'github', name: 'name-github'}
- };
- var externalAccounts;
+ var linkedAccounts = angular.copy(mockLinkedAccounts);
+ var externalAccounts, controller;
beforeEach(function() {
scope.linkedAccounts = linkedAccounts;
- scope.linksData = linksData;
- element = angular.element(')');
+ element = angular.element(')');
externalAccounts = $compile(element)(scope);
scope.$digest();
- // scope.$apply();
+
+ controller = element.controller('externalAccounts');
+ });
+
+ afterEach(function() {
+ linkedAccounts = angular.copy(mockLinkedAccounts);
+ scope.linkedAccounts = linkedAccounts;
});
it('should have added account list to scope', function() {
expect(element.isolateScope().accountList).to.exist;
-
});
it('should have "linked" property set for github & linkedin', function() {
- var githubAccount = _.find(element.isolateScope().accountList, function(a) { return a.provider === 'github'});
- expect(githubAccount).to.have.property('status')
- .that.equals('linked');
+ var githubAccount = _.find(element.isolateScope().accountList, function(a) {
+ return a.provider === 'github'
+ });
+ expect(githubAccount).to.have.property('status').that.equals('linked');
+ });
- // var linkeindAccount = _.find(element.isolateScope().accountList, function(a) { return a.provider === 'linkedin'});
- // expect(linkeindAccount).to.have.property('linked')
- // .that.equals(true);
+ it('should have pending status for stackoverflow ', function() {
+ scope.linkedAccounts.push({provider: 'stackoverflow', data: {status: 'PENDING'}});
+ scope.$digest();
+ var soAccount = _.find(element.isolateScope().accountList, function(a) {
+ return a.provider === 'stackoverflow'
+ });
+ expect(soAccount).to.have.property('status').that.equals('pending');
});
- });
-});
-describe('External Links Data Directive', function() {
- var scope;
- var element;
+ it('should reset accountList when linkedAccounts set to null ', function() {
+ scope.linkedAccounts = null;
+ scope.$digest();
+ expect(element.isolateScope().accountList).to.have.length(7);
+ expect(_.all(_.pluck(element.isolateScope().accountList, 'status'))).to.be.false;
+ });
- beforeEach(function() {
- bard.appModule('topcoder');
- bard.inject(this, '$compile', '$rootScope');
- scope = $rootScope.$new();
- });
+ it('should link external account ', function() {
+ element.isolateScope().handleClick('stackoverflow', 'unlinked');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('success').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(3);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else if (['stackoverflow'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('pending');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
+ });
- bard.verifyNoOutstandingHttpRequests();
+ it('should NOT link external account with fatal error ', function() {
+ element.isolateScope().handleClick('weblink', 'unlinked');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(2);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
+ });
- describe('Linked external accounts', function() {
- var externalLinks = [
- {
- providerType: 'linkedin',
- // don't care about other details
- },
- {
- providerType: 'github'
- },
- {
- providerType: 'behance'
- },
- {
- providerType: 'dribbble'
- },
- {
- providerType: 'bitbucket'
- }
- ];
- var linkedAccounts = {
- github: {
- handle: "github-handle",
- followers: 1,
- publicRepos: 1
- },
- stackoverflow: {
- handle: 'so-handle',
- reputation: 2,
- answers: 2
- },
- behance: {
- name: 'behance name',
- projectViews: 3,
- projectAppreciations: 3
- },
- dribbble: {
- handle: 'dribble-handle',
- followers: 4,
- likes: 4
- },
- bitbucket: {
- username: 'bitbucket-username',
- followers: 5,
- repositories: 5
- },
- twitter: {
- handle: 'twitter-handle',
- noOfTweets: 6,
- followers: 6
- },
- linkedin: {
- name: 'linkedin name',
- title: 'linkedin title'
- }
- };
- var externalLinksData;
+ it('should NOT link external account with already existing account ', function() {
+ element.isolateScope().handleClick('twitter', 'unlinked');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(2);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
+ });
- beforeEach(function() {
- scope.linkedAccounts = linkedAccounts;
- scope.externalLinks = externalLinks;
- element = angular.element(')');
- externalLinksData = $compile(element)(scope);
+ it('should unlink external account ', function() {
+ element.isolateScope().handleClick('github', 'linked');
scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('success').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(1);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
});
- it('should have added linkedAccounts to scope', function() {
- expect(element.isolateScope().linkedAccounts).to.exist;
- // linkedAccounts should have 5 entries because externalLinks contains only 5 records
- expect(element.isolateScope().linkedAccounts).to.have.length(5);
+ it('should unlink if controller doesn\'t have account linked but API returns success ', function() {
+ element.isolateScope().handleClick('stackoverflow', 'linked');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('success').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(2);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
+ });
+
+ it('should NOT ulink external account with fatal error ', function() {
+ element.isolateScope().handleClick('weblink', 'linked');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(2);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
+ });
+
+ it('should NOT unlink external account with already unlinked account ', function() {
+ element.isolateScope().handleClick('twitter', 'linked');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce;
+ expect(element.isolateScope().linkedAccounts).to.have.length(2);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
+ });
+
+ it('should not do anything ', function() {
+ element.isolateScope().handleClick('github', 'pending');
+ scope.$digest();
+ expect(toasterSvc.pop).to.have.callCount(0);
+ expect(element.isolateScope().linkedAccounts).to.have.length(2);
+ expect(element.isolateScope().accountList).to.have.length(7);
+ element.isolateScope().accountList.forEach(function(account) {
+ expect(account.status).to.exist;
+ expect(account.provider).to.exist;
+ if (['github', 'linkedin'].indexOf(account.provider) != -1) {
+ expect(account.status).to.equal('linked');
+ } else {
+ expect(account.status).to.equal('unlinked');
+ }
+ });
});
});
diff --git a/app/directives/external-account/external-link-data.directive.jade b/app/directives/external-account/external-link-data.directive.jade
index 789a7a1f9..77a486ffe 100644
--- a/app/directives/external-account/external-link-data.directive.jade
+++ b/app/directives/external-account/external-link-data.directive.jade
@@ -1,5 +1,5 @@
.external-link-list
- div.external-link-tile(ng-repeat="account in linkedAccounts")
+ div.external-link-tile(ng-repeat="account in linkedAccountsData")
.top
div.logo
i.fa(ng-class="(account|providerData:'className') || 'fa-globe'")
@@ -97,3 +97,9 @@
.handle {{account.data.name}}
.title {{account.data.title}}
+
+ div(ng-switch-when="weblink")
+ p.link-title(data-ellipsis, ng-bind="account.title", ng-hide="account.status === 'PENDING'")
+ p.link-title(ng-show="account.status === 'PENDING'") Loading data. This will take a few minutes.
+
+ a.link-url(ng-href="{{account.URL}}", ng-bind="account.URL")
diff --git a/app/directives/external-account/external-links-data.directive.js b/app/directives/external-account/external-links-data.directive.js
new file mode 100644
index 000000000..fdb77f690
--- /dev/null
+++ b/app/directives/external-account/external-links-data.directive.js
@@ -0,0 +1,22 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @desc links data card directive
+ * @example
+ */
+ angular
+ .module('tcUIComponents')
+ .directive('externalLinksData', externalLinksData);
+
+ function externalLinksData() {
+ var directive = {
+ restrict: 'E',
+ templateUrl: 'directives/external-account/external-link-data.directive.html',
+ scope: {
+ linkedAccountsData: '='
+ }
+ };
+ return directive;
+ }
+})();
diff --git a/app/directives/external-account/external-links-data.directive.spec.js b/app/directives/external-account/external-links-data.directive.spec.js
new file mode 100644
index 000000000..81f085ef9
--- /dev/null
+++ b/app/directives/external-account/external-links-data.directive.spec.js
@@ -0,0 +1,85 @@
+/* jshint -W117, -W030 */
+describe('External Links Data Directive', function() {
+ var scope;
+ var element;
+
+ beforeEach(function() {
+ bard.appModule('topcoder');
+ bard.inject(this, '$compile', '$rootScope');
+ scope = $rootScope.$new();
+ });
+
+ bard.verifyNoOutstandingHttpRequests();
+
+ describe('Linked external accounts', function() {
+ var linkedAccounts = [
+ {
+ provider: 'github',
+ data: {
+ handle: "github-handle",
+ followers: 1,
+ publicRepos: 1
+ }
+ },
+ { provider: 'stackoverflow',
+ data: {
+ handle: 'so-handle',
+ reputation: 2,
+ answers: 2
+ }
+ },
+ {
+ provider: 'behance',
+ data: {
+ name: 'behance name',
+ projectViews: 3,
+ projectAppreciations: 3
+ }
+ },
+ {
+ provider: 'dribbble',
+ data: {
+ handle: 'dribbble-handle',
+ followers: 4,
+ likes: 4
+ }
+ },
+ {
+ provider: 'bitbucket',
+ data: {
+ username: 'bitbucket-username',
+ followers: 5,
+ repositories: 5
+ }
+ },
+ {
+ provider: 'twitter',
+ data: {
+ handle: 'twitter-handle',
+ noOfTweets: 6,
+ followers: 6
+ }
+ },
+ {
+ provider: 'linkedin',
+ data: {
+ status: 'pending'
+ }
+ }
+ ];
+ var externalLinksData;
+
+ beforeEach(function() {
+ scope.linkedAccounts = linkedAccounts;
+ element = angular.element(')');
+ externalLinksData = $compile(element)(scope);
+ scope.$digest();
+ });
+
+ it('should have added linkedAccounts to scope', function() {
+ expect(element.isolateScope().linkedAccountsData).to.exist;
+ expect(element.isolateScope().linkedAccountsData).to.have.length(7);
+ });
+
+ });
+});
diff --git a/app/directives/external-account/external-web-link.directive.jade b/app/directives/external-account/external-web-link.directive.jade
new file mode 100644
index 000000000..0f99e4cec
--- /dev/null
+++ b/app/directives/external-account/external-web-link.directive.jade
@@ -0,0 +1,16 @@
+.web-link
+ form(name="addWebLinkFrm", ng-submit="addWebLinkFrm.$valid && addWebLink()", autocomplete="off")
+ .validation-bar.url(ng-class="{ 'error-bar': (addWebLinkFrm.url.$dirty && addWebLinkFrm.url.$invalid) }")
+ input.form-field.url(name="url", type="url", ng-model="url", placeholder="http://www.yourlink.com", required)
+
+ .form-input-error(ng-show="addWebLinkFrm.url.$dirty && addWebLinkFrm.url.$invalid")
+ p(ng-show="addWebLinkFrm.url.$error.required") This is a required field.
+
+ p(ng-show="addWebLinkFrm.url.$error.url") Please enter a valid URL
+
+ button.tc-btn.tc-btn-m(type="submit",
+ tc-busy-button, tc-busy-when="addingWebLink", tc-busy-message="Adding",
+ ng-disabled="addWebLinkFrm.$invalid || addWebLinkFrm.$pristine") Add
+
+ .form-errors(ng-show="errorMessage")
+ p.form-error {{errorMessage}}
diff --git a/app/directives/external-account/external-web-links.directive.js b/app/directives/external-account/external-web-links.directive.js
new file mode 100644
index 000000000..411d1f22f
--- /dev/null
+++ b/app/directives/external-account/external-web-links.directive.js
@@ -0,0 +1,61 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @desc links data card directive
+ * @example
+ */
+ angular
+ .module('tcUIComponents')
+ .directive('externalWebLink', ExternalWebLink);
+ ExternalWebLink.$inject = ['$log', 'ExternalWebLinksService', 'toaster'];
+
+ function ExternalWebLink($log, ExternalWebLinksService, toaster) {
+ var directive = {
+ restrict: 'E',
+ templateUrl: 'directives/external-account/external-web-link.directive.html',
+ scope: {
+ linkedAccounts: '=',
+ userHandle: '@'
+ },
+ controller: ['$scope', '$log', ExternalWebLinkCtrl]
+ };
+
+
+ function ExternalWebLinkCtrl($scope, $log) {
+ $log = $log.getInstance('ExternalWebLinkCtrl');
+ $scope.addingWebLink = false;
+ $scope.errorMessage = null;
+
+ $scope.addWebLink = function() {
+ $log.debug("URL: " + $scope.url);
+ $scope.addingWebLink = true;
+ $scope.errorMessage = null;
+ ExternalWebLinksService.addLink($scope.userHandle, $scope.url)
+ .then(function(data) {
+ $scope.addingWebLink = false;
+ $log.debug("Web link added: " + JSON.stringify(data));
+ data.data.provider = data.provider;
+ $scope.linkedAccounts.push(data.data);
+ toaster.pop('success', "Success", "Your link has been added. Data from your link will be visible on your profile shortly.");
+ })
+ .catch(function(resp) {
+ $scope.addingWebLink = false;
+
+ if (resp.status === 'WEBLINK_ALREADY_EXISTS') {
+ $log.info("Social profile already linked to another account");
+ toaster.pop('error', "Whoops!",
+ "This weblink is already added to your account. \
+ If you think this is an error please contact support@topcoder.com."
+ );
+ } else {
+ $log.error("Fatal Error: addWebLink: " + resp.msg);
+ toaster.pop('error', "Sorry, we are unable add web link. If problem persist please contact support@topcoder.com.");
+ }
+ });
+ };
+ }
+
+ return directive;
+ }
+})();
diff --git a/app/directives/external-account/external-web-links.directive.spec.js b/app/directives/external-account/external-web-links.directive.spec.js
new file mode 100644
index 000000000..6e8997e8b
--- /dev/null
+++ b/app/directives/external-account/external-web-links.directive.spec.js
@@ -0,0 +1,115 @@
+/* jshint -W117, -W030 */
+describe('ExternalWebLinks Directive', function() {
+ var scope = {};
+ var element;
+ var extWebLinkSvc;
+ var toasterSvc;
+ var mockLinkedAccounts = [
+ {
+ provider: 'github',
+ data: {
+ handle: "github-handle",
+ followers: 1,
+ publicRepos: 1
+ }
+ }
+ ];
+
+ beforeEach(function() {
+ bard.appModule('topcoder');
+ bard.inject(this, '$compile', '$rootScope', 'ExternalWebLinksService', '$q', 'toaster');
+
+ extWebLinkSvc = ExternalWebLinksService;
+
+ // mock external weblink service
+ sinon.stub(extWebLinkSvc, 'addLink', function(handle, url) {
+ var $deferred = $q.defer();
+ if (handle === 'throwError') {
+ $deferred.reject({
+ status: 'FATAL_ERROR',
+ msg: 'fatal error'
+ });
+ } else if(handle === 'alreadyExistsError') {
+ $deferred.reject({
+ status: 'WEBLINK_ALREADY_EXISTS',
+ msg: 'link already added to your account'
+ });
+ } else {
+ $deferred.resolve({
+ data: {
+ status: 'PENDING'
+ },
+ provider: 'weblink'
+ });
+ }
+ return $deferred.promise;
+ });
+
+ toasterSvc = toaster;
+ bard.mockService(toaster, {
+ pop: $q.when(true),
+ default: $q.when(true)
+ });
+
+ scope = $rootScope.$new();
+ });
+
+ bard.verifyNoOutstandingHttpRequests();
+
+ describe('Linked external accounts', function() {
+ var linkedAccounts = angular.copy(mockLinkedAccounts);
+ var template, element, controller;
+
+ beforeEach(function() {
+ scope.linkedAccounts = linkedAccounts;
+ element = angular.element(')');
+ template = $compile(element)(scope);
+ scope.$digest();
+
+ controller = element.controller('externalWebLink');
+ });
+
+ afterEach(function() {
+ linkedAccounts = angular.copy(mockLinkedAccounts);
+ scope.linkedAccounts = linkedAccounts;
+ });
+
+ it('should have added linkedAccounts to scope', function() {
+ expect(scope.linkedAccounts).to.exist;
+ expect(scope.linkedAccounts).to.have.length(1);
+ });
+
+ it('should have added new weblink to linkedAccounts', function() {
+ scope.userHandle = 'test';
+ scope.url = 'https://www.topcoder.com';
+ element.isolateScope().addWebLink();
+ scope.$digest();
+ expect(scope.linkedAccounts).to.have.length(2);
+ var topcoderLink = _.find(scope.linkedAccounts, function(a) {
+ return a.provider === 'weblink'
+ });
+ expect(topcoderLink).to.exist;
+ expect(topcoderLink.status).to.exist.to.equal('PENDING');
+ expect(toasterSvc.pop).to.have.been.calledWith('success').calledOnce;
+ });
+
+ it('should NOT add new weblink to linkedAccounts', function() {
+ element.isolateScope().userHandle = 'throwError';
+ element.isolateScope().url = 'https://www.topcoder.com';
+ element.isolateScope().addWebLink();
+ scope.$digest();
+ expect(scope.linkedAccounts).to.have.length(1);
+ expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce;
+ });
+
+ it('should NOT add new weblink to linkedAccounts', function() {
+ element.isolateScope().userHandle = 'alreadyExistsError';
+ element.isolateScope().url = 'https://www.topcoder.com';
+ element.isolateScope().addWebLink();
+ scope.$digest();
+ expect(scope.linkedAccounts).to.have.length(1);
+ expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce;
+ });
+
+ });
+});
diff --git a/app/filters/external-link-color.filter.js b/app/filters/external-link-color.filter.js
index 9a81f4033..5dc7c9578 100644
--- a/app/filters/external-link-color.filter.js
+++ b/app/filters/external-link-color.filter.js
@@ -10,7 +10,7 @@
var providerColors = {
'el-weblinks': '#82A0AA',
'el-bitbucket': '#205081',
- 'el-dribble': '#EA4C89',
+ 'el-dribbble': '#EA4C89',
'el-linkedin': '#127CB5',
'el-twitter': '#62AADC',
'el-stackoverflow': '#E5712A',
diff --git a/app/index.jade b/app/index.jade
index 30d2b4c69..65b8274b9 100644
--- a/app/index.jade
+++ b/app/index.jade
@@ -73,6 +73,7 @@ html
link(rel="stylesheet", href="assets/css/directives/page-state-header.directive.css")
link(rel="stylesheet", href="assets/css/directives/ios-card.css")
link(rel="stylesheet", href="assets/css/directives/history-graph.css")
+ link(rel="stylesheet", href="assets/css/directives/external-web-link.css")
link(rel="stylesheet", href="assets/css/directives/external-link-data.css")
link(rel="stylesheet", href="assets/css/directives/external-account.css")
link(rel="stylesheet", href="assets/css/directives/empty-state-placeholder.css")
@@ -128,6 +129,7 @@ html
script(src='../bower_components/angucomplete-alt/angucomplete-alt.js')
script(src='../bower_components/angular-cookies/angular-cookies.js')
script(src='../bower_components/angular-dropdowns/dist/angular-dropdowns.js')
+ script(src='../bower_components/angular-ellipsis/src/angular-ellipsis.js')
script(src='../bower_components/angular-filter/dist/angular-filter.min.js')
script(src='../bower_components/angular-img-fallback/angular.dcb-img-fallback.js')
script(src='../bower_components/intro.js/intro.js')
@@ -192,6 +194,8 @@ html
script(src="directives/distribution-graph/distribution-graph.directive.js")
script(src="directives/empty-state-placeholder/empty-state-placeholder.directive.js")
script(src="directives/external-account/external-account.directive.js")
+ script(src="directives/external-account/external-links-data.directive.js")
+ script(src="directives/external-account/external-web-links.directive.js")
script(src="directives/focus-on.directive.js")
script(src="directives/header/header-menu-item.directive.js")
script(src="directives/history-graph/history-graph.directive.js")
@@ -270,6 +274,7 @@ html
script(src="services/communityData.service.js")
script(src="services/emptyState.service.js")
script(src="services/externalAccounts.service.js")
+ script(src="services/externalLinks.service.js")
script(src="services/helpers.service.js")
script(src="services/image.service.js")
script(src="services/introduction.service.js")
diff --git a/app/profile/about/about.controller.js b/app/profile/about/about.controller.js
index 564986821..cfd21c43d 100644
--- a/app/profile/about/about.controller.js
+++ b/app/profile/about/about.controller.js
@@ -3,9 +3,9 @@
angular.module('tc.profile').controller('ProfileAboutController', ProfileAboutController);
- ProfileAboutController.$inject = ['$log', '$scope', 'ProfileService', 'ExternalAccountService', 'UserService', 'CONSTANTS'];
+ ProfileAboutController.$inject = ['$log', '$scope', '$q', 'ProfileService', 'ExternalAccountService', 'ExternalWebLinksService', 'UserService', 'CONSTANTS'];
- function ProfileAboutController($log, $scope, ProfileService, ExternalAccountService, UserService, CONSTANTS) {
+ function ProfileAboutController($log, $scope, $q, ProfileService, ExternalAccountService, ExternalWebLinksService, UserService, CONSTANTS) {
var vm = this;
$log = $log.getInstance("ProfileAboutController");
var profileVm = $scope.$parent.profileVm;
@@ -22,38 +22,18 @@
function activate() {
- ExternalAccountService.getLinkedExternalLinksData(profileVm.userHandle).then(function(data) {
- vm.linkedExternalAccountsData = data.plain();
-
- // show section if user is viewing his/her own profile OR if we have data
- //vm.hasLinks = profileVm.linkedExternalAccounts.length;
- vm.hasLinks = _.any(_.valuesIn(_.omit(vm.linkedExternalAccountsData, ['userId', 'updatedAt','createdAt','createdBy','updatedBy','handle'])));
- vm.displaySection.externalLinks = profileVm.showEditProfileLink || vm.hasLinks;
-
- // if user is authenticated, call for profiles end point
- if (profileVm.isUser) {
- var userId = UserService.getUserIdentity().userId;
- ExternalAccountService.getLinkedExternalAccounts(userId).then(function(data) {
- vm.linkedExternalAccounts = data;
- profileVm.status.externalLinks = CONSTANTS.STATE_READY;
- }).catch(function(err) {
- profileVm.status.externalLinks = CONSTANTS.STATE_ERROR;
- });
- } else {
- vm.linkedExternalAccounts = [];
- // remove all keys except the provider keys
- var accounts = _.omit(vm.linkedExternalAccountsData, ['userId', 'updatedAt','createdAt','createdBy','updatedBy','handle']);
- // populate the externalLinks for external-account-data directive with info from ext accounts data
- for(var provider in accounts) {
- if (accounts[provider]) {
- vm.linkedExternalAccounts.push({
- providerType: provider
- });
- }
- }
- profileVm.status.externalLinks = CONSTANTS.STATE_READY;
- }
- }).catch(function(err) {
+ var _userId = profileVm.isUser ? UserService.getUserIdentity().userId : false;
+ // retrieve web links & external accounts
+ var _linksPromises = [
+ ExternalAccountService.getAllExternalLinks(profileVm.userHandle, _userId, !!_userId),
+ ExternalWebLinksService.getLinks(profileVm.userHandle, !!_userId)
+ ];
+ $q.all(_linksPromises).then(function(data) {
+ vm.linkedExternalAccounts = data[0].concat(data[1]);
+ vm.displaySection.externalLinks = profileVm.showEditProfileLink || !!vm.linkedExternalAccounts.length;
+
+ profileVm.status.externalLinks = CONSTANTS.STATE_READY;
+ }).catch(function(resp) {
profileVm.status.externalLinks = CONSTANTS.STATE_ERROR;
});
diff --git a/app/profile/about/about.jade b/app/profile/about/about.jade
index 712d49d6a..2c7826e1e 100644
--- a/app/profile/about/about.jade
+++ b/app/profile/about/about.jade
@@ -12,7 +12,7 @@
.empty-profile
.empty-state
- empty-state-placeholder(state-name="profile-empty", show="profileVm.status.skills === 'ready' && profileVm.status.stats === 'ready' && profileVm.status.externalLinks === 'ready' && !profileVm.showEditProfileLink && !profileVm.showTCActivity && (!profileVm.skills || (profileVm.skills && profileVm.skills.length == 0)) && !vm.hasLinks")
+ empty-state-placeholder(state-name="profile-empty", show="profileVm.status.skills === 'ready' && profileVm.status.stats === 'ready' && profileVm.status.externalLinks === 'ready' && !profileVm.showEditProfileLink && !profileVm.showTCActivity && (!profileVm.skills || (profileVm.skills && profileVm.skills.length == 0)) && !vm.linkedExternalAccounts.length")
.sample-image
img(ng-src="/images/robot.svg")
@@ -93,10 +93,14 @@
#externalLinks
tc-section(ng-show="vm.displaySection.externalLinks", state="profileVm.status.externalLinks")
.external-links
- h3.activity(ng-if="vm.hasLinks") on the web
- external-links-data(ng-show="vm.hasLinks", external-links="vm.linkedExternalAccounts", linked-accounts-data="vm.linkedExternalAccountsData")
+
+ h3.activity(ng-if="vm.linkedExternalAccounts.length") on the web
+
+ external-links-data(ng-show="vm.linkedExternalAccounts.length", linked-accounts-data="vm.linkedExternalAccounts")
.empty-state
- empty-state-placeholder(state-name="profile-external-links", show="!vm.hasLinks")
+ empty-state-placeholder(state-name="profile-external-links", show="!vm.linkedExternalAccounts.length")
external-accounts.external-account-container(linked-accounts="[]", links-data="{}", read-only="true")
+
+
diff --git a/app/services/externalAccounts.service.js b/app/services/externalAccounts.service.js
index e99f71a76..b6e47b6c2 100644
--- a/app/services/externalAccounts.service.js
+++ b/app/services/externalAccounts.service.js
@@ -9,12 +9,13 @@
var auth0 = auth;
$log = $log.getInstance('ExternalAccountService');
- var api = ApiService.restangularV3;
+ var memberApi = ApiService.getApiServiceProvider('MEMBER');
var userApi = ApiService.getApiServiceProvider('USER');
var service = {
- getLinkedExternalAccounts: getLinkedExternalAccounts,
- getLinkedExternalLinksData: getLinkedExternalLinksData,
+ getAllExternalLinks: getAllExternalLinks,
+ getLinkedAccounts: getLinkedAccounts,
+ getAccountsData: getAccountsData,
linkExternalAccount: linkExternalAccount,
unlinkExternalAccount: unlinkExternalAccount
};
@@ -28,22 +29,23 @@
* @param userId
* @return list of linked Accounts
*/
- function getLinkedExternalAccounts(userId) {
+ function getLinkedAccounts(userId) {
return userApi.one('users', userId).get({fields:"profiles"})
.then(function(result) {
- return result.profiles || [];
+ angular.forEach(result.profiles, function(p) {
+ p.provider = p.providerType;
+ });
+ return result.profiles;
});
}
- function getLinkedExternalLinksData(userHandle) {
- return api.one('members', userHandle).withHttpConfig({skipAuthorization: true}).customGET('externalAccounts')
- .then(function(data) {
- // TODO workaround for dribbble spelling mistake, remove once API is fixed
- if (data.dribble) {
- data.dribbble = data.dribble;
- }
- return data;
- })
+ function getAccountsData(userHandle) {
+ return memberApi.one('members', userHandle)
+ .withHttpConfig({skipAuthorization: true})
+ .customGET('externalAccounts')
+ .then(function(data) {
+ return data;
+ });
}
function unlinkExternalAccount(account) {
@@ -71,6 +73,53 @@
});
}
+
+ function _convertAccountsIntoCards(links, data, includePending) {
+ var _cards = [];
+ if (!links.length) {
+ var providers = _.omit(data, ['userId', 'updatedAt', 'createdAt', 'createdBy', 'updatedBy', 'handle']);
+ // populate the externalLinks for external-account-data directive with info from ext accounts data
+
+ angular.forEach(_.keys(providers), function(p) {
+ if (providers[p])
+ links.push({provider: p});
+ });
+ }
+ // handling external accounts first
+ angular.forEach(links, function(link) {
+ var provider = link.provider;
+ if (data[provider]) {
+ // add data
+ _cards.push({provider: provider, data: data[provider]});
+ } else if (includePending) {
+ // add pending card
+ _cards.push({provider: provider, data: {handle: link.name, status: 'PENDING'}});
+ }
+ });
+ $log.debug("Processed Accounts Cards: " + JSON.stringify(_cards));
+ return _cards;
+ }
+
+
+ function getAllExternalLinks(userHandle, userId, includePending) {
+ return $q(function(resolve, reject) {
+ var _promises = [getAccountsData(userHandle)];
+ if (includePending)
+ _promises.push(getLinkedAccounts(userId));
+
+ $q.all(_promises).then(function(data) {
+ var links = includePending ? data[1]: [];
+ var _cards = _convertAccountsIntoCards(links, data[0].plain(), includePending);
+ // TODO add weblinks
+ resolve(_cards);
+ }).catch(function(resp) {
+ $log.error(resp);
+ reject(resp);
+ })
+ })
+ }
+
+
function linkExternalAccount(provider, callbackUrl) {
return $q(function(resolve, reject) {
// supported backends
@@ -81,7 +130,6 @@
connection: provider,
scope: "openid profile offline_access",
state: callbackUrl,
- // callbackURL: CONSTANTS.auth0Callback
},
function(profile, idToken, accessToken, state, refreshToken) {
$log.debug("onSocialLoginSuccess");
@@ -105,10 +153,16 @@
userApi.one('users', user.userId).customPOST(postData, "profiles", {}, {})
.then(function(resp) {
$log.debug("Succesfully linked account: " + JSON.stringify(resp));
- resolve({
+ // construct "card" object and resolve it
+ var _data = {
status: "SUCCESS",
- profile: postData
- });
+ linkedAccount: {
+ provider: provider,
+ data: postData
+ }
+ };
+ _data.linkedAccount.data.status = 'PENDING';
+ resolve(_data);
})
.catch(function(resp) {
var errorStatus = "FATAL_ERROR";
diff --git a/app/services/externalAccounts.service.spec.js b/app/services/externalAccounts.service.spec.js
new file mode 100644
index 000000000..39a8af578
--- /dev/null
+++ b/app/services/externalAccounts.service.spec.js
@@ -0,0 +1,236 @@
+/* jshint -W117, -W030 */
+describe('ExternalAccount Service', function() {
+ var service;
+ var mockAccountsData = mockData.getMockLinkedExternalAccountsData();
+ var mockUserLinksData = mockData.getMockLinkedExternalAccounts();
+ var mockAuth0Profile = mockData.getMockAuth0Profile();
+ var mockProfile = mockData.getMockProfile();
+ var apiUrl;
+ var auth0, userService;
+ var profileGet, profilePost, profileDelete;
+
+
+ beforeEach(function() {
+ bard.appModule('topcoder');
+ bard.inject(this, 'ExternalAccountService', '$httpBackend', '$q', 'CONSTANTS', 'JwtInterceptorService', 'auth', 'UserService');
+ bard.mockService(JwtInterceptorService, {
+ getToken: $q.when('token'),
+ _default: $q.when([])
+ });
+
+ apiUrl = CONSTANTS.API_URL;
+ service = ExternalAccountService;
+ auth0 = auth;
+ userService = UserService;
+
+ // mock user api
+ sinon.stub(auth0, 'signin', function(params, successCallback, failureCallback) {
+ if (params && params.state == 'failure') {
+ failureCallback.call(failureCallback, "MOCK_ERROR");
+ }
+ successCallback.call(
+ successCallback,
+ mockAuth0Profile,
+ "mockAuth0IdToken",
+ "mockAuth0AccessToken",
+ params.state,
+ null
+ );
+ });
+
+ // mock user service
+ sinon.stub(userService, 'getUserIdentity', function() {
+ return {userId: 111, handle: mockProfile.handle };
+ });
+
+ $httpBackend
+ .when('GET', apiUrl + '/members/test1/externalAccounts/')
+ .respond(200, {result: {content: mockAccountsData}});
+
+ profileGet = $httpBackend.when('GET', apiUrl + '/users/111/?fields=profiles');
+ profileGet.respond(200, {result: {content: mockUserLinksData}});
+
+ profilePost = $httpBackend.when('POST', apiUrl + '/users/111/profiles/');
+ profilePost.respond(200, {result: {content: mockProfile}});
+
+ profileDelete = $httpBackend.when('DELETE', apiUrl + '/users/111/profiles/stackoverflow/');
+ profileDelete.respond(200, {result: {content: mockProfile}});
+
+ });
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('service should be defined', function() {
+ expect(service).to.be.defined;
+ });
+
+ it('should return linked external accounts', function() {
+ service.getLinkedAccounts(111).then(function(data) {
+ expect(data).to.have.length(5);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should return linked external accounts data', function() {
+ service.getAccountsData('test1').then(function(data) {
+ data = data.plain();
+ expect(data).to.be.defined;
+ expect(_.keys(data)).to.include.members(['dribbble', 'github', 'behance', 'bitbucket', 'linkedin', 'stackoverflow', 'twitter']);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should return all non-pending external links', function() {
+ // spy
+ service.getAllExternalLinks('test1', 111, false).then(function(data) {
+ expect(data).to.be.defined;
+ expect(_.pluck(data, 'provider')).to.include.members(['dribbble', 'github','bitbucket', 'stackoverflow']);
+ expect(_.all(_.pluck(data, 'data'))).to.be.truthy;
+ });
+ $httpBackend.flush();
+ });
+
+ it('should return all external links including pending', function() {
+ // spy
+ service.getAllExternalLinks('test1', 111, true).then(function(data) {
+ expect(data).to.be.defined;
+ expect(_.pluck(data, 'provider')).to.include.members(['dribbble', 'github', 'behance', 'bitbucket','stackoverflow']);
+ expect(data).to.have.length(5);
+ var nullAccounts = _.remove(data, function(n) {return n.data.status === 'PENDING'});
+ expect(nullAccounts).to.have.length(1);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should not return unsupported links even if they are returned by the API', function() {
+ var profiles = JSON.parse(JSON.stringify(mockUserLinksData));
+ profiles.profiles.push({providerType: 'unsupported'});
+ profileGet.respond(200, {result: {content: profiles}});
+ // spy
+ service.getAllExternalLinks('test1', 111, true).then(function(data) {
+ expect(data).to.be.defined;
+ expect(_.pluck(data, 'provider')).to.include.members(['dribbble', 'github','bitbucket', 'stackoverflow']);
+ expect(_.all(_.pluck(data, 'data'))).to.be.truthy;
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail in returning links', function() {
+ var errorMessage = "bad request";
+ // mocks the GET call to respond with 400 bad request
+ profileGet.respond(400, {result: { status: 400, content: errorMessage } });
+ // calls getAllExternalLinks method with valid params
+ service.getAllExternalLinks('test1', 111, true).then(function(data) {
+ sinon.assert.fail('should not be called');
+ }).catch(function(resp) {
+ expect(resp).to.exist;
+ expect(resp.status).to.exist.to.equal(400);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should link external account', function() {
+ // call linkExternalAccount method with supporte network, should succeed
+ service.linkExternalAccount('stackoverflow', "callback").then(function(data) {
+ expect(data).to.be.defined;
+ // console.log(data);
+ expect(data.status).to.exist.to.equal('SUCCESS');
+ expect(data.linkedAccount).to.exist;
+ expect(data.linkedAccount.provider).to.exist.to.equal('stackoverflow');
+ expect(data.linkedAccount.data).to.exist;
+ expect(data.linkedAccount.data.status).to.exist.to.equal('PENDING');
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail with unsupported network', function() {
+ // call linkExternalAccount method with unsupported network, should fail
+ service.linkExternalAccount('unsupported', "callback").then(function(data) {
+ expect(data).to.be.defined;
+ expect(data.status).to.exist.to.equal('failed');
+ expect(data.error.to.contain('unsupported'));
+ });
+ });
+
+ it('should fail with already existing profile', function() {
+ var errorMessage = "social profile exists";
+ profilePost.respond(400, {result: { status: 400, content: errorMessage } });
+ // call linkExternalAccount method, having user service throw already exist
+ service.linkExternalAccount('stackoverflow', "callback").then(function(data) {
+ sinon.assert.fail('should not be called');
+ }, function(error) {
+ expect(error).to.be.defined;
+ expect(error.status).to.exist.to.equal('SOCIAL_PROFILE_ALREADY_EXISTS');
+ expect(error.msg).to.exist.to.equal(errorMessage);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail with auth0 error', function() {
+ // call linkExternalAccount method with auth0 throwing error
+ service.linkExternalAccount('stackoverflow', "failure").then(function(data) {
+ sinon.assert.fail('should not be called');
+ }, function(error) {
+ expect(error).to.be.exist.to.equal('MOCK_ERROR');
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail, with fatal error, in linking external account', function() {
+ var errorMessage = "endpoint not found";
+ profilePost.respond(404, {result: { status: 404, content: errorMessage } });
+ // call unlinkExternalAccount method with supporte network, should succeed
+ service.linkExternalAccount('stackoverflow', "callback").then(function(data) {
+ sinon.assert.fail('should not be called');
+ }).catch(function(error) {
+ expect(error).to.be.defined;
+ expect(error.status).to.exist.to.equal('FATAL_ERROR');
+ expect(error.msg).to.exist.to.equal(errorMessage);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should unlink external account', function() {
+ var errorMessage = "social profile exists";
+ profilePost.respond(400, {result: { status: 400, content: errorMessage } });
+ // call unlinkExternalAccount method with supporte network, should succeed
+ service.unlinkExternalAccount('stackoverflow').then(function(data) {
+ expect(data).to.be.defined;
+ // console.log(data);
+ expect(data.status).to.exist.to.equal('SUCCESS');
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail, with profile does not exist, in unlinking external account', function() {
+ var errorMessage = "social profile does not exists";
+ profileDelete.respond(404, {result: { status: 404, content: errorMessage } });
+ // call unlinkExternalAccount method with supporte network, should succeed
+ service.unlinkExternalAccount('stackoverflow').then(function(data) {
+ sinon.assert.fail('should not be called');
+ }).catch(function(error) {
+ expect(error).to.be.defined;
+ expect(error.status).to.exist.to.equal('SOCIAL_PROFILE_NOT_EXIST');
+ expect(error.msg).to.exist.to.equal(errorMessage);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail, with fatal error, in unlinking external account', function() {
+ var errorMessage = "bad request";
+ profileDelete.respond(400, {result: { status: 400, content: errorMessage } });
+ // call unlinkExternalAccount method with supporte network, should succeed
+ service.unlinkExternalAccount('stackoverflow').then(function(data) {
+ sinon.assert.fail('should not be called');
+ }).catch(function(error) {
+ expect(error).to.be.defined;
+ expect(error.status).to.exist.to.equal('FATAL_ERROR');
+ expect(error.msg).to.exist.to.equal(errorMessage);
+ });
+ $httpBackend.flush();
+ });
+
+});
diff --git a/app/services/externalLinks.service.js b/app/services/externalLinks.service.js
new file mode 100644
index 000000000..8c8437401
--- /dev/null
+++ b/app/services/externalLinks.service.js
@@ -0,0 +1,74 @@
+(function() {
+ 'use strict';
+
+ angular.module('tc.services').factory('ExternalWebLinksService', ExternalWebLinksService);
+
+ ExternalWebLinksService.$inject = ['$log', 'CONSTANTS', 'ApiService', '$q'];
+
+ function ExternalWebLinksService($log, CONSTANTS, ApiService, $q) {
+ $log = $log.getInstance("ExternalWebLinksService");
+
+ var memberApi = ApiService.getApiServiceProvider('MEMBER');
+
+ var service = {
+ getLinks: getLinks,
+ addLink: addLink,
+ removeLink: removeLink
+ };
+ return service;
+
+ /////////////////////////
+
+ function getLinks(userHandle, includePending) {
+ return memberApi.one('members', userHandle)
+ .withHttpConfig({skipAuthorization: true})
+ .customGET('externalLinks')
+ .then(function(links) {
+ links = links.plain();
+ if (!includePending) {
+ _.remove(links, function(l) {
+ return _.get(l, 'synchronizedAt') === 0;
+ });
+ }
+ // add provider type as weblink
+ links = _(links).forEach(function(l) {
+ l.provider = 'weblink';
+ if (l.synchronizedAt === 0) {
+ l.status = 'PENDING';
+ }
+ }).value();
+ return links;
+ });
+ }
+
+ function addLink(userHandle, url) {
+ return $q(function(resolve, reject) {
+ memberApi.one('members', userHandle).customPOST({'url': url}, 'externalLinks')
+ .then(function(resp) {
+ var _newLink = {
+ provider: 'weblink',
+ data: resp
+ };
+ _newLink.data.status = 'PENDING';
+ resolve(_newLink);
+ })
+ .catch(function(resp) {
+ var errorStatus = "FATAL_ERROR";
+ $log.error("Error adding weblink: " + resp.data.result.content);
+ if (resp.data.result && resp.data.result.status === 400) {
+ errorStatus = "WEBLINK_ALREADY_EXISTS";
+ }
+ reject({
+ status: errorStatus,
+ msg: resp.data.result.content
+ });
+ });
+ });
+ }
+
+ function removeLink(userHandle, key) {
+ return memberApi.one('members', userHandle).one('externalLinks', key).remove();
+ }
+
+ }
+})();
diff --git a/app/services/externalLinks.service.spec.js b/app/services/externalLinks.service.spec.js
new file mode 100644
index 000000000..0b228456a
--- /dev/null
+++ b/app/services/externalLinks.service.spec.js
@@ -0,0 +1,90 @@
+/* jshint -W117, -W030 */
+describe('ExternalWebLinks service', function() {
+ var service;
+ var mockExternalLinks = mockData.getMockExternalWebLinksData();
+ var apiUrl;
+ var linksGet, linksPost, linksDelete;
+
+
+ beforeEach(function() {
+ bard.appModule('topcoder');
+ bard.inject(this, 'ExternalWebLinksService', 'JwtInterceptorService', '$httpBackend', 'CONSTANTS', '$q');
+ bard.mockService(JwtInterceptorService, {
+ getToken: $q.when('token'),
+ _default: $q.when([])
+ });
+
+ apiUrl = CONSTANTS.API_URL;
+ service = ExternalWebLinksService;
+
+ // mock profile api
+ linksGet = $httpBackend.when('GET', apiUrl + '/members/test1/externalLinks/');
+ linksGet.respond(200, {result: {content: mockExternalLinks}});
+
+ // mock profile api [POST]
+ linksPost = $httpBackend.when('POST', apiUrl + '/members/test1/externalLinks/');
+ linksPost.respond(200, {result: {content: mockExternalLinks[0]}});
+
+ // mock profile api [DELETE]
+ linksDelete = $httpBackend.when('DELETE', apiUrl + '/members/test1/externalLinks/testkey/');
+ linksDelete.respond(200, {result: {}});
+ });
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should be defined', function() {
+ expect(service).to.be.defined;
+ });
+
+ it('should return linked external web links including pending', function() {
+ service.getLinks('test1', true).then(function(data) {
+ expect(data).to.have.length(3);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should return linked external non-pending web links ', function() {
+ service.getLinks('test1', false).then(function(data) {
+ expect(data).to.have.length(2);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should add external link', function() {
+ // call addLink method with valid params, should succeed
+ service.addLink('test1', "http://google.com").then(function(newLink) {
+ expect(newLink).to.be.exist;
+ expect(newLink.provider).to.exist.to.equal('weblink');
+ expect(newLink.data).to.exist;
+ expect(newLink.data.status).to.exist.to.equal('PENDING');
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail with already existing link', function() {
+ var errorMessage = "web link exists";
+ linksPost.respond(400, {result: { status: 400, content: errorMessage } });
+ // call linkExternalAccount method, having user service throw already exist
+ service.addLink('test1', "http://google.com").then(function(data) {
+ sinon.assert.fail('should not be called');
+ }, function(error) {
+ expect(error).to.be.defined;
+ expect(error.status).to.exist.to.equal('WEBLINK_ALREADY_EXISTS');
+ expect(error.msg).to.exist.to.equal(errorMessage);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should remove external link', function() {
+ // call removeLink method with valid params, should succeed
+ service.removeLink('test1', "testkey").then(function(newLink) {
+ }).catch(function(error) {
+ sinon.assert.fail('should not be called');
+ });
+ $httpBackend.flush();
+ });
+
+});
diff --git a/app/services/user.service.js b/app/services/user.service.js
index de598d536..b1006636f 100644
--- a/app/services/user.service.js
+++ b/app/services/user.service.js
@@ -25,6 +25,7 @@
updatePassword: updatePassword,
getUserProfile: getUserProfile,
getV2UserProfile: getV2UserProfile,
+ addSocialProfile: addSocialProfile,
removeSocialProfile: removeSocialProfile,
getPreference: getPreference,
setPreference: setPreference
@@ -102,6 +103,10 @@
return api.one('users', userId).get(queryParams);
}
+ function addSocialProfile(userId, profileData) {
+ return api.one('users', userId).customPOST(profileData, "profiles", {}, {});
+ }
+
function removeSocialProfile (userId, account) {
return api.one("users", userId).one("profiles", account).remove();
}
diff --git a/app/settings/edit-profile/edit-profile.controller.js b/app/settings/edit-profile/edit-profile.controller.js
index 7c908d47b..dc182e84b 100644
--- a/app/settings/edit-profile/edit-profile.controller.js
+++ b/app/settings/edit-profile/edit-profile.controller.js
@@ -3,10 +3,9 @@
angular.module('tc.settings').controller('EditProfileController', EditProfileController);
+ EditProfileController.$inject = ['$rootScope', 'userData', 'userHandle', 'ProfileService', 'ExternalAccountService', 'ExternalWebLinksService', '$log', 'ISO3166', 'ImageService', 'CONSTANTS', 'TagsService', 'toaster', '$q', '$scope'];
- EditProfileController.$inject = ['$rootScope', 'userData', 'userHandle', 'ProfileService', 'ExternalAccountService', '$log', 'ISO3166', 'ImageService', 'CONSTANTS', 'TagsService', 'toaster', '$scope'];
-
- function EditProfileController($rootScope, userData, userHandle, ProfileService, ExternalAccountService, $log, ISO3166, ImageService, CONSTANTS, TagsService, toaster, $scope) {
+ function EditProfileController($rootScope, userData, userHandle, ProfileService, ExternalAccountService, ExternalWebLinksService, $log, ISO3166, ImageService, CONSTANTS, TagsService, toaster, $q, $scope) {
$log = $log.getInstance("EditProfileCtrl");
var vm = this;
vm.toggleTrack = toggleTrack;
@@ -35,25 +34,19 @@
processData(vm.userData);
- // commenting out since this might come back
-// $scope.tracks = vm.tracks;
-// $scope.$watch('tracks', function watcher() {
-// if (!tracksValid()) {
-// toaster.pop('error', "Error", "Please select at least one track.");
-// }
-// }, true);
-
- ExternalAccountService.getLinkedExternalAccounts(vm.userData.userId).then(function(data) {
- vm.linkedExternalAccounts = data;
- });
-
- ExternalAccountService.getLinkedExternalLinksData(userHandle).then(function(data) {
- vm.linkedExternalAccountsData = data.plain();
- vm.hasLinks = _.any(_.valuesIn(_.omit(vm.linkedExternalAccountsData, ['userId', 'updatedAt','createdAt','createdBy','updatedBy','handle'])));
- })
- .catch(function(err) {
- $log.error(JSON.stringify(err));
+ var userId = vm.userData.userId;
+ var userHandle = vm.userData.handle;
+ var _linksPromises = [
+ ExternalAccountService.getAllExternalLinks(userHandle, userId, true),
+ ExternalWebLinksService.getLinks(userHandle, true)
+ ];
+ $q.all(_linksPromises).then(function(data) {
+ vm.linkedExternalAccountsData = data[0].concat(data[1]);
});
+ ExternalAccountService.getLinkedAccounts(userId)
+ .then(function(data) {
+ vm.linkedExternalAccounts = data;
+ })
TagsService.getApprovedSkillTags()
.then(function(tags) {
diff --git a/app/settings/edit-profile/edit-profile.jade b/app/settings/edit-profile/edit-profile.jade
index 132e6f603..4b977b46f 100644
--- a/app/settings/edit-profile/edit-profile.jade
+++ b/app/settings/edit-profile/edit-profile.jade
@@ -95,12 +95,17 @@
.description Show off your work and experience outside of Topcoder. Connect accounts from popular services and networks or add a link to any site.
.section-fields
+ .field-label Add a web link
+
+ .web-links
+ external-web-link(linked-accounts="vm.linkedExternalAccountsData", user-handle="{{vm.userData.handle}}")
+
.field-label Link Your Accounts
.external-links
- external-accounts(linked-accounts="vm.linkedExternalAccounts", links-data="vm.linkedExternalAccountsData", read-only="false")
+ external-accounts(linked-accounts="vm.linkedExternalAccountsData", read-only="false")
.field-label Linked Accounts
.existing-links
- external-links-data(external-links="vm.linkedExternalAccounts", linked-accounts-data="vm.linkedExternalAccountsData")
+ external-links-data(linked-accounts-data="vm.linkedExternalAccountsData")
diff --git a/app/specs.html b/app/specs.html
index bc986d6ac..d70c81ed1 100644
--- a/app/specs.html
+++ b/app/specs.html
@@ -122,6 +122,7 @@
+
@@ -199,6 +200,8 @@
+
+
@@ -242,6 +245,8 @@
+
+
@@ -263,15 +268,18 @@
-
+
+
+
-
+
+
-
+
diff --git a/app/topcoder.module.js b/app/topcoder.module.js
index 381202abb..fcb692b88 100644
--- a/app/topcoder.module.js
+++ b/app/topcoder.module.js
@@ -33,7 +33,8 @@
'angular-intro',
'ngMessages',
'angular-carousel',
- 'sticky'
+ 'sticky',
+ 'dibari.angular-ellipsis'
];
angular.module('topcoder', dependencies).run(appRun);
diff --git a/assets/css/directives/external-link-data.scss b/assets/css/directives/external-link-data.scss
index 3c068ce80..e71c29e20 100644
--- a/assets/css/directives/external-link-data.scss
+++ b/assets/css/directives/external-link-data.scss
@@ -67,7 +67,7 @@ external-accounts {
}
.bottom {
width: 220px;
- margin-top: 15px;
+ margin-top: 10px;
.handle {
@include sofia-pro-light;
font-size: 18px;
@@ -75,7 +75,7 @@ external-accounts {
}
.title {
// placeholder
- margin-top: 20px;
+ margin-top: 10px;
color: #9e9e9e;
font-size: 12px;
line-height: 14px;
@@ -117,6 +117,29 @@ external-accounts {
padding: 10px;
font-size: 50px;
}
+
+ .link-title {
+ @include sofia-pro-medium;
+ font-size: 12px;
+ line-height: 20px;
+ color: $gray-darkest;
+ margin-top: 10px;
+ height: 40px;
+ overflow: hidden;
+ padding: 0px 20px;
+ text-transform: uppercase;
+ }
+
+ .link-url {
+ font-size: 12px;
+ line-height: 14px;
+ word-wrap: break-word;
+ display: block;
+ overflow: hidden;
+ max-height: 14px;
+ padding: 0px 20px;
+ text-transform: uppercase;
+ }
}
}
@@ -175,6 +198,7 @@ external-accounts {
}
.bottom {
+ margin-top: 15px;
width: auto;
.handle {
margin-top: 20px;
@@ -183,6 +207,8 @@ external-accounts {
.title {
// placeholder
height: 40px;
+ margin-top: 15px;
+ margin-bottom: 30px;
}
ul {
@@ -213,6 +239,16 @@ external-accounts {
.logo {
padding: 10px;
font-size: 50px;
+ }
+
+ .link-title {
+ margin-top: 15px;
+ margin-bottom: 20px;
+ height: 60px;
+ }
+
+ .link-url {
+ max-height: 28px;
}
}
}
diff --git a/assets/css/directives/external-web-link.scss b/assets/css/directives/external-web-link.scss
new file mode 100644
index 000000000..6c8811045
--- /dev/null
+++ b/assets/css/directives/external-web-link.scss
@@ -0,0 +1,55 @@
+@import 'tc-includes';
+
+external-web-link {
+ .web-link {
+ margin: 6px 0 31px 0;
+
+ .form-errors {
+ position: initial;
+ width: 100%;
+ }
+
+ form {
+ display: flex;
+ flex-flow: row wrap;
+
+ .form-label {
+ @include sofia-pro-regular;
+ font-size: 12px;
+ color: black;
+ text-transform: uppercase;
+ margin-bottom: 5px;
+ margin-top: 5px;
+ }
+ .form-field {
+ @include form-field;
+ @include ui-form-placeholder;
+ &:disabled {
+ color: #B7B7B7;
+ }
+ }
+ .form-field-focused {
+ @include form-field-focused;
+ }
+
+ .validation-bar.url {
+ flex: 1;
+ width: auto;
+
+ &:before {
+ height: 40px;
+ }
+ }
+
+ input.url {
+ width: 100%;
+ margin: 0px;
+ }
+
+ button {
+ width: auto;
+ margin-left: 10px;
+ }
+ }
+ }
+}
diff --git a/bower.json b/bower.json
index 0035233e4..a46e8baca 100644
--- a/bower.json
+++ b/bower.json
@@ -28,6 +28,7 @@
"angular": "1.4.x",
"angular-cookies": "1.4.x",
"angular-dropdowns": "1.1.0",
+ "angular-ellipsis": "~0.1.6",
"angular-filter": "~0.5.4",
"angular-img-fallback": "~0.1.3",
"angular-intro.js": "~1.3.0",
diff --git a/tests/test-helpers/mock-data.js b/tests/test-helpers/mock-data.js
index a9807a3b9..e9f8a4f70 100644
--- a/tests/test-helpers/mock-data.js
+++ b/tests/test-helpers/mock-data.js
@@ -22,7 +22,9 @@ var mockData = (function() {
getMockBadge: getMockBadge,
getMockUserFinancials: getMockUserFinancials,
getMockLinkedExternalAccounts: getMockLinkedExternalAccounts,
- getMockLinkedExternalAccountsData: getMockLinkedExternalAccountsData
+ getMockLinkedExternalAccountsData: getMockLinkedExternalAccountsData,
+ getMockExternalWebLinksData: getMockExternalWebLinksData,
+ getMockAuth0Profile: getMockAuth0Profile
};
function getMockStates() {
@@ -1627,99 +1629,85 @@ var mockData = (function() {
"DESIGN": {
"challenges": 664,
"wins": 271,
- "subTracks": [
- {
- "id": 34,
- "name": "STUDIO_OTHER",
- "challenges": 21,
- "wins": 4,
- "mostRecentEventDate": "2011-04-20T09:00:00.000Z"
- },
- {
- "id": 30,
- "name": "WIDGET_OR_MOBILE_SCREEN_DESIGN",
- "challenges": 82,
- "wins": 30,
- "mostRecentEventDate": "2015-02-01T22:00:53.000Z"
- },
- {
- "id": 22,
- "name": "IDEA_GENERATION",
- "challenges": 3,
- "wins": 0,
- "mostRecentEventDate": "2013-05-27T10:00:07.000Z"
- },
- {
- "id": 17,
- "name": "WEB_DESIGNS",
- "challenges": 418,
- "wins": 190,
- "mostRecentEventDate": "2015-01-26T19:00:03.000Z"
- },
- {
- "id": 29,
- "name": "COPILOT_POSTING",
- "challenges": 1,
- "wins": 0,
- "mostRecentEventDate": null
- },
- {
- "id": 18,
- "name": "WIREFRAMES",
- "challenges": 2,
- "wins": 1,
- "mostRecentEventDate": "2010-11-17T09:00:00.000Z"
- },
- {
- "id": 14,
- "name": "ASSEMBLY_COMPETITION",
- "challenges": 1,
- "wins": 0,
- "mostRecentEventDate": null
- },
- {
- "id": 32,
- "name": "APPLICATION_FRONT_END_DESIGN",
- "challenges": 54,
- "wins": 23,
- "mostRecentEventDate": "2014-08-07T01:03:11.000Z"
- },
- {
- "id": 21,
- "name": "PRINT_OR_PRESENTATION",
- "challenges": 24,
- "wins": 8,
- "mostRecentEventDate": "2014-10-08T17:48:09.000Z"
- },
- {
- "id": 16,
- "name": "BANNERS_OR_ICONS",
- "challenges": 24,
- "wins": 10,
- "mostRecentEventDate": "2014-01-11T17:30:27.000Z"
- },
- {
- "id": 20,
- "name": "LOGO_DESIGN",
- "challenges": 31,
- "wins": 4,
- "mostRecentEventDate": "2014-02-21T19:00:09.000Z"
- },
- {
- "id": 31,
- "name": "FRONT_END_FLASH",
- "challenges": 2,
- "wins": 1,
- "mostRecentEventDate": "2009-06-19T23:00:00.000Z"
- },
- {
- "id": 13,
- "name": "TEST_SUITES",
- "challenges": 1,
- "wins": 0,
- "mostRecentEventDate": null
- }
- ],
+ "subTracks": [{
+ "id": 34,
+ "name": "STUDIO_OTHER",
+ "challenges": 21,
+ "wins": 4,
+ "mostRecentEventDate": "2011-04-20T09:00:00.000Z"
+ }, {
+ "id": 30,
+ "name": "WIDGET_OR_MOBILE_SCREEN_DESIGN",
+ "challenges": 82,
+ "wins": 30,
+ "mostRecentEventDate": "2015-02-01T22:00:53.000Z"
+ }, {
+ "id": 22,
+ "name": "IDEA_GENERATION",
+ "challenges": 3,
+ "wins": 0,
+ "mostRecentEventDate": "2013-05-27T10:00:07.000Z"
+ }, {
+ "id": 17,
+ "name": "WEB_DESIGNS",
+ "challenges": 418,
+ "wins": 190,
+ "mostRecentEventDate": "2015-01-26T19:00:03.000Z"
+ }, {
+ "id": 29,
+ "name": "COPILOT_POSTING",
+ "challenges": 1,
+ "wins": 0,
+ "mostRecentEventDate": null
+ }, {
+ "id": 18,
+ "name": "WIREFRAMES",
+ "challenges": 2,
+ "wins": 1,
+ "mostRecentEventDate": "2010-11-17T09:00:00.000Z"
+ }, {
+ "id": 14,
+ "name": "ASSEMBLY_COMPETITION",
+ "challenges": 1,
+ "wins": 0,
+ "mostRecentEventDate": null
+ }, {
+ "id": 32,
+ "name": "APPLICATION_FRONT_END_DESIGN",
+ "challenges": 54,
+ "wins": 23,
+ "mostRecentEventDate": "2014-08-07T01:03:11.000Z"
+ }, {
+ "id": 21,
+ "name": "PRINT_OR_PRESENTATION",
+ "challenges": 24,
+ "wins": 8,
+ "mostRecentEventDate": "2014-10-08T17:48:09.000Z"
+ }, {
+ "id": 16,
+ "name": "BANNERS_OR_ICONS",
+ "challenges": 24,
+ "wins": 10,
+ "mostRecentEventDate": "2014-01-11T17:30:27.000Z"
+ }, {
+ "id": 20,
+ "name": "LOGO_DESIGN",
+ "challenges": 31,
+ "wins": 4,
+ "mostRecentEventDate": "2014-02-21T19:00:09.000Z"
+ }, {
+ "id": 31,
+ "name": "FRONT_END_FLASH",
+ "challenges": 2,
+ "wins": 1,
+ "mostRecentEventDate": "2009-06-19T23:00:00.000Z"
+ }, {
+ "id": 13,
+ "name": "TEST_SUITES",
+ "challenges": 1,
+ "wins": 0,
+ "mostRecentEventDate": null
+ }],
"mostRecentEventDate": "2015-02-01T22:00:53.000Z"
},
"DATA_SCIENCE": {
@@ -1977,38 +1965,116 @@ var mockData = (function() {
function getMockLinkedExternalAccountsData() {
return {
- github: null,
- stackoverflow: null,
- dribble: null,
- behance: null,
- bitbucket: null,
- linkedin: null,
- twitter: null,
- userId: 123,
+ "updatedAt": null,
+ "createdAt": null,
+ "createdBy": null,
+ "updatedBy": null,
+ "userId": 22688955,
+ "handle": "test",
+ "behance": null,
+ "bitbucket": {
+ "handle": "test1",
+ "followers": 0,
+ "languages": "html/css",
+ "repos": 1
+ },
+ "dribbble": {
+ "handle": "test2",
+ "socialId": "944202",
+ "name": "Vikas Agarwal",
+ "summary": "Principal Engineer @Appirio",
+ "followers": 0,
+ "likes": 0,
+ "tags": null
+ },
+ "github": {
+ "handle": "test3",
+ "socialId": "2417632",
+ "publicRepos": 11,
+ "followers": 2,
+ "languages": "Java,JavaScript,HTML,CSS,Ruby"
+ },
+ "linkedin": null,
+ "stackoverflow": {
+ "name": "test",
+ "socialId": "365172",
+ "answers": 42,
+ "questions": 9,
+ "reputation": 928,
+ "topTags": "java,jsp,jstl,hashmap,quartz-scheduler,eclipse,ant,tomcat,warnings,hadoop,mysql,amazon-ec2,amazon-ebs,java-ee,amazon-web-services,amazon-rds,hibernate,scala,maven,apache-spark,apache-spark-sql,hbase,scheduling,javascript,gmail,junit,byte,persistence,hql,gdata"
+ },
+ "twitter": null,
plain: function() {}
};
}
function getMockLinkedExternalAccounts() {
- return [
- {
- providerType: 'linkedin',
- // don't care about other details
- },
- {
+ return {
+ profiles: [{
providerType: 'github'
- },
- {
+ }, {
+ providerType: 'stackoverflow'
+ }, {
providerType: 'behance'
- },
- {
+ }, {
providerType: 'dribbble'
- },
- {
+ }, {
providerType: 'bitbucket'
+ }]
+ }
+ }
+
+ function getMockExternalWebLinksData() {
+ return [
+ {
+ "userId": 111,
+ "key": "c69a1246c135b16069395010e91f5c64",
+ "handle": "test1",
+ "description": "description 1.",
+ "entities": "Activiti,Data Science,Reference Implementation for Angular Reference",
+ "keywords": "topcoder-app,merged,oct,dashboard,15appirio-tech,20appirio-tech,polish,21appirio-tech,sup-1889,19appirio-tech",
+ "title": "Test's profile",
+ "images": "https://avatars3.githubusercontent.com/u/2417632?v=3&s=400,https://avatars1.githubusercontent.com/u/2417632?v=3&s=460,https://assets-cdn.github.com/images/spinners/octocat-spinner-128.gif",
+ "source": "embed.ly",
+ "synchronizedAt": 123112
+ }, {
+ "userId": 111,
+ "key": "c69a1246c135b16069395010e91f5c65",
+ "handle": "test1",
+ "description": "description 1.",
+ "entities": "Activiti,Data Science,Reference Implementation for Angular Reference",
+ "keywords": "topcoder-app,merged,oct,dashboard,15appirio-tech,20appirio-tech,polish,21appirio-tech,sup-1889,19appirio-tech",
+ "title": "Test's profile",
+ "images": "https://avatars3.githubusercontent.com/u/2417632?v=3&s=400,https://avatars1.githubusercontent.com/u/2417632?v=3&s=460,https://assets-cdn.github.com/images/spinners/octocat-spinner-128.gif",
+ "source": "embed.ly",
+ "synchronizedAt": 123123
+ }, {
+ "userId": 111,
+ "key": "c69a1246c135b16069395010e91f5c66",
+ "handle": "test1",
+ "synchronizedAt": 0
}
- ];
+ ]
}
+ function getMockAuth0Profile() {
+ return {
+ "user_id": "mockSocialNetwork|123456",
+ "given_name": "mock",
+ "family_name": "user",
+ "first_name": "mock",
+ "last_name": "user",
+ "nickname": "mocky",
+ "name": "mock user",
+ "email": "mock@topcoder.com",
+ "username": "mockuser",
+ "identities": [
+ {
+ "access_token": "abcdefghi",
+ "access_token_secret": "abcdefghijklmnopqrstuvwxyz"
+ }
+ ]
+ };
+ }
})();