- needed to position the dropdown content */
253 | .dropdown {
254 | position: relative;
255 | display: inline-block;
256 | z-index: 999;
257 | }
258 |
259 | /* Dropdown Content (Hidden by Default) */
260 | .dropdown-content {
261 | display: none;
262 | position: absolute;
263 | background-color: green;
264 | min-width: 250px;
265 | box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
266 | }
267 |
268 | /* Links inside the dropdown */
269 | .dropdown-content a {
270 | color: black;
271 | padding: 12px 16px;
272 | text-decoration: none;
273 | display: block;
274 | }
275 |
276 | /* Change color of dropdown links on hover */
277 | .dropdown-content a:hover {
278 | background-color: #6e8294
279 | }
280 |
281 | /* Show the dropdown menu on hover */
282 | .dropdown:hover .dropdown-content {
283 | display: block;
284 | background-color: #4f6375;
285 | }
286 |
287 | /* Change the background color of the dropdown button when the dropdown content is shown */
288 | .dropdown:hover .dropbtn {
289 | background-color: #4f6375;
290 | }
291 |
292 | md-card {
293 | margin: 0;
294 | }
295 |
296 | md-toast.md-default-theme .md-toast-content, md-toast .md-toast-content {
297 | margin-top: 70px;
298 | margin-right: 25px;
299 | background-color: white;
300 | color: black;
301 | text-align: center;
302 | }
303 |
304 | md-chips.md-default-theme .md-chips, md-chips .md-chips {
305 | box-shadow: none;
306 | }
307 |
308 | .md-button.light-blue {
309 | background-color: rgba(74, 163, 223, 1);
310 | }
311 |
312 | /** Pagination **/
313 | .label-primary {
314 | background-color: #286090;
315 | }
316 |
317 | /* Useful for the `New Subject` auto-complete field */
318 | .md-whiteframe-1dp, .md-whiteframe-z1 {
319 | box-shadow: none;
320 | }
321 |
322 | /*.md-whiteframe-2dp {*/
323 | /*box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12);*/
324 | /*}*/
325 |
326 | /*.md-whiteframe-2dp:focus {*/
327 | /*box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.4), 0 2px 2px 0 rgba(0, 0, 0, 0.28), 0 3px 1px -2px rgba(0, 0, 0, 0.24);*/
328 | /*}*/
329 |
330 | .md-button.light-blue {
331 | background-color: rgba(74, 163, 223, 1);
332 | }
333 |
334 | .md-button.md-fab {
335 | width: 42px;
336 | height: 42px;
337 | background-color: white;
338 | }
339 |
340 | a.md-button.md-default-theme.md-warn.md-raised, a.md-button.md-warn.md-raised, a.md-button.md-default-theme.md-warn.md-fab, a.md-button.md-warn.md-fab, .md-button.md-default-theme.md-warn.md-raised, .md-button.md-warn.md-raised, .md-button.md-default-theme.md-warn.md-fab, .md-button.md-warn.md-fab {
341 | color: rgb(255, 255, 255);
342 | background-color: rgba(169, 169, 169, 0.1);
343 | }
344 |
345 | /* Custom topics-ui CSS */
346 | md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style {
347 | min-height: 0;
348 | }
349 |
350 | .table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th {
351 | padding-top: 10px;
352 | padding-bottom: 10px;
353 | vertical-align: middle;
354 | }
355 |
356 | md-card md-card-title {
357 | padding-bottom: 10px;
358 | }
359 |
360 | /** Kafka-Connect only **/
361 | .ats-switch span.switch-right {
362 | color: #000;
363 | background: #f59800;
364 | }
365 |
366 | .md-tooltip ._md-content {
367 | height: auto;
368 | }
369 |
370 | /*Animated cogs https://codepen.io/marclloyd77/pen/tAlmd*/
371 | #development_icon {
372 | width: 100px;
373 | margin: 0;
374 | -webkit-animation: pop 0.4s ease-in;
375 | }
376 |
377 | /*Animate cogs*/
378 | #large-cog, #small-cog {
379 | -webkit-animation: spin 4s linear infinite;
380 | -webkit-transform-origin: 50% 50%;
381 | -webkit-animation-delay: 0.6s;
382 | }
383 |
384 | #small-cog {
385 | -webkit-animation: spinback 2s linear infinite;
386 | -webkit-animation-delay: 0.6s;
387 | }
388 |
389 | @-webkit-keyframes pop {
390 | 0% {
391 | -webkit-transform: scale(0);
392 | }
393 | 90% {
394 | -webkit-transform: scale(1.1);
395 | }
396 | 100% {
397 | -webkit-transform: scale(1);
398 | }
399 | }
400 |
401 | @-webkit-keyframes spin {
402 | 100% {
403 | -webkit-transform: rotate(360deg);
404 | }
405 | }
406 |
407 | @-webkit-keyframes spinback {
408 | 100% {
409 | -webkit-transform: rotate(-360deg);
410 | }
411 | }
412 |
413 | .ace_diff_new_line {
414 | background: rgba(0, 128, 0, 0.04);
415 | }
416 |
417 | .noselect {
418 | -webkit-touch-callout: none;
419 | -webkit-user-select: none;
420 | -khtml-user-select: none;
421 | -moz-user-select: none;
422 | -ms-user-select: none;
423 | user-select: none;
424 | }
425 |
426 | /* diff css */
427 | .match {
428 | display: none;
429 | color: blue;
430 | }
431 |
432 | .ins {
433 | background: rgba(55, 255, 55, 0.15);
434 | }
435 |
436 | .del {
437 | background: rgba(243, 59, 59, 0.15);
438 | }
439 |
440 | /* Essential for dirPagination material design */
441 | .pagination .md-button {
442 | min-width: 30px;
443 | margin: 5px 8px;
444 | }
445 |
446 | /* Essential for dirPagination material design */
447 | .md-button {
448 | min-width: 70px;
449 | margin: 5px 8px;
450 | color: #333;
451 | }
452 |
453 | .selectedListItem {
454 | background-color: #DDDDDD;
455 | }
456 |
457 | /*Funny color*/
458 | .md-fab:hover, .md-fab.md-focused {
459 | background-color: rgba(230, 100, 0, .9) !important;
460 | }
461 |
462 | md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style {
463 | height: 48px;
464 | }
465 |
466 | md-content {
467 | background-color: white;
468 | }
469 |
470 | .md-button.md-default-theme.md-primary.md-raised.newschemabutton {
471 | margin-right: 4px;
472 | background-color: #448AFF;
473 | color: white;
474 | }
475 |
476 | md-list-item.md-2-line.shemaslistitem {
477 | width: 100%;
478 | }
479 |
480 | .md-2-line.shemaslistitem a .md-button.md-warn.md-raised.divlistitem {
481 | min-width: 100%;
482 | text-align: left;
483 | box-shadow: 0 0 0 0 rgba(0, 0, 0, .26) !important;
484 | }
485 |
486 | .md-raised.md-warn.md-button.versionbox {
487 | box-shadow: 0 0 0 0 rgba(0, 0, 0, .46);
488 | min-width: initial;
489 | font-size: 75%;
490 | line-height: 17px;
491 | min-height: 18px;
492 | text-transform: none;
493 | background-color: rgba(44, 152, 240, 0.3);
494 | color: black;
495 | text-align: left;
496 | float: right;
497 | padding: 3px 12px;
498 | margin-top: 10px;
499 | }
500 |
501 | .md-raised.md-warn.md-button.versionbox.moreversions {
502 | background: rgb(139, 195, 74);
503 | }
504 |
505 | .md-raised.md-warn.md-button.md-ink-ripple.editbutton,
506 | .md-raised.md-warn.md-button.md-ink-ripple.testbutton {
507 | padding-left: 20px;
508 | padding-right: 20px;
509 | background-color: #448AFF;
510 | color: white;
511 | }
512 |
513 | .md-default-theme md-input-container .md-errors-spacer {
514 | min-height: 0;
515 | }
516 |
517 | span.title {
518 | margin: 0;
519 | padding: 0;
520 | line-height: 44px;
521 | font-size: 14px;
522 | }
523 |
524 | md-tab-content md-content {
525 | overflow: auto
526 | }
527 |
528 | .flex-2 {
529 | width: 50%;
530 | float: left;
531 | }
532 |
533 | md-switch.md-default-theme.md-checked .md-thumb, md-switch.md-checked .md-thumb {
534 | background-color: rgb(65, 191, 236);
535 | }
536 |
537 | md-switch.md-default-theme.md-checked .md-bar, md-switch.md-checked .md-bar {
538 | background-color: rgba(65, 191, 236, 0.6);
539 | }
540 |
541 | .seperator:last-child {
542 | border-top: 2px dashed #aaa;
543 | margin: 40px 0 30px;
544 | }
545 |
546 |
547 |
548 | /* The Modal (background) */
549 | .modal {
550 | display: none; /* Hidden by default */
551 | position: fixed; /* Stay in place */
552 | z-index: 1; /* Sit on top */
553 | left: 0;
554 | top: 0;
555 | width: 100%; /* Full width */
556 | height: 100%; /* Full height */
557 | overflow: auto; /* Enable scroll if needed */
558 | background-color: rgb(0,0,0); /* Fallback color */
559 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
560 | z-index: 999999;
561 | }
562 |
563 | /* Modal Content/Box */
564 | .modal-content {
565 | background-color: #fefefe;
566 | margin: 5% auto; /* 15% from the top and centered */
567 | padding: 20px;
568 | border: 1px solid #888;
569 | width: 1200px; /* Could be more or less, depending on screen size */
570 | }
571 |
572 | /* The Close Button */
573 | .close {
574 | color: #aaa;
575 | float: right;
576 | font-size: 28px;
577 | font-weight: bold;
578 | }
579 |
580 | .close:hover,
581 | .close:focus {
582 | color: black;
583 | text-decoration: none;
584 | cursor: pointer;
585 | }
586 |
587 | .md-button.btn-danger {
588 | background:#d05653;
589 | color:white;
590 | }
591 |
592 | .md-button.btn-danger:hover {
593 | background:#a7110d;
594 |
595 | }
--------------------------------------------------------------------------------
/src/schema-registry/new/new.controller.js:
--------------------------------------------------------------------------------
1 | var angular = require('angular');
2 | var angularAPP = angular.module('angularAPP');
3 |
4 | var NewSubjectCtrl = function ($scope, $route, $rootScope, $http, $log, $q, $location, UtilsFactory, SchemaRegistryFactory, toastFactory, env) {
5 | $log.debug("NewSubjectCtrl - initiating");
6 |
7 | $scope.$on('$routeChangeSuccess', function () {
8 | $scope.cluster = env.getSelectedCluster().NAME;//$routeParams.cluster;
9 | });
10 |
11 | $scope.noSubjectName = true;
12 | $rootScope.listChanges = false;
13 | toastFactory.hideToast();
14 |
15 | $scope.showSimpleToast = function (message) {
16 | toastFactory.showSimpleToast(message)
17 | };
18 | $scope.showSimpleToastToTop = function (message) {
19 | toastFactory.showSimpleToastToTop(message);
20 | };
21 |
22 | $scope.hideToast = function () {
23 | toastFactory.hide();
24 | };
25 |
26 | $scope.$watch(function () {
27 | return $scope.text;
28 | }, function (a) {
29 | $scope.allowCreateOrEvolution = false;
30 | updateCurl();
31 | }, true);
32 |
33 | $scope.$watch(function () {
34 | return $scope.newAvroString;
35 | }, function (a) {
36 | $scope.allowCreateOrEvolution = false;
37 | updateCurl();
38 | }, true);
39 |
40 |
41 | /**
42 | * Create filter function for a query string
43 | */
44 | function createFilterFor(query) {
45 | var lowercaseQuery = angular.lowercase(query);
46 | return function filterFn(state) {
47 | return (state.value.indexOf(lowercaseQuery) === 0);
48 | };
49 | }
50 |
51 | /**
52 | * Possibilities
53 | * 1. no-subject-name -> User has not filled-in the subjectName
54 | * 2. not-json -> Schema is invalid Json
55 | * 3. new-schema -> Schema is Json + subject does not exist
56 | */
57 | $scope.allowCreateOrEvolution = false;
58 | var validTypes = ["null", "double", "string", "record", "int", "float", "long", "array", "boolean", "enum", "map", "fixed", "bytes", "type"];
59 | var primitiveTypes = ["null", "boolean", "int", "long", "float", "double", "bytes", "string"];
60 |
61 | function testCompatibility(subject, newAvroString) {
62 | if (env.readonlyMode()) {
63 | var deferred = $q.defer();
64 | $scope.showSimpleToastToTop("Creation is not allowed in readonly mode");
65 | deferred.resolve("readonly");
66 | return deferred.promise;
67 | }
68 |
69 | $scope.notValidType = false;
70 |
71 | if (newAvroString === "null") {
72 | if (primitiveTypes.indexOf(newAvroString) === -1) {
73 | $scope.wrongType = newAvroString;
74 | $scope.notValidType = true;
75 | }
76 | } else {
77 | var a;
78 | try {
79 | a = JSON.parse(newAvroString);
80 | console.log("It's probably object, so checking types", a)
81 | } catch (e) {
82 | if (typeof(newAvroString) === "string") {
83 | if (primitiveTypes.indexOf(newAvroString) === -1) {
84 | $scope.wrongType = newAvroString;
85 | $scope.notValidType = true;
86 | }
87 | }
88 |
89 | }
90 | }
91 |
92 | var flattenObject = function (ob) {
93 | var toReturn = {};
94 |
95 | for (var i in ob) {
96 | if (!ob.hasOwnProperty(i)) continue;
97 |
98 | if ((typeof ob[i]) === 'object') {
99 | var flatObject = flattenObject(ob[i]);
100 | for (var x in flatObject) {
101 | if (!flatObject.hasOwnProperty(x)) continue;
102 | toReturn[i + '.' + x] = flatObject[x];
103 | }
104 |
105 | } else {
106 | toReturn[i] = ob[i];
107 | }
108 | }
109 | return toReturn;
110 | };
111 |
112 | var obj = flattenObject(newAvroString);
113 | var typeKeysToCheck = Object.keys(obj)
114 | .reduce(function (typeKeys, key, idx) {
115 | // Check that this string has a type substring, if not, we don't have to validate
116 | if (key.indexOf('type') !== -1 && isPrimitiveTypeKey(key)) {
117 | typeKeys.push(key);
118 | }
119 |
120 | return typeKeys;
121 | }, []);
122 |
123 | var typeKeysToCheckLength = typeKeysToCheck.length;
124 |
125 | // Create for loop vars
126 | var i;
127 | var keyToCheck;
128 |
129 | /*
130 | * By iterating in a for loop, we can break out of an invalid key type found immediately.
131 | * That way the UI shows each wrong type one by one(if there are many) instead of just the last
132 | * one.
133 | */
134 | for (i = 0; i < typeKeysToCheckLength; i++) {
135 | keyToCheck = typeKeysToCheck[i];
136 |
137 | if (validTypes.indexOf(obj[keyToCheck]) < 0) {
138 | $scope.wrongType = obj[keyToCheck];
139 | $scope.notValidType = true;
140 |
141 | break;
142 | }
143 | }
144 |
145 | function isKeyType(key) {
146 | return key === 'type';
147 | }
148 |
149 | function checkLastTwoKeyParts(lastKeyPart, nextToLastKeyPart) {
150 | var isLastKeyPartNotANumber = isNaN(lastKeyPart);
151 |
152 | // If it is not a number, then make sure it's a type key
153 | if (isLastKeyPartNotANumber) {
154 | return isKeyType(lastKeyPart);
155 | }
156 |
157 | // If the last part was a number, is the next to last a type part?
158 | return isKeyType(nextToLastKeyPart);
159 | };
160 |
161 | // Check if they key is actually a key that defines the primitive type
162 | function isPrimitiveTypeKey(key) {
163 | var keyToArray = key.split('.');
164 | var keyToArrayLength = keyToArray.length;
165 | var lastKeyPart = keyToArray[keyToArrayLength - 1];
166 | var nextToLastKeyPart = keyToArray[keyToArrayLength - 2];
167 |
168 | return keyToArrayLength === 1 || checkLastTwoKeyParts(lastKeyPart, nextToLastKeyPart);
169 | }
170 |
171 | newAvroString = JSON.stringify(newAvroString);
172 |
173 | var deferred = $q.defer();
174 |
175 | if ((subject === undefined) || subject.length === 0) {
176 | $scope.showSimpleToastToTop("Please fill in the subject name"); // (1.)
177 | $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)";
178 | deferred.resolve("no-subject-name");
179 | } else {
180 | if ($scope.notValidType) {
181 | $scope.showSimpleToastToTop($scope.wrongType + " is not valid"); // (2.)
182 | $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
183 | deferred.resolve("not-valid-type")
184 | } else if (!UtilsFactory.IsJsonString(newAvroString)) {
185 | $scope.showSimpleToastToTop("This schema is not valid"); // (2.)
186 | $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
187 | deferred.resolve("not-json")
188 | } else {
189 | var latestKnownSubject = SchemaRegistryFactory.getLatestSubjectFromCache(subject);
190 | if (latestKnownSubject === undefined) {
191 | // (3.)
192 | $scope.createOrEvolve = "Create new schema";
193 | $scope.showSimpleToast("This will be a new Subject");
194 | $scope.allowCreateOrEvolution = true;
195 | $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)";
196 | $log.info('Valid schema');
197 | deferred.resolve("new-schema")
198 | } else {
199 | SchemaRegistryFactory.testSchemaCompatibility($scope.text, $scope.newAvroString).then(
200 | function success(data) {
201 | $log.info("Success in testing schema compatibility " + data);
202 | // (4.)
203 | $scope.allowCreateOrEvolution = false;
204 | $scope.showSimpleToastToTop("Schema exists, please select a unique subject name");
205 | $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
206 | deferred.resolve("non-compatible")
207 | },
208 | function failure(data) {
209 | $scope.showSimpleToastToTop("Failure with - " + data);
210 | deferred.resolve("failure");
211 | }
212 | );
213 | }
214 | }
215 | }
216 |
217 | return deferred.promise;
218 | }
219 |
220 | /**
221 | * Update curl to reflect selected subject + schema
222 | */
223 | function updateCurl() {
224 | //$log.debug("Updating curl commands accordingly");
225 | var remoteSubject = "FILL_IN_SUBJECT";
226 | if (($scope.text !== undefined) && $scope.text.length > 0) {
227 | remoteSubject = $scope.text;
228 | }
229 | if (JSON.stringify($scope.newAvroString)) {
230 | var curlPrefix = 'curl -vs --stderr - -XPOST -i -H "Content-Type: application/vnd.schemaregistry.v1+json" --data ';
231 | $scope.curlCommand =
232 | "\n" +
233 | "// Register new schema\n" + curlPrefix +
234 | "'" + '{"schema":"' + JSON.stringify($scope.newAvroString).replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") +
235 | '"}' + "' " + env.SCHEMA_REGISTRY() + "/subjects/" + remoteSubject + "/versions";
236 | }
237 | }
238 |
239 | /**
240 | * Private method to register-new-schema
241 | */
242 | function registerNewSchemaPrivate(newSubject, newAvro) {
243 |
244 | var deferred = $q.defer();
245 | SchemaRegistryFactory.registerNewSchema(newSubject, newAvro).then(
246 | function success(id) {
247 | $log.info("Success in registering new schema " + id);
248 | var schemaId = id;
249 | $scope.showSimpleToastToTop("Schema ID : " + id);
250 | $rootScope.listChanges = true; // trigger a cache re-load
251 | $location.path('/cluster/' + $scope.cluster + '/schema/' + newSubject + '/version/latest');
252 | deferred.resolve(schemaId);
253 | },
254 | function error(data, status) {
255 | $log.info("Error on schema registration : " + JSON.stringify(data));
256 | var errorMessage = data.message;
257 | $scope.showSimpleToastToTop(errorMessage);
258 | if (status >= 400) {
259 | $log.debug("Schema registrations is not allowed " + status + " " + data);
260 | } else {
261 | $log.debug("Schema registration failure: " + JSON.stringify(data));
262 | }
263 | deferred.reject(errorMessage);
264 | });
265 |
266 | return deferred.promise;
267 |
268 | }
269 |
270 | $scope.testCompatibility = function () {
271 | return testCompatibility($scope.text, $scope.newAvroString);
272 | };
273 |
274 | /**
275 | * How to responde to register new schema clicks
276 | */
277 | $scope.registerNewSchema = function () {
278 | var subject = $scope.text;
279 | testCompatibility(subject, $scope.newAvroString).then(
280 | function success(response) {
281 | // no-subject-name | not-json | new-schema | compatible | non-compatible | failure | readonly
282 | switch (response) {
283 | case "no-subject-name":
284 | case "not-json":
285 | case "not-valid-type":
286 | case "failure":
287 | case "non-compatible":
288 | case "readonly":
289 | $log.debug("registerNewSchema - cannot do anything more with [ " + response + " ]");
290 | break;
291 | case 'new-schema':
292 | var schemaString = '';
293 | if (typeof $scope.newAvroString !== 'string')
294 | schemaString = JSON.stringify($scope.newAvroString);
295 | else
296 | schemaString = $scope.newAvroString;
297 | registerNewSchemaPrivate(subject, schemaString).then(
298 | function success(newSchemaId) {
299 | $log.info("New subject id after posting => " + newSchemaId);
300 | },
301 | function failure(data) {
302 | $log.error("peiler2=>" + data);
303 | $scope.allowCreateOrEvolution = false;
304 | $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
305 | });
306 | break;
307 | case 'compatible':
308 | $log.info("Compatibility [compatible]");
309 | // TODO
310 | var latestKnownSubject = SchemaRegistryFactory.getLatestSubjectFromCache(subject);
311 | if (latestKnownSubject === undefined) {
312 | $log.error("This should never happen.")
313 | } else {
314 | $log.info("Existing schema id = " + latestKnownSubject.version);
315 | registerNewSchemaPrivate(subject, $scope.newAvroString).then(
316 | function success(newSchemaId) {
317 | $log.info("New subject id after posting => " + newSchemaId);
318 | if (latestKnownSubject.version === newSchemaId) {
319 | toastFactory.showSimpleToastToTop("The schema you posted was same to the existing one")
320 | }
321 | },
322 | function failure(data) {
323 | $log.error("peiler=>" + data);
324 | $scope.allowCreateOrEvolution = false;
325 | $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
326 | });
327 | break;
328 | }
329 | default:
330 | $log.warn("Should never come here " + response);
331 | }
332 | },
333 | function failure(data) {
334 | if (data.error_code === 500) {
335 | $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
336 | toastFactory.showSimpleToastToTop("Not a valid avro");
337 | }
338 | else {
339 | $log.error("Could not test compatibilitydasdas", data);
340 | }
341 | });
342 |
343 | };
344 |
345 | // $scope.createOrEvolve = "Create new schema";
346 | // $scope.allowCreateOrEvolution = true;
347 | // $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)";
348 |
349 |
350 | // $http(postSchemaRegistration)
351 | // $http.get(env.SCHEMA_REGISTRY() + '/subjects/' + $scope.text + '/versions/latest')
352 | // .success(function (data) {
353 | // $log.info("Schema succesfully registered: " + JSON.stringify(data));
354 | // $location.path('/subjects/' + data.subject + '/version/' + data.version);
355 | // });
356 | // }
357 |
358 | // When the 'Ace' of the schema/new is loaded
359 | $scope.newSchemaAceLoaded = function (_editor) {
360 | $scope.editor = _editor;
361 | $scope.editor.$blockScrolling = Infinity;
362 | $scope.aceSchemaSession = _editor.getSession(); // we can get data on changes now
363 | var lines = $scope.newAvroString.split("\n").length;
364 | // TODO : getScalaFiles($scope.aceString);
365 | // Add one extra line for each command > 110 characters
366 | angular.forEach($scope.newAvroString.split("\n"), function (line) {
367 | lines = lines + Math.floor(line.length / 110);
368 | });
369 | if (lines <= 1) {
370 | lines = 10;
371 | }
372 | _editor.setOptions({
373 | minLines: lines + 1,
374 | maxLines: lines + 1,
375 | highlightActiveLine: false
376 | });
377 | updateCurl();
378 | };
379 |
380 | // When the 'Ace' of the schema/new is CHANGED (!)
381 | $scope.newSchemaAceChanged = function (_editor) {
382 | $scope.editor = _editor;
383 | updateCurl();
384 |
385 | };
386 |
387 | // When the 'Ace' of the curl command is loaded
388 | $scope.curlCommandAceLoaded = function (_editor) {
389 | $scope.editor = _editor;
390 | $scope.editor.$blockScrolling = Infinity;
391 | };
392 |
393 |
394 | $scope.newAvroString =
395 | angular.toJson(
396 | {
397 | "type": "record",
398 | "name": "evolution",
399 | "doc": "This is a sample Avro schema to get you started. Please edit",
400 | "namespace": "com.landoop",
401 | "fields": [{"name": "name", "type": "string"}, {"name": "number1", "type": "int"}, {
402 | "name": "number2",
403 | "type": "float"
404 | }]
405 | }, true);
406 |
407 | };
408 |
409 | NewSubjectCtrl.$inject = ['$scope', '$route', '$rootScope', '$http', '$log', '$q', '$location', 'UtilsFactory', 'SchemaRegistryFactory', 'toastFactory', 'env']
410 |
411 | angularAPP.controller('NewSubjectCtrl', NewSubjectCtrl);
412 |
413 |
--------------------------------------------------------------------------------
/src/schema-registry/view/view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
46 |
47 |
48 |
52 | version {{subjectObject.version}}
53 |
54 |
55 |
56 |
57 | version {{subjectObject.version}}
58 |
59 |
60 |
61 |
62 |
63 | version {{version}}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
77 |
78 | EDIT
79 |
80 |
81 |
84 |
85 | CANCEL
86 |
87 |
88 |
89 |
90 | DELETE
91 |
92 |
93 |
94 |
95 | Delete Latest Version
96 |
97 |
98 |
99 |
100 | Delete Selected Version ({{subjectObject.version}})
101 |
102 |
103 |
104 |
105 | Delete Subject
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Schema
{{subjectObject.subjectName}} version: {{versionToBeDeleted}} will be
deleted.
115 |
116 |
117 |
118 | DELETE
119 |
120 |
121 |
122 | CANCEL
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Schema
131 |
132 |
133 |
134 |
135 |
136 |
151 |
152 |
153 |
159 | VALIDATE
160 |
161 |
162 |
167 |
168 | EVOLVE SCHEMA
169 |
170 |
171 |
172 |
173 |
174 |
175 | Info
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
186 |
187 |
188 | type: {{subjectObject.Schema.type}}
189 | name: {{subjectObject.Schema.name}}
190 | namespace: {{subjectObject.Schema.namespace}}
191 |
192 |
193 |
194 | {{subjectObject.Schema.doc}}
195 |
196 |
197 |
198 |
199 |
200 |
201 |
203 |
204 |
205 | | Name |
206 | Type |
207 | Default |
208 | Documentation |
209 |
210 |
211 |
212 |
213 | | {{s.name}} |
214 |
215 |
216 | {{s.type}}
217 | |
218 | {{s.default}} |
219 | {{s.doc}} |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
230 |
231 |
232 | type: {{schemas.type}}
233 | name: {{schemas.name}}
234 | namespace: {{schemas.namespace}}
235 |
236 |
237 |
238 | {{subjectObject.Schema.doc}}
239 |
240 |
241 |
242 |
245 |
246 |
247 | | Name |
248 | Type |
249 | Default |
250 | Documentation |
251 |
252 |
253 |
254 |
255 | | {{s.name}} |
256 |
257 |
258 | {{s.type}}
259 | |
260 | {{s.default}} |
261 | {{s.doc}} |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 | Config
275 |
276 |
277 |
278 |
279 |
280 |
296 |
297 | Successfully changed compatibility level to {{compatibilitySelect}}
298 |
299 |
300 |
301 |
302 |
303 |
304 | History
305 |
306 |
307 |
308 |
309 |
310 |
Version {{x.version}} (Schema ID: {{x.id}})
311 |
314 |
315 | Version 1 (Schema ID: {{completeSubjectHistory[0].id}})
317 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
--------------------------------------------------------------------------------
/src/factories/schema-registry-factory.js:
--------------------------------------------------------------------------------
1 | var angular = require('angular');
2 | var angularAPP = angular.module('angularAPP');
3 |
4 | /**
5 | * Schema-Registry angularJS Factory
6 | *
7 | * Landoop - version 0.9.x (May.2017)
8 | */
9 | var SchemaRegistryFactory = function ($rootScope, $http, $location, $q, $log, UtilsFactory, env) {
10 |
11 | /**
12 | * Get subjects
13 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects
14 | */
15 | function getSubjects() {
16 |
17 | var url = env.SCHEMA_REGISTRY() + '/subjects/';
18 | $log.debug(" curl -X GET " + url);
19 | var start = new Date().getTime();
20 |
21 | var deferred = $q.defer();
22 | $http.get(url)
23 | .then(
24 | function successCallback(response) {
25 | var allSubjectNames = response.data;
26 | $log.debug(" curl -X GET " + url + " => " + allSubjectNames.length + " registered subjects in [ " + ((new Date().getTime()) - start) + " ] msec");
27 | deferred.resolve(allSubjectNames);
28 | },
29 | function errorCallback(response) {
30 | deferred.reject("Failure with : " + response)
31 | });
32 |
33 | return deferred.promise;
34 | }
35 |
36 | /**
37 | * Get subjects versions
38 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects-(string- subject)-versions
39 | */
40 | function getSubjectsVersions(subjectName) {
41 |
42 | var url = env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/';
43 | $log.debug(" curl -X GET " + url);
44 | var start = new Date().getTime();
45 |
46 | var deferred = $q.defer();
47 | $http.get(url).then(
48 | function successCallback(response) {
49 | var allVersions = response.data;
50 | $log.debug(" curl -X GET " + url + " => " + JSON.stringify(allVersions) + " versions in [ " + (new Date().getTime() - start) + " ] msec");
51 | deferred.resolve(allVersions);
52 | },
53 | function errorCallback(response) {
54 | var msg = "Failure with : " + response + " " + JSON.stringify(response);
55 | $log.error("Error in getting subject versions : " + msg);
56 | deferred.reject(msg);
57 | });
58 |
59 | return deferred.promise;
60 |
61 | }
62 |
63 | /**
64 | * Get a specific version of the schema registered under this subject
65 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects-(string- subject)-versions-(versionId- version)
66 | */
67 | function getSubjectAtVersion(subjectName, version) {
68 |
69 | var url = env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version;
70 | $log.debug(" curl -X GET " + url);
71 |
72 | var deferred = $q.defer();
73 | var start = new Date().getTime();
74 | $http.get(url).then(
75 | function successCallback(response) {
76 | var subjectInformation = response.data;
77 | $log.debug(" curl -X GET " + url + " => [" + subjectName + "] subject " + JSON.stringify(subjectInformation).length + " bytes in [ " + (new Date().getTime() - start) + " ] msec");
78 | deferred.resolve(subjectInformation);
79 | },
80 | function errorCallback(response) {
81 | var msg = "Failure getting subject at version : " + response + " " + JSON.stringify(response);
82 | $log.error(msg);
83 | deferred.reject(msg);
84 | });
85 |
86 | return deferred.promise;
87 |
88 | }
89 |
90 | function getAllSchemas(cache) {
91 | var i;
92 | var allSchemasCache = [];
93 | angular.forEach(cache, function (schema) {
94 | for (i = 1; i <= schema.version; i++) {
95 | getSubjectAtVersion(schema.subjectName, i).then(function (selectedSubject) {
96 | allSchemasCache.push(selectedSubject)
97 | //$rootScope.downloadFile += '\n echo >>>' + selectedSubject.subject +'.'+ selectedSubject.version + '.json <<< \n' + schema.schema + ' \n \n EOF';
98 | })
99 | }
100 | });
101 | $rootScope.allSchemasCache = allSchemasCache;
102 | return allSchemasCache
103 | }
104 |
105 | /**
106 | * Register a new schema under the specified subject. If successfully registered, this returns the unique identifier of this schema in the registry.
107 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--subjects-(string- subject)-versions
108 | */
109 | function postNewSubjectVersion(subjectName, newSchema) {
110 |
111 | var deferred = $q.defer();
112 | $log.debug("Posting new version of subject [" + subjectName + "]");
113 |
114 | var postSchemaRegistration = {
115 | method: 'POST',
116 | url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + "/versions",
117 | data: '{"schema":"' + newSchema.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'",
118 | dataType: 'json',
119 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
120 | };
121 |
122 | $http(postSchemaRegistration)
123 | .success(function (data) {
124 | //$log.info("Success in registering new schema " + JSON.stringify(data));
125 | var schemaId = data.id;
126 | deferred.resolve(schemaId);
127 | })
128 | .error(function (data, status) {
129 | $log.info("Error on schema registration : " + JSON.stringify(data));
130 | var errorMessage = data.message;
131 | if (status >= 400) {
132 | $log.debug("Schema registrations is not allowed " + status + " " + data);
133 | } else {
134 | $log.debug("Schema registration failure: " + JSON.stringify(data));
135 | }
136 | deferred.reject(data);
137 | });
138 |
139 | return deferred.promise;
140 |
141 | }
142 |
143 | /**
144 | * Check if a schema has already been registered under the specified subject. If so, this returns the schema string
145 | * along with its globally unique identifier, its version under this subject and the subject name.
146 | *
147 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--subjects-(string- subject)
148 | */
149 | function checkSchemaExists(subjectName, subjectInformation) {
150 |
151 | var deferred = $q.defer();
152 | $log.debug("Checking if schema exists under this subject [" + subjectName + "]");
153 |
154 | var postSchemaExists = {
155 | method: 'POST',
156 | url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName,
157 | data: '{"schema":"' + subjectInformation.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'",
158 | dataType: 'json',
159 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
160 | };
161 |
162 | $http(postSchemaExists)
163 | .success(function (data) {
164 | var response = {
165 | id: data.id,
166 | version: data.version
167 | };
168 | $log.info("Response : " + JSON.stringify(response));
169 | deferred.resolve(response);
170 | })
171 | .error(function (data, status) {
172 | $log.info("Error while checking if schema exists under a subject : " + JSON.stringify(data));
173 | var errorMessage = data.message;
174 | if (status === 407) {
175 | $log.debug("Subject not found or schema not found - 407 - " + status + " " + data);
176 | } else {
177 | $log.debug("Some other failure: " + JSON.stringify(data));
178 | }
179 | $defered.reject("Something")
180 | });
181 |
182 | return deferred.promise;
183 |
184 | }
185 |
186 | /**
187 | * Test input schema against a particular version of a subject’s schema for compatibility.
188 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--compatibility-subjects-(string- subject)-versions-(versionId- version)
189 | */
190 | function testSchemaCompatibility(subjectName, subjectInformation) {
191 |
192 | var deferred = $q.defer();
193 | $log.debug(" Testing schema compatibility for [" + subjectName + "]");
194 |
195 | var postCompatibility = {
196 | method: 'POST',
197 | url: env.SCHEMA_REGISTRY() + '/compatibility/subjects/' + subjectName + "/versions/latest",
198 | data: '{"schema":"' + subjectInformation.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'",
199 | dataType: 'json',
200 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
201 | };
202 |
203 | $http(postCompatibility)
204 | .success(function (data) {
205 | $log.info("Success in testing schema compatibility " + JSON.stringify(data));
206 | deferred.resolve(data.is_compatible)
207 | })
208 | .error(function (data, status) {
209 | $log.warn("Error on check compatibility : " + JSON.stringify(data));
210 | if (status === 404) {
211 | if (data.error_code === 40401) {
212 | $log.warn("40401 = Subject not found");
213 | }
214 | $log.warn("[" + subjectName + "] is a non existing subject");
215 | deferred.resolve("new"); // This will be a new subject (!)
216 | } else {
217 | $log.error("HTTP > 200 && < 400 (!) " + JSON.stringify(data));
218 | }
219 | deferred.reject(data);
220 | });
221 |
222 | return deferred.promise;
223 |
224 | }
225 |
226 | /**
227 | * Put global config (Test input schema against a particular version of a subject’s schema for compatibility.
228 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#put--config
229 | */
230 | function putConfig(compatibilityLevel) {
231 |
232 | var deferred = $q.defer();
233 |
234 | if (["NONE", "FULL", "FORWARD", "BACKWARD", "FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"].indexOf(compatibilityLevel) !== -1) {
235 |
236 | var putConfig = {
237 | method: 'PUT',
238 | url: env.SCHEMA_REGISTRY() + '/config',
239 | data: '{"compatibility":"' + compatibilityLevel + '"}' + "'",
240 | dataType: 'json',
241 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
242 | };
243 |
244 | $http(putConfig)
245 | .success(function (data) {
246 | $log.info("Success in changing global schema-registry compatibility " + JSON.stringify(data));
247 | deferred.resolve(data.compatibility)
248 | })
249 | .error(function (data, status) {
250 | $log.info("Error on changing global compatibility : " + JSON.stringify(data));
251 | if (status === 422) {
252 | $log.warn("Invalid compatibility level " + JSON.stringify(status) + " " + JSON.stringify(data));
253 | if (JSON.stringify(data).indexOf('50001') > -1) {
254 | $log.error(" Error in the backend data store - " + $scope.text);
255 | } else if (JSON.stringify(data).indexOf('50003') > -1) {
256 | $log.error("Error while forwarding the request to the master: " + JSON.stringify(data));
257 | }
258 | } else {
259 | $log.debug("HTTP > 200 && < 400 (!) " + JSON.stringify(data));
260 | }
261 | deferred.reject(data);
262 | });
263 |
264 | } else {
265 | $log.warn("Compatibility level:" + compatibilityLevel + " is not supported");
266 | deferred.reject();
267 | }
268 |
269 | return deferred.promise;
270 |
271 | }
272 |
273 | /**
274 | * Get global compatibility-level config
275 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--config
276 | */
277 | function getGlobalConfig() {
278 |
279 | var deferred = $q.defer();
280 | var url = env.SCHEMA_REGISTRY() + '/config';
281 | $log.debug(" curl -X GET " + url);
282 | var start = new Date().getTime();
283 | $http.get(url)
284 | .success(function (data) {
285 | $log.debug(" curl -X GET " + url + " => in [ " + ((new Date().getTime()) - start) + "] msec");
286 | deferred.resolve(data)
287 | })
288 | .error(function (data, status) {
289 | deferred.reject("Get global config rejection : " + data + " " + status)
290 | });
291 |
292 | return deferred.promise;
293 |
294 | }
295 |
296 | function getSubjectConfig(subjectName) {
297 | var deferred = $q.defer();
298 | var url = env.SCHEMA_REGISTRY() + '/config/' + subjectName;
299 | $log.debug(" curl -X GET " + url);
300 | var start = new Date().getTime();
301 | $http.get(url)
302 | .success(function (data) {
303 | $log.debug(" curl -X GET " + url + " => in [ " + ((new Date().getTime()) - start) + "] msec");
304 | deferred.resolve(data)
305 | })
306 | .error(function (data, status) {
307 | if (status === 404) {
308 | $log.warn('No compatibility level is set for ' + subjectName + '. Global compatibility level is applied');
309 | } else
310 | deferred.reject("Get global config rejection : " + data + " " + status)
311 | });
312 | return deferred.promise;
313 |
314 | }
315 |
316 | /**
317 | * Update compatibility level for the specified subject
318 | * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#put--config-(string- subject)
319 | */
320 | function updateSubjectCompatibility(subjectName, newCompatibilityLevel) {
321 |
322 | var deferred = $q.defer();
323 |
324 | if (["NONE", "FULL", "FORWARD", "BACKWARD", "FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"].indexOf(newCompatibilityLevel) !== -1) {
325 |
326 | var putConfig = {
327 | method: 'PUT',
328 | url: env.SCHEMA_REGISTRY() + '/config/' + subjectName,
329 | data: '{"compatibility":"' + newCompatibilityLevel + '"}' + "'",
330 | dataType: 'json',
331 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
332 | };
333 |
334 | $http(putConfig)
335 | .success(function (data) {
336 | $log.info("Success in changing subject [ " + subjectName + " ] compatibility " + JSON.stringify(data));
337 | deferred.resolve(data.compatibility)
338 | })
339 | .error(function (data, status) {
340 | $log.info("Error on changing compatibility : " + JSON.stringify(data));
341 | if (status === 422) {
342 | $log.warn("Invalid compatibility level " + JSON.stringify(status) + " " + JSON.stringify(data));
343 | if (JSON.stringify(data).indexOf('50001') > -1) {
344 | $log.error(" Error in the backend data store - " + $scope.text);
345 | } else if (JSON.stringify(data).indexOf('50003') > -1) {
346 | $log.error("Error while forwarding the request to the master: " + JSON.stringify(data));
347 | }
348 | } else {
349 | $log.debug("HTTP > 200 && < 400 (!) " + JSON.stringify(data));
350 | }
351 | deferred.reject(data);
352 | });
353 |
354 | } else {
355 | $log.warn("Compatibility level:" + newCompatibilityLevel + " is not supported");
356 | deferred.reject();
357 | }
358 |
359 | return deferred.promise;
360 |
361 | }
362 |
363 |
364 | /**
365 | * Custom logic of Factory is implemented here.
366 | *
367 | * In a nut-shell `CACHE` is holding a cache of known subjects
368 | * Methods here are utilizing the cache - picking from it or updating
369 | *
370 | * Subjects are immutable in the schema-registry, thus downloading them
371 | * just once is enough !
372 | */
373 |
374 | var CACHE = []; // A cache of the latest subject
375 |
376 | /**
377 | * Gets from CACHE if exists - undefined otherwise
378 | */
379 | function getFromCache(subjectName, subjectVersion) {
380 | var start = new Date().getTime();
381 | var response = undefined;
382 | angular.forEach(CACHE, function (subject) {
383 | if (subject.subjectName === subjectName && subject.version === subjectVersion) {
384 | $log.debug(" [ " + subjectName + "/" + subjectVersion + " ] found in cache " + JSON.stringify(subject).length + " bytes in [ " + ((new Date().getTime()) - start) + " ] msec");
385 | response = subject;
386 | }
387 | });
388 | return response;
389 | }
390 |
391 | /**
392 | * GETs latest from CACHE or 'undefined'
393 | */
394 | function getLatestFromCache(subjectName) {
395 | var subjectFromCache = undefined;
396 | for (var i = 1; i < 10000; i++) {
397 | var x = getFromCache(subjectName, i);
398 | if (x !== undefined)
399 | subjectFromCache = x;
400 | }
401 | return subjectFromCache;
402 | }
403 |
404 |
405 | /**
406 | *
407 | * Composite & Public Methods of this factory
408 | *
409 | */
410 | return {
411 |
412 | // Proxy in function
413 | getGlobalConfig: function () {
414 | return getGlobalConfig();
415 | },
416 |
417 | getSubjectConfig: function (subjectName) {
418 | return getSubjectConfig(subjectName);
419 | },
420 |
421 | putConfig: function (config) {
422 | return putConfig(config);
423 | },
424 | updateSubjectCompatibility: function (subjectName, newCompatibilityLevel) {
425 | return updateSubjectCompatibility(subjectName, newCompatibilityLevel);
426 | },
427 |
428 | // Proxy in function
429 | testSchemaCompatibility: function (subjectName, subjectInformation) {
430 | return testSchemaCompatibility(subjectName, subjectInformation);
431 | },
432 |
433 | // Proxy in function
434 | registerNewSchema: function (subjectName, subjectInformation) {
435 | return postNewSubjectVersion(subjectName, subjectInformation);
436 | },
437 |
438 | // Proxy in function
439 | getSubjectsVersions: function (subjectName) {
440 | return getSubjectsVersions(subjectName);
441 | },
442 |
443 | // Proxy in function
444 | getLatestSubjectFromCache: function (subjectName) {
445 | return getLatestFromCache(subjectName);
446 | },
447 | // Proxy in function
448 | getAllSchemas: function (schemas) {
449 | return getAllSchemas(schemas);
450 | },
451 |
452 | deleteVersionOfSubject: function (subjectName, version) {
453 | var deferred = $q.defer();
454 |
455 | var request = {
456 | method: 'DELETE',
457 | url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version,
458 | dataType: 'json',
459 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain'}
460 | };
461 | $http(request)
462 | .success(function (data) {
463 | $log.info("Success in deleting subject version" + subjectName + ", version" + version);
464 | var schemaId = data.id;
465 | deferred.resolve(schemaId);
466 | })
467 | .error(function (data, status) {
468 | $log.info("Error on subject version deletion : ", data);
469 | deferred.reject(data);
470 | });
471 | return deferred.promise;
472 | },
473 |
474 | deleteSubject: function (subjectName) {
475 |
476 | var deferred = $q.defer();
477 |
478 | var request = {
479 | method: 'DELETE',
480 | url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName,
481 | dataType: 'json',
482 | headers: {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain'}
483 | };
484 | $http(request)
485 | .success(function (data) {
486 | $log.info("Success in deleting schema" + subjectName);
487 | deferred.resolve();
488 | })
489 | .error(function (data, status) {
490 | $log.info("Error on schema deletion : ", data);
491 | deferred.reject();
492 | });
493 | return deferred.promise;
494 | },
495 |
496 | /**
497 | * GETs all subject-names and then GETs the /versions/latest of each one
498 | *
499 | * Refreshes the CACHE object with latest subjects
500 | */
501 | refreshLatestSubjectsCACHE: function () {
502 |
503 | var deferred = $q.defer();
504 | var start = new Date().getTime();
505 |
506 | // 1. Get all subject names
507 | getSubjects().then(
508 | function success(allSubjectNames) {
509 | // 2. Get full details of subject's final versions
510 | var urlFetchLatestCalls = [];
511 | angular.forEach(allSubjectNames, function (subject) {
512 | urlFetchLatestCalls.push($http.get(env.SCHEMA_REGISTRY() + '/subjects/' + subject + '/versions/latest'));
513 | });
514 | $q.all(urlFetchLatestCalls).then(function (latestSchemas) {
515 | CACHE = []; // Clean up existing cache - to replace with new one
516 | angular.forEach(latestSchemas, function (result) {
517 | var data = result.data;
518 | var cacheData = {
519 | version: data.version, // version
520 | id: data.id, // id
521 | schema: data.schema, // schema - in String - schema i.e. {\"type\":\"record\",\"name\":\"User\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"}]}
522 | Schema: JSON.parse(data.schema), // js type | name | doc | fields ...
523 | subjectName: data.subject
524 | };
525 | CACHE.push(cacheData);
526 | });
527 | $log.debug(" pipeline : get-latest-subjects-refresh-cache in [ " + (new Date().getTime() - start) + " ] msec");
528 | $rootScope.showSpinner = false;
529 | $rootScope.Cache = CACHE;
530 | deferred.resolve(CACHE);
531 | });
532 | });
533 |
534 | return deferred.promise;
535 |
536 | },
537 | /**
538 | * Get one subject at a particular version
539 | */
540 | getSubjectAtVersion: function (subjectName, subjectVersion) {
541 |
542 | var deferred = $q.defer();
543 |
544 | // If it's easier to fetch it from cache
545 | var subjectFromCache = getFromCache(subjectName, subjectVersion);
546 | if (subjectFromCache !== undefined) {
547 | deferred.resolve(subjectFromCache);
548 | } else {
549 | var start = new Date().getTime();
550 | getSubjectAtVersion(subjectName, subjectVersion).then(
551 | function success(subjectInformation) {
552 | //cache it
553 | var subjectInformationWithMetadata = {
554 | version: subjectInformation.version,
555 | id: subjectInformation.id,
556 | schema: subjectInformation.schema, // this is text
557 | Schema: JSON.parse(subjectInformation.schema), // this is json
558 | subjectName: subjectInformation.subject
559 | };
560 | $log.debug(" pipeline: " + subjectName + "/" + subjectVersion + " in [ " + (new Date().getTime() - start) + " ] msec");
561 | deferred.resolve(subjectInformationWithMetadata);
562 | },
563 | function errorCallback(response) {
564 | $log.error("Failure with : " + JSON.stringify(response));
565 | });
566 | }
567 | return deferred.promise;
568 |
569 | },
570 |
571 | /**
572 | * GETs the entire subject's history, by
573 | *
574 | * i. Getting all version
575 | * ii. Fetching each version either from cache or from HTTP GET
576 | */
577 | getSubjectHistory: function (subjectName) {
578 |
579 | var deferred = $q.defer();
580 |
581 | $log.info("Getting subject [ " + subjectName + "] history");
582 | var completeSubjectHistory = [];
583 | getSubjectsVersions(subjectName).then(
584 | function success(allVersions) {
585 | var urlCalls = [];
586 | angular.forEach(allVersions, function (version) {
587 | // If in cache
588 | var subjectFromCache = getFromCache(subjectName, version);
589 | if (subjectFromCache !== undefined) {
590 | completeSubjectHistory.push(subjectFromCache);
591 | } else {
592 | urlCalls.push($http.get(env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version));
593 | }
594 | });
595 | // Get all missing versions and add them to cache
596 | $q.all(urlCalls).then(function (results) {
597 | angular.forEach(results, function (result) {
598 | completeSubjectHistory.push(result.data);
599 | });
600 | deferred.resolve(completeSubjectHistory);
601 | });
602 | },
603 | function failure(data) {
604 | deferred.reject("pdata=>" + data);
605 | });
606 |
607 | return deferred.promise;
608 |
609 | },
610 |
611 | /**
612 | * Get the history in a diff format convenient for rendering a ui
613 | */
614 | getSubjectHistoryDiff: function (subjectHistory) {
615 | var changelog = [];
616 |
617 | $log.info("Sorting by version..");
618 | var sortedHistory = UtilsFactory.sortByVersion(subjectHistory);
619 | for (var i = 0; i < sortedHistory.length; i++) {
620 | var previous = '';
621 | if (i > 0)
622 | previous = JSON.parse(sortedHistory[i - 1].schema);
623 | var changeDetected = {
624 | version: sortedHistory[i].version,
625 | id: sortedHistory[i].id,
626 | current: JSON.parse(sortedHistory[i].schema),
627 | previous: previous
628 | };
629 | changelog.push(changeDetected);
630 | }
631 |
632 | return changelog;
633 | }
634 | }
635 |
636 | };
637 |
638 | SchemaRegistryFactory.$inject = ['$rootScope', '$http', '$location', '$q', '$log', 'UtilsFactory', 'env'];
639 |
640 | angularAPP.factory('SchemaRegistryFactory', SchemaRegistryFactory);
641 |
--------------------------------------------------------------------------------