Merge branch 'master' into gitfix
This commit is contained in:
commit
91c829bd14
18 changed files with 466 additions and 34 deletions
Binary file not shown.
|
@ -1,5 +1,12 @@
|
|||
# vim: ft=nginx
|
||||
|
||||
set_real_ip_from 0.0.0.0/0;
|
||||
real_ip_recursive on;
|
||||
log_format lb_pp '$remote_addr ($proxy_protocol_addr) '
|
||||
'- $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent"'
|
||||
|
||||
types_hash_max_size 2048;
|
||||
include /usr/local/nginx/conf/mime.types.default;
|
||||
|
||||
|
|
|
@ -26,18 +26,19 @@ http {
|
|||
|
||||
# This header must be set only for HTTPS
|
||||
add_header Strict-Transport-Security "max-age=63072000; preload";
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
include proxy-protocol.conf;
|
||||
include server-base.conf;
|
||||
|
||||
listen 8443 default proxy_protocol;
|
||||
|
||||
ssl on;
|
||||
|
||||
# This header must be set only for HTTPS
|
||||
add_header Strict-Transport-Security "max-age=63072000; preload";
|
||||
|
||||
real_ip_header proxy_protocol;
|
||||
|
||||
access_log /dev/stdout lb_pp;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# vim: ft=nginx
|
||||
|
||||
set_real_ip_from 0.0.0.0/0;
|
||||
real_ip_header proxy_protocol;
|
||||
log_format elb_pp '$proxy_protocol_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent"';
|
||||
access_log /dev/stdout elb_pp;
|
|
@ -2928,4 +2928,3 @@ def revert_tag(repository, tag_name, docker_image_id):
|
|||
|
||||
return create_or_update_tag(repository.namespace_user.username, repository.name,
|
||||
tag_name, docker_image_id, reversion=True)
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ from data import model
|
|||
from data.billing import PLANS
|
||||
|
||||
import features
|
||||
import uuid
|
||||
import json
|
||||
|
||||
def carderror_response(e):
|
||||
return {'carderror': e.message}, 402
|
||||
|
@ -96,6 +98,48 @@ def get_invoices(customer_id):
|
|||
}
|
||||
|
||||
|
||||
def get_invoice_fields(user):
|
||||
try:
|
||||
cus = billing.Customer.retrieve(user.stripe_id)
|
||||
except stripe.APIConnectionError:
|
||||
abort(503, message='Cannot contact Stripe')
|
||||
|
||||
if not 'metadata' in cus:
|
||||
cus.metadata = {}
|
||||
|
||||
return json.loads(cus.metadata.get('invoice_fields') or '[]'), cus
|
||||
|
||||
|
||||
def create_billing_invoice_field(user, title, value):
|
||||
new_field = {
|
||||
'uuid': str(uuid.uuid4()).split('-')[0],
|
||||
'title': title,
|
||||
'value': value
|
||||
}
|
||||
|
||||
invoice_fields, cus = get_invoice_fields(user)
|
||||
invoice_fields.append(new_field)
|
||||
|
||||
if not 'metadata' in cus:
|
||||
cus.metadata = {}
|
||||
|
||||
cus.metadata['invoice_fields'] = json.dumps(invoice_fields)
|
||||
cus.save()
|
||||
return new_field
|
||||
|
||||
|
||||
def delete_billing_invoice_field(user, field_uuid):
|
||||
invoice_fields, cus = get_invoice_fields(user)
|
||||
invoice_fields = [field for field in invoice_fields if not field['uuid'] == field_uuid]
|
||||
|
||||
if not 'metadata' in cus:
|
||||
cus.metadata = {}
|
||||
|
||||
cus.metadata['invoice_fields'] = json.dumps(invoice_fields)
|
||||
cus.save()
|
||||
return True
|
||||
|
||||
|
||||
@resource('/v1/plans/')
|
||||
@show_if(features.BILLING)
|
||||
class ListPlans(ApiResource):
|
||||
|
@ -367,3 +411,159 @@ class OrgnaizationInvoiceList(ApiResource):
|
|||
return get_invoices(organization.stripe_id)
|
||||
|
||||
raise Unauthorized()
|
||||
|
||||
|
||||
@resource('/v1/user/invoice/fields')
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class UserInvoiceFieldList(ApiResource):
|
||||
""" Resource for listing and creating a user's custom invoice fields. """
|
||||
schemas = {
|
||||
'InvoiceField': {
|
||||
'id': 'InvoiceField',
|
||||
'type': 'object',
|
||||
'description': 'Description of an invoice field',
|
||||
'required': [
|
||||
'title', 'value'
|
||||
],
|
||||
'properties': {
|
||||
'title': {
|
||||
'type': 'string',
|
||||
'description': 'The title of the field being added',
|
||||
},
|
||||
'value': {
|
||||
'type': 'string',
|
||||
'description': 'The value of the field being added',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('listUserInvoiceFields')
|
||||
def get(self):
|
||||
""" List the invoice fields for the current user. """
|
||||
user = get_authenticated_user()
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
return {'fields': get_invoice_fields(user)[0]}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('createUserInvoiceField')
|
||||
@validate_json_request('InvoiceField')
|
||||
def post(self):
|
||||
""" Creates a new invoice field. """
|
||||
user = get_authenticated_user()
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
data = request.get_json()
|
||||
created_field = create_billing_invoice_field(user, data['title'], data['value'])
|
||||
return created_field
|
||||
|
||||
|
||||
@resource('/v1/user/invoice/field/<field_uuid>')
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class UserInvoiceField(ApiResource):
|
||||
""" Resource for deleting a user's custom invoice fields. """
|
||||
@require_user_admin
|
||||
@nickname('deleteUserInvoiceField')
|
||||
def delete(self, field_uuid):
|
||||
""" Deletes the invoice field for the current user. """
|
||||
user = get_authenticated_user()
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
result = delete_billing_invoice_field(user, field_uuid)
|
||||
if not result:
|
||||
abort(404)
|
||||
|
||||
return 'Okay', 201
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/invoice/fields')
|
||||
@path_param('orgname', 'The name of the organization')
|
||||
@related_user_resource(UserInvoiceFieldList)
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class OrganizationInvoiceFieldList(ApiResource):
|
||||
""" Resource for listing and creating an organization's custom invoice fields. """
|
||||
schemas = {
|
||||
'InvoiceField': {
|
||||
'id': 'InvoiceField',
|
||||
'type': 'object',
|
||||
'description': 'Description of an invoice field',
|
||||
'required': [
|
||||
'title', 'value'
|
||||
],
|
||||
'properties': {
|
||||
'title': {
|
||||
'type': 'string',
|
||||
'description': 'The title of the field being added',
|
||||
},
|
||||
'value': {
|
||||
'type': 'string',
|
||||
'description': 'The value of the field being added',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('listOrgInvoiceFields')
|
||||
def get(self, orgname):
|
||||
""" List the invoice fields for the organization. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
if not organization.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
return {'fields': get_invoice_fields(organization)[0]}
|
||||
|
||||
abort(403)
|
||||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('createOrgInvoiceField')
|
||||
@validate_json_request('InvoiceField')
|
||||
def post(self, orgname):
|
||||
""" Creates a new invoice field. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
if not organization.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
data = request.get_json()
|
||||
created_field = create_billing_invoice_field(organization, data['title'], data['value'])
|
||||
return created_field
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/invoice/field/<field_uuid>')
|
||||
@path_param('orgname', 'The name of the organization')
|
||||
@related_user_resource(UserInvoiceField)
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class OrganizationInvoiceField(ApiResource):
|
||||
""" Resource for deleting an organization's custom invoice fields. """
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('deleteOrgInvoiceField')
|
||||
def delete(self, orgname, field_uuid):
|
||||
""" Deletes the invoice field for the current user. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
if not organization.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
result = delete_billing_invoice_field(organization, field_uuid)
|
||||
if not result:
|
||||
abort(404)
|
||||
|
||||
return 'Okay', 201
|
||||
|
||||
abort(403)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
.billing-invoices-element .fields-menu {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.billing-invoices-element .invoice-title {
|
||||
padding: 6px;
|
||||
|
@ -22,4 +25,20 @@
|
|||
|
||||
.billing-invoices-element .fa-download {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.billing-invoices-element .fa-trash-o {
|
||||
float: right;
|
||||
margin-top: -3px;
|
||||
margin-right: -14px !important;
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.billing-invoices-element .invoice-field {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
|
@ -9,6 +9,30 @@
|
|||
</div>
|
||||
|
||||
<div ng-show="!loading && invoices.length">
|
||||
<div class="dropdown fields-menu" data-title="Custom Invoice Fields" bs-tooltip>
|
||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
<i class="fa fa-bars"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="presentation" ng-repeat="invoiceField in invoiceFields">
|
||||
<a class="invoice-field" role="menuitem" tabindex="-1" href="javascript:void(0)" >
|
||||
{{ invoiceField.title }}: {{ invoiceField.value }}
|
||||
<i class="fa fa-trash-o btn btn-danger" ng-click="askDeleteField(invoiceField)"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="disabled" ng-if="!invoiceFields.length">
|
||||
<a role="menuitem" tabindex="-1" href="javascript:void(0)">No Custom Fields Defined</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li role="presentation">
|
||||
<a role="menuitem" tabindex="-1" href="javascript:void(0)" ng-click="showCreateField()">
|
||||
<i class="fa fa-plus"></i>Add Custom Invoice Field
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<table class="co-table">
|
||||
<thead>
|
||||
<td>Billing Date/Time</td>
|
||||
|
@ -39,4 +63,23 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Delete Tag Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="createFieldInfo"
|
||||
dialog-action="createCustomField(info.title, info.value, callback)"
|
||||
dialog-title="Create Custom Field"
|
||||
dialog-action-title="Create Field">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="titleInput">Enter Field Title</label>
|
||||
<input id="titleInput" type="text" class="form-control"ng-model="createFieldInfo.title" placeholder="Field Title">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="valueInput">Enter Field Value</label>
|
||||
<input id="valueInput" type="text" class="form-control" ng-model="createFieldInfo.value" placeholder="Field Value">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
<span id="logs-range" class="mini">
|
||||
<span class="date-line">
|
||||
<span class="date-line-caption">From</span>
|
||||
<input type="text" class="logs-date-picker input-sm" name="start" ng-model="logStartDate" data-max-date="{{ logEndDate }}" data-container="body" bs-datepicker/>
|
||||
<input type="text" class="logs-date-picker input-sm" name="start" ng-model="options.logStartDate" data-max-date="{{ options.logEndDate }}" data-container="body" bs-datepicker/>
|
||||
</span>
|
||||
|
||||
<span class="date-line">
|
||||
<span class="date-line-caption add-on">to</span>
|
||||
<input type="text" class="logs-date-picker input-sm" name="end" ng-model="logEndDate" data-min-date="{{ logStartDate }}" bs-datepicker/>
|
||||
<input type="text" class="logs-date-picker input-sm" name="end" ng-model="options.logEndDate" data-min-date="{{ options.logStartDate }}" bs-datepicker/>
|
||||
</span>
|
||||
</span>
|
||||
<span class="hidden-xs right">
|
||||
|
|
|
@ -189,7 +189,8 @@
|
|||
repository="repository"
|
||||
trigger="currentSetupTrigger"
|
||||
canceled="cancelSetupTrigger(trigger)"
|
||||
counter="showTriggerSetupCounter"></div>
|
||||
counter="showTriggerSetupCounter"
|
||||
trigger-runner="askRunTrigger(trigger)"></div>
|
||||
|
||||
<!-- Manual trigger dialog -->
|
||||
<div class="manual-trigger-build-dialog"
|
||||
|
|
|
@ -122,7 +122,10 @@
|
|||
ng-click="activate()"
|
||||
ng-show="currentView == 'analyzed'">Create Trigger</button>
|
||||
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ currentView == 'postActivation' ? 'Done' : 'Cancel' }}</button>
|
||||
<button type="button" class="btn btn-success" ng-click="runTriggerNow()"
|
||||
ng-if="currentView == 'postActivation'">Run Trigger Now</button>
|
||||
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ currentView == 'postActivation' ? 'Done' : 'Cancel' }}</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
|
|
|
@ -15,6 +15,8 @@ angular.module('quay').directive('billingInvoices', function () {
|
|||
},
|
||||
controller: function($scope, $element, $sce, ApiService) {
|
||||
$scope.loading = false;
|
||||
$scope.showCreateField = null;
|
||||
$scope.invoiceFields = [];
|
||||
|
||||
var update = function() {
|
||||
var hasValidUser = !!$scope.user;
|
||||
|
@ -34,11 +36,59 @@ angular.module('quay').directive('billingInvoices', function () {
|
|||
$scope.invoices = [];
|
||||
$scope.loading = false;
|
||||
});
|
||||
|
||||
ApiService.listInvoiceFields($scope.organization).then(function(resp) {
|
||||
$scope.invoiceFields = resp.fields || [];
|
||||
}, function() {
|
||||
$scope.invoiceFields = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('organization', update);
|
||||
$scope.$watch('user', update);
|
||||
$scope.$watch('makevisible', update);
|
||||
|
||||
$scope.showCreateField = function() {
|
||||
$scope.createFieldInfo = {
|
||||
'title': '',
|
||||
'value': ''
|
||||
};
|
||||
};
|
||||
|
||||
$scope.askDeleteField = function(field) {
|
||||
bootbox.confirm('Are you sure you want to delete field ' + field.title + '?', function(r) {
|
||||
if (r) {
|
||||
var params = {
|
||||
'field_uuid': field.uuid
|
||||
};
|
||||
|
||||
ApiService.deleteInvoiceField($scope.organization, null, params).then(function(resp) {
|
||||
$scope.invoiceFields = $.grep($scope.invoiceFields, function(current) {
|
||||
return current.uuid != field.uuid
|
||||
});
|
||||
|
||||
}, ApiService.errorDisplay('Could not delete custom field'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.createCustomField = function(title, value, callback) {
|
||||
var data = {
|
||||
'title': title,
|
||||
'value': value
|
||||
};
|
||||
|
||||
if (!title || !value) {
|
||||
callback(false);
|
||||
bootbox.alert('Missing title or value');
|
||||
return;
|
||||
}
|
||||
|
||||
ApiService.createInvoiceField($scope.organization, data).then(function(resp) {
|
||||
$scope.invoiceFields.push(resp);
|
||||
callback(true);
|
||||
}, ApiService.errorDisplay('Could not create custom field'));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -24,9 +24,11 @@ angular.module('quay').directive('logsView', function () {
|
|||
$scope.chartVisible = true;
|
||||
$scope.logsPath = '';
|
||||
|
||||
$scope.options = {};
|
||||
|
||||
var datetime = new Date();
|
||||
$scope.logStartDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate() - 7);
|
||||
$scope.logEndDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate());
|
||||
$scope.options.logStartDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate() - 7);
|
||||
$scope.options.logEndDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate());
|
||||
|
||||
var defaultPermSuffix = function(metadata) {
|
||||
if (metadata.activating_username) {
|
||||
|
@ -261,9 +263,10 @@ angular.module('quay').directive('logsView', function () {
|
|||
return;
|
||||
}
|
||||
|
||||
var twoWeeksAgo = getOffsetDate($scope.logEndDate, -14);
|
||||
if ($scope.logStartDate > $scope.logEndDate || $scope.logStartDate < twoWeeksAgo) {
|
||||
$scope.logStartDate = twoWeeksAgo;
|
||||
var twoWeeksAgo = getOffsetDate($scope.options.logEndDate, -14);
|
||||
if ($scope.options.logStartDate > $scope.options.logEndDate ||
|
||||
$scope.options.logStartDate < twoWeeksAgo) {
|
||||
$scope.options.logStartDate = twoWeeksAgo;
|
||||
}
|
||||
|
||||
$scope.loading = true;
|
||||
|
@ -282,8 +285,8 @@ angular.module('quay').directive('logsView', function () {
|
|||
url = UtilService.getRestUrl('superuser', 'logs')
|
||||
}
|
||||
|
||||
url += '?starttime=' + encodeURIComponent(getDateString($scope.logStartDate));
|
||||
url += '&endtime=' + encodeURIComponent(getDateString($scope.logEndDate));
|
||||
url += '?starttime=' + encodeURIComponent(getDateString($scope.options.logStartDate));
|
||||
url += '&endtime=' + encodeURIComponent(getDateString($scope.options.logEndDate));
|
||||
|
||||
if ($scope.performer) {
|
||||
url += '&performer=' + encodeURIComponent($scope.performer.name);
|
||||
|
@ -300,7 +303,7 @@ angular.module('quay').directive('logsView', function () {
|
|||
});
|
||||
}
|
||||
|
||||
$scope.chart.draw('bar-chart', resp.logs, $scope.logStartDate, $scope.logEndDate);
|
||||
$scope.chart.draw('bar-chart', resp.logs, $scope.options.logStartDate, $scope.options.logEndDate);
|
||||
$scope.kindsAllowed = null;
|
||||
$scope.logs = resp.logs;
|
||||
$scope.loading = false;
|
||||
|
@ -329,8 +332,9 @@ angular.module('quay').directive('logsView', function () {
|
|||
$scope.$watch('repository', update);
|
||||
$scope.$watch('makevisible', update);
|
||||
$scope.$watch('performer', update);
|
||||
$scope.$watch('logStartDate', update);
|
||||
$scope.$watch('logEndDate', update);
|
||||
|
||||
$scope.$watch('options.logStartDate', update);
|
||||
$scope.$watch('options.logEndDate', update);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ angular.module('quay').directive('setupTriggerDialog', function () {
|
|||
'trigger': '=trigger',
|
||||
'counter': '=counter',
|
||||
'canceled': '&canceled',
|
||||
'activated': '&activated'
|
||||
'activated': '&activated',
|
||||
'triggerRunner': '&triggerRunner'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, UserService, TriggerService) {
|
||||
var modalSetup = false;
|
||||
|
@ -55,6 +56,11 @@ angular.module('quay').directive('setupTriggerDialog', function () {
|
|||
$('#setupTriggerModal').modal('hide');
|
||||
};
|
||||
|
||||
$scope.runTriggerNow = function() {
|
||||
$('#setupTriggerModal').modal('hide');
|
||||
$scope.triggerRunner({'trigger': $scope.trigger});
|
||||
};
|
||||
|
||||
$scope.checkAnalyze = function(isValid) {
|
||||
$scope.currentView = 'analyzing';
|
||||
$scope.pullInfo = {
|
||||
|
|
Binary file not shown.
|
@ -19,7 +19,6 @@ from endpoints.api.build import (FileDropResource, RepositoryBuildStatus, Reposi
|
|||
from endpoints.api.robot import (UserRobotList, OrgRobot, OrgRobotList, UserRobot,
|
||||
RegenerateOrgRobot, RegenerateUserRobot, UserRobotPermissions,
|
||||
OrgRobotPermissions)
|
||||
|
||||
from endpoints.api.trigger import (BuildTriggerActivate, BuildTriggerSources, BuildTriggerSubdirs,
|
||||
TriggerBuildList, ActivateBuildTrigger, BuildTrigger,
|
||||
BuildTriggerList, BuildTriggerAnalyze, BuildTriggerFieldValues)
|
||||
|
@ -33,7 +32,9 @@ from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList
|
|||
from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList
|
||||
from endpoints.api.logs import UserLogs, OrgLogs, RepositoryLogs
|
||||
from endpoints.api.billing import (UserInvoiceList, UserCard, UserPlan, ListPlans,
|
||||
OrgnaizationInvoiceList, OrganizationCard, OrganizationPlan)
|
||||
OrgnaizationInvoiceList, OrganizationCard, OrganizationPlan,
|
||||
UserInvoiceFieldList, UserInvoiceField,
|
||||
OrganizationInvoiceFieldList, OrganizationInvoiceField)
|
||||
from endpoints.api.discovery import DiscoveryResource
|
||||
from endpoints.api.organization import (OrganizationList, OrganizationMember,
|
||||
OrgPrivateRepositories, OrgnaizationMemberList,
|
||||
|
@ -43,7 +44,6 @@ from endpoints.api.organization import (OrganizationList, OrganizationMember,
|
|||
from endpoints.api.repository import RepositoryList, RepositoryVisibility, Repository
|
||||
from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPermission,
|
||||
RepositoryTeamPermissionList, RepositoryUserPermissionList)
|
||||
|
||||
from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
|
||||
SuperUserSendRecoveryEmail, UsageInformation,
|
||||
SuperUserOrganizationManagement, SuperUserOrganizationList)
|
||||
|
@ -4058,6 +4058,104 @@ class TestSuperUserManagement(ApiTestCase):
|
|||
self._run_test('DELETE', 204, 'devtable', None)
|
||||
|
||||
|
||||
class TestUserInvoiceFieldList(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(UserInvoiceFieldList)
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('GET', 401, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('GET', 404, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('GET', 404, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('GET', 200, 'devtable', None)
|
||||
|
||||
|
||||
def test_post_anonymous(self):
|
||||
self._run_test('POST', 401, None, None)
|
||||
|
||||
def test_post_freshuser(self):
|
||||
self._run_test('POST', 404, 'freshuser', dict(title='foo', value='bar'))
|
||||
|
||||
def test_post_reader(self):
|
||||
self._run_test('POST', 404, 'reader', dict(title='foo', value='bar'))
|
||||
|
||||
def test_post_devtable(self):
|
||||
self._run_test('POST', 200, 'devtable', dict(title='foo', value='bar'))
|
||||
|
||||
|
||||
class TestUserInvoiceField(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(UserInvoiceField, field_uuid='1234')
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('DELETE', 401, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('DELETE', 404, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('DELETE', 404, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('DELETE', 201, 'devtable', None)
|
||||
|
||||
|
||||
|
||||
class TestOrganizationInvoiceFieldList(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(OrganizationInvoiceFieldList, orgname='buynlarge')
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('GET', 403, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('GET', 403, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('GET', 403, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('GET', 200, 'devtable', None)
|
||||
|
||||
|
||||
def test_post_anonymous(self):
|
||||
self._run_test('POST', 403, None, dict(title='foo', value='bar'))
|
||||
|
||||
def test_post_freshuser(self):
|
||||
self._run_test('POST', 403, 'freshuser', dict(title='foo', value='bar'))
|
||||
|
||||
def test_post_reader(self):
|
||||
self._run_test('POST', 403, 'reader', dict(title='foo', value='bar'))
|
||||
|
||||
def test_post_devtable(self):
|
||||
self._run_test('POST', 200, 'devtable', dict(title='foo', value='bar'))
|
||||
|
||||
|
||||
class TestOrganizationInvoiceField(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(OrganizationInvoiceField, orgname='buynlarge', field_uuid='1234')
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('DELETE', 403, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('DELETE', 403, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('DELETE', 403, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('DELETE', 201, 'devtable', None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -25,6 +25,8 @@ def renderInvoiceToPdf(invoice, user):
|
|||
|
||||
def renderInvoiceToHtml(invoice, user):
|
||||
""" Renders a nice HTML display for the given invoice. """
|
||||
from endpoints.api.billing import get_invoice_fields
|
||||
|
||||
def get_price(price):
|
||||
if not price:
|
||||
return '$0'
|
||||
|
@ -44,7 +46,8 @@ def renderInvoiceToHtml(invoice, user):
|
|||
'invoice': invoice,
|
||||
'invoice_date': format_date(invoice.date),
|
||||
'getPrice': get_price,
|
||||
'getRange': get_range
|
||||
'getRange': get_range,
|
||||
'custom_fields': get_invoice_fields(user)[0],
|
||||
}
|
||||
|
||||
template = env.get_template('invoice.tmpl')
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
<table>
|
||||
<tr><td>Date:</td><td>{{ invoice_date }}</td></tr>
|
||||
<tr><td>Invoice #:</td><td style="font-size: 10px">{{ invoice.id }}</td></tr>
|
||||
{% for custom_field in custom_fields %}
|
||||
<tr>
|
||||
<td>*{{ custom_field['title'] }}:</td>
|
||||
<td style="font-size: 10px">{{ custom_field['value'] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -38,8 +44,8 @@
|
|||
<td style="padding: 4px; min-width: 150px;">{{ getPrice(line.amount) }}</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<td valign="right">
|
||||
|
@ -54,7 +60,7 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<div style="margin: 6px; padding: 6px; width: 100%; max-width: 640px; border-top: 2px solid #eee; text-align: center; font-size: 14px; -webkit-text-adjust: none; font-weight: bold;">
|
||||
We thank you for your continued business!
|
||||
</div>
|
||||
|
|
Reference in a new issue