| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| import angular from 'angular'; |
| import brIconGenerator from 'brooklyn-ui-utils/icon-generator/icon-generator'; |
| import brooklynCatalogApi from 'brooklyn-ui-utils/providers/catalog-api.provider'; |
| import brooklynTypeItem from '../../components/type-item/index'; |
| import brUtils from 'brooklyn-ui-utils/utils/general'; |
| import template from './catalog.template.html'; |
| import {analyzeDescription} from 'brooklyn-ui-utils/md-helper'; |
| import {HIDE_INTERSTITIAL_SPINNER_EVENT} from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner'; |
| |
| const MODULE_NAME = 'catalog.state'; |
| |
| angular.module(MODULE_NAME, [brIconGenerator, brooklynCatalogApi, brooklynTypeItem, brUtils]) |
| .config(['$stateProvider', catalogStateConfig]) |
| .filter('bundleName', ['$sce', bundleNameFilter]) |
| .filter('bundleDescription', ['$sce', bundleDescriptionFilter]) |
| .filter('bundleHighlights', ['$sce', bundleHighlightsFilter]) |
| .filter('bundleTypeFilter', ['$sce', bundleTypeFilter]); |
| |
| export default MODULE_NAME; |
| |
| export const catalogState = { |
| name: 'catalog', |
| url: '/', |
| template: template, |
| controller: ['$scope', '$rootScope', 'catalogApi', 'brUtilsGeneral', catalogController], |
| controllerAs: 'ctrl' |
| }; |
| |
| export function catalogStateConfig($stateProvider) { |
| $stateProvider.state(catalogState); |
| } |
| |
| export function catalogController($scope, $rootScope, catalogApi, brUtilsGeneral) { |
| const orderBysBundles = [{ |
| id: 'name', |
| label: 'Name' |
| }, { |
| id: '-version', |
| label: 'Version' |
| }, { |
| id: '-types', |
| label: 'Most types' |
| }, { |
| id: 'types', |
| label: 'Least types' |
| }]; |
| const orderBysTypes = [{ |
| id: 'displayName', |
| label: 'Display name' |
| }, { |
| id: 'id', |
| label: 'Type name' |
| }, { |
| id: '-version', |
| label: 'Version' |
| }, { |
| id: 'supertypes', |
| label: 'Supertype' |
| }, { |
| id: 'containingBundle', |
| label: 'Bundle' |
| }]; |
| |
| const savedOrderByKey = 'catalog-order-by'; |
| |
| const savedOrderBy = localStorage && localStorage.getItem(savedOrderByKey) !== null ? |
| JSON.parse(localStorage.getItem(savedOrderByKey)) |
| : { |
| orderBy: 'bundles', |
| sortBy: 0 |
| } |
| |
| $scope.pagination = { |
| page: 1, // not used |
| itemsPerPage: 20 // used as an absolute limit |
| }; |
| $scope.config = { |
| orderBy: savedOrderBy.orderBy === 'bundles' ? orderBysBundles : orderBysTypes |
| }; |
| $scope.state = { |
| view: savedOrderBy.orderBy, |
| versions: [], |
| orderBy: $scope.config.orderBy.length > savedOrderBy.sortBy ? $scope.config.orderBy[savedOrderBy.sortBy] : 0, |
| search: {} |
| }; |
| |
| $scope.$watch('state.view', (newView, oldView) => { |
| if (newView && oldView && !angular.equals(newView, oldView)) { |
| $scope.config.orderBy = newView === 'types' ? orderBysTypes : orderBysBundles; |
| $scope.state.orderBy = $scope.config.orderBy[0] |
| savedOrderBy.orderBy = newView; |
| savedOrderBy.sortBy = 0; |
| if (localStorage) { |
| localStorage.setItem(savedOrderByKey, JSON.stringify(savedOrderBy)); |
| } |
| } |
| }); |
| |
| $scope.$watch('state.orderBy', (newOrderBy, oldOrderBy) => { |
| if(newOrderBy && oldOrderBy && !angular.equals(newOrderBy, oldOrderBy)) { |
| savedOrderBy.sortBy = $scope.config.orderBy.indexOf(newOrderBy); |
| if (localStorage) { |
| localStorage.setItem(savedOrderByKey, JSON.stringify(savedOrderBy)); |
| } |
| } |
| }); |
| |
| $scope.clearSearchFilters = () => { |
| $scope.state.search = {}; |
| $scope.state.orderBy = orderBysBundles[0]; |
| }; |
| |
| $scope.isNonEmpty = (o) => { |
| return brUtilsGeneral.isNonEmpty(o); |
| }; |
| |
| $scope.launchCatalogUploader = ()=> { |
| $rootScope.$broadcast('open-catalog-uploader'); |
| }; |
| |
| catalogApi.getBundles({params: {detail: true}}).then(data => { |
| $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT); |
| |
| processBundles(data); |
| }); |
| |
| function processBundles(bundles) { |
| $scope.bundles = bundles; |
| |
| $scope.versions = bundles.reduce((versions, bundle) => { |
| if (!versions.hasOwnProperty(bundle.symbolicName)) { |
| versions[bundle.symbolicName] = []; |
| } |
| versions[bundle.symbolicName].push(bundle.version); |
| return versions; |
| }, {}); |
| $scope.config.versions = Array.from(Object.values($scope.versions).reduce((set, versions) => { |
| versions.forEach(version => set.add(version)); |
| return set; |
| }, new Set())); |
| |
| $scope.types = bundles.reduce((types, bundle) => { |
| let typesInBundle = angular.copy(bundle.types).map(t => { |
| // record the bundle |
| t.bundle = { |
| symbolicName: bundle.symbolicName, |
| version: bundle.version, |
| }; |
| |
| // tidy up display so that things labelled [DEPRECATED] don't have that ugly name |
| // (since we highlight deprecated things, and particularly bad since [ appears first alphabetically!) |
| // [as in bundle.state.js] |
| if (t.deprecated && t.displayName.match(/^[^\w]*deprecated[^\w]*/i)) { |
| t.displayName = t.displayName.replace(/^[^\w]*deprecated[^\w]*/i, ''); |
| } |
| |
| return t; |
| }); |
| return types.concat(typesInBundle); |
| }, []); |
| } |
| } |
| |
| export function bundleNameFilter() { |
| return function(input) { |
| if (!input) { |
| return; |
| } |
| // we could give better names to these unhelpful bundle names; |
| // but actually doing so currently is jarring as all the other bundle names |
| // are in symbolic name format and this is in display name format; |
| // if we had nice bundle display names it would make much more sense to do this: |
| // (reenable when we have display names for bundles) |
| // if (input.symbolicName && input.symbolicName.startsWith('brooklyn-catalog-bom')) { |
| // return 'Unnamed bundle'; |
| // } |
| return input.symbolicName; |
| } |
| } |
| |
| export function bundleDescriptionFilter() { |
| return function(input) { |
| if (!input) { |
| return; |
| } |
| if (input.description) { |
| // bundles don't have description yet so this is moot, but when they do this will be nice - or better use the md-if-multiline widget from mdHelper |
| return analyzeDescription(input).oneline; |
| } |
| |
| let alwaysGenerateDefaultDescription = true; |
| if (alwaysGenerateDefaultDescription || (input.symbolicName && input.symbolicName.startsWith('brooklyn-catalog-bom'))) { |
| // useful in anonymous case because the name gives no clue as to the title; |
| // useful in other cases too, even if redundant, but as a flag above in case we don't want to do that |
| if (angular.isArray(input.types) && input.types.length > 0) { |
| let displayedTypes = input.types.slice(0, input.types.length > 3 ? 2 : input.types.length); |
| let description = `Composed of ${displayedTypes.map(type => type.displayName || type.symbolicName).join(', ')}`; |
| if (input.types.length > displayedTypes.length) { |
| description += ` and ${input.types.length - displayedTypes.length} more items`; |
| } |
| return description; |
| } else { |
| return "This is likely a support bundle." |
| } |
| } |
| } |
| } |
| |
| export function bundleHighlightsFilter($sce) { |
| return function(input) { |
| let highlights = []; |
| |
| if (input && input.types) { |
| let entities = input.types.filter(type => type.supertypes.includes('org.apache.brooklyn.api.entity.Entity')).length; |
| let policies = input.types.filter(type => type.supertypes.includes('org.apache.brooklyn.api.policy.Policy')).length; |
| let enrichers = input.types.filter(type => type.supertypes.includes('org.apache.brooklyn.api.sensor.Enricher')).length; |
| |
| if (entities > 0) { |
| highlights.push({ |
| label: (entities==1 ? 'entity' : 'entities'), |
| count: entities |
| }); |
| } |
| if (policies > 0) { |
| highlights.push({ |
| label: (policies==1 ? 'policy' : 'policies'), |
| count: policies |
| }); |
| } |
| if (enrichers > 0) { |
| highlights.push({ |
| label: (enrichers==1 ? 'enricher' : 'enrichers'), |
| count: enrichers |
| }); |
| } |
| } |
| |
| return $sce.trustAsHtml(highlights.map(highlight => { |
| return `<span class="highlight-count">${highlight.count}</span> <span class="highlight-label">${highlight.label}</span>` |
| }).join(' ')); |
| }; |
| } |
| |
| export function bundleTypeFilter() { |
| return function(input, superType) { |
| return input && input.filter(type => type.supertypes.includes(superType)); |
| } |
| } |