<commit_before>/*
 * Copyright (c) WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 *   Licensed 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.
 */
asset.manager = function(ctx) {
    var notifier = require('store').notificationManager;
    var storeConstants = require('store').storeConstants;
    var COMMENT = 'User comment';
    var carbon = require('carbon');
    var social = carbon.server.osgiService('org.wso2.carbon.social.core.service.SocialActivityService');
    var log = new Log('default-asset');
    return {
        create: function(options) {
            var ref = require('utils').time;
            //Check if the options object has a createdtime attribute and populate it
            if ((options.attributes) && ctx.rxtManager.getRxtField(ctx.assetType, 'overview_createdtime')) {
                options.attributes.overview_createdtime = ref.getCurrentTime();
            }
            this._super.create.call(this, options);
            var asset = this.get(options.id); //TODO avoid get: expensive operation
            var assetPath = asset.path;
            var user = ctx.username;
            var userRoles = ctx.userManager.getRoleListOfUser(user);
            try {
                social.warmUpRatingCache(ctx.assetType + ':' + options.id);
            } catch (e) {
                log.warn("Unable to publish the asset: " + ctx.assetType + ":" + options.id + " to social cache. This may affect on sort by popularity function.");
            }
            //Check whether the user has admin role
            var endpoint = storeConstants.PRIVATE_ROLE_ENDPOINT + user;
            for (var role in userRoles) {
                if (userRoles.hasOwnProperty(role) && userRoles[role] == storeConstants.ADMIN_ROLE) {
                    endpoint = storeConstants.ADMIN_ROLE_ENDPOINT;
                }
            }

            var provider = ctx.username;

            if(options.attributes.overview_provider){
                provider = options.attributes.overview_provider;
            }
            provider = provider.replace(':', '@');
            //Subscribe the asset author for LC update event and asset update event
            notifier.subscribeToEvent(provider, assetPath, endpoint, storeConstants.LC_STATE_CHANGE);
            notifier.subscribeToEvent(provider, assetPath, endpoint, storeConstants.ASSET_UPDATE);
        },
        update: function(options) {
            this._super.update.call(this, options);
            var asset = this.get(options.id); //TODO avoid get: expensive operation
            //trigger notification on asset update
            notifier.notifyEvent(storeConstants.ASSET_UPDATE_EVENT, asset.type, asset.name, null, asset.path, ctx.tenantId);
        },
        search: function(query, paging) {
            return this._super.search.call(this, query, paging);
        },
        list: function(paging) {
            return this._super.list.call(this, paging);
        },
        get: function(id) {
            return this._super.get.call(this, id);
        },
        invokeLcAction: function(asset, action, lcName) {
            var success;
            if (lcName) {
                success = this._super.invokeLcAction.call(this, asset, action, lcName);
            } else {
                success = this._super.invokeLcAction.call(this, asset, action);
            }
            //trigger notification on LC state change
            notifier.notifyEvent(storeConstants.LC_STATE_CHANGE_EVENT, asset.type, asset.name, COMMENT, asset.path, ctx.tenantId);
            return success;
        }
    };
};
asset.server = function(ctx) {
    var type = ctx.type;
    return {
        onUserLoggedIn: function() {},
        endpoints: {
            apis: [{
                url: 'assets',
                path: 'assets.jag'
            }, {
                url: 'asset',
                path: 'asset.jag'
            }, {
                url: 'statistics',
                path: 'statistics.jag'
            }],
            pages: [{
                title: 'Asset: ' + type,
                url: 'asset',
                path: 'asset.jag'
            }, {
                title: 'Assets ' + type,
                url: 'assets',
                path: 'assets.jag'
            }, {
                title: 'Create ' + type,
                url: 'create',
                path: 'create.jag',
                permission: 'ASSET_CREATE'
            }, {
                title: 'Update ' + type,
                url: 'update',
                path: 'update.jag',
                permission: 'ASSET_UPDATE'
            }, {
                title: 'Details ' + type,
                url: 'details',
                path: 'details.jag'
            }, {
                title: 'List ' + type,
                url: 'list',
                path: 'list.jag',
                permission: 'ASSET_LIST'
            }, {
                title: 'Lifecycle',
                url: 'lifecycle',
                path: 'lifecycle.jag',
                permission: 'ASSET_LIFECYCLE'
            }, {
                title: 'Old lifecycle ',
                url: 'old-lifecycle',
                path: 'old-lifecycle.jag'
            }, {
                title: 'Statistics',
                url: 'statistics',
                path: 'statistics.jag'
            }, {
                title: 'Copy ' + type,
                url: 'copy',
                path: 'copy.jag',
                permission: 'ASSET_CREATE'
            }, {
                title: 'Delete ' + type,
                url: 'delete',
                path: 'delete.jag'
            }]
        }
    };
};
asset.configure = function() {
    return {
        table: {
            overview: {
                fields: {
                    provider: {
                        auto: true
                    },
                    name: {
                        name: {
                            name: 'name',
                            label: 'Name'
                        },
                        readonly: true,
                        validation: function() {}
                    },
                    version: {
                        name: {
                            label: 'Version'
                        }
                    },
                    createdtime: {
                        hidden: true
                    }
                }
            },
            images: {
                fields: {
                    thumbnail: {
                        type: 'file'
                    },
                    banner: {
                        type: 'file'
                    }
                }
            }
        },
        meta: {
            lifecycle: {
                name: 'SampleLifeCycle2',
                commentRequired: false,
                defaultLifecycleEnabled: true,
                defaultAction: 'Promote',
                deletableStates: ['Unpublished'],
                publishedStates: ['Published'],
                lifecycleEnabled: true
            },
            ui: {
                icon: 'fw fw-resource'
            },
            categories: {
                categoryField: 'overview_category'
            },
            thumbnail: 'images_thumbnail',
            banner: 'images_banner',
            nameAttribute: 'overview_name',
            versionAttribute: 'overview_version',
            providerAttribute: 'overview_provider',
            timestamp: 'overview_createdtime',
            grouping: {
                groupingEnabled: false,
                groupingAttributes: ['overview_name']
            },
            permissions: {
                configureRegistryPermissions: function(ctx) {
                }
            }
        }
    };
};
asset.renderer = function(ctx) {
    var type = ctx.assetType;
    var permissionAPI = require('rxt').permissions;
    var isAssetWithLifecycle = function(asset) {
        if ((asset.lifecycle) && (asset.lifecycleState)) {
            return true;
        }
        if (log.isDebugEnabled()) {
            log.debug('asset: ' + asset.name + ' does not have a lifecycle or a state.The lifecycle view will not be rendered for this asset');
        }
        return false;
    };
    var buildListLeftNav = function(page, util) {
        var navList = util.navList();
        if (permissionAPI.hasAssetPermission(permissionAPI.ASSET_CREATE, ctx.assetType, ctx.session)) {
            navList.push('Add ' + type, 'btn-add-new', util.buildUrl('create'));
            navList.push('Statistics', 'btn-stats', '/assets/' + type + '/statistics');
        }
        //navList.push('Configuration', 'icon-dashboard', util.buildUrl('configuration'));
        return navList.list();
    };
    var buildDefaultLeftNav = function(page, util) {
        var id = page.assets.id;
        var path = page.assets.path;
        var navList = util.navList();
        var isLCViewEnabled = ctx.rxtManager.isLifecycleViewEnabled(ctx.assetType);
        if (permissionAPI.hasActionPermissionforPath(path, 'write', ctx.session)) {
            navList.push('Edit', 'btn-edit', util.buildUrl('update') + '/' + id);
            navList.push('Version', 'btn-copy', util.buildUrl('copy') + '/' + id);
        }
        navList.push('Overview', 'btn-overview', util.buildUrl('details') + '/' + id);
        //Only render the view if the asset has a
        if ((isLCViewEnabled) && (isAssetWithLifecycle(page.assets))) {
            if (permissionAPI.hasAssetPermission(permissionAPI.ASSET_LIFECYCLE, ctx.assetType, ctx.session)) {
                navList.push('Life Cycle', 'btn-lifecycle', util.buildUrl('lifecycle') + '/' + id);
            }
        }
        if (permissionAPI.hasActionPermissionforPath(path, 'delete', ctx.session)) {
            navList.push('Delete', 'btn-delete', util.buildUrl('delete') + '/' + id);
        }
        return navList.list();
    };
    var buildAddLeftNav = function(page, util) {
        return [];
    };
    var isActivatedAsset = function(assetType) {
        var app = require('rxt').app;
        var activatedAssets = app.getUIActivatedAssets(ctx.tenantId); //ctx.tenantConfigs.assets;
        //return true;
        if (!activatedAssets) {
            throw 'Unable to load all activated assets for current tenant: ' + ctx.tenatId + '.Make sure that the assets property is present in the tenant config';
        }
        for (var index in activatedAssets) {
            if (activatedAssets[index] == assetType) {
                return true;
            }
        }
        return false;
    };
    return {
        list: function(page) {
            var assets = page.assets;
            for (var index in assets) {
                var asset = assets[index];
                var timestampAttribute = ctx.rxtManager.getTimeStampAttribute(ctx.assetType);
                if (asset.attributes.hasOwnProperty(timestampAttribute)) {
                    var value = asset.attributes[timestampAttribute];
                    var date = new Date();
                    date.setTime(value);
                    asset.attributes[timestampAttribute] = date.toUTCString();
                }
            }
            require('/modules/page-decorators.js').pageDecorators.assetCategoryDetails(ctx, page, this);
        },
        details: function(page) {
            var tables = page.assets.tables;
            //TODO:This cannot be hardcoded
            var timestampAttribute = 'createdtime'; //ctx.rxtManager.getTimeStampAttribute(this.assetType);
            for (var index in tables) {
                var table = tables[index];
                if ((table.name == 'overview') && (table.fields.hasOwnProperty(timestampAttribute))) {
                    var value = table.fields[timestampAttribute].value || '';
                    var date = new Date();
                    date.setTime(value);
                    table.fields[timestampAttribute].value = date.toUTCString();
                }
            }
        },
        create: function(page) {
            var tables = page.assets.tables;
            var providerAttribute = 'provider';
            for (var index in tables) {
                var table = tables[index];
                if ((table.name == 'overview') && (table.fields.hasOwnProperty(providerAttribute))) {
                    table.fields[providerAttribute].value = page.cuser.username;
                }
            }
        },
        update: function(page) {
            var tables = page.assets.tables;
            var timestampAttribute = 'createdtime';
            for (var index in tables) {
                var table = tables[index];
                if ((table.name == 'overview') && (table.fields.hasOwnProperty(timestampAttribute))) {
                    var value = table.fields[timestampAttribute].value;
                    var date = new Date();
                    date.setTime(value);
                    table.fields[timestampAttribute].value = date.toUTCString();
                }
            }
        },
        pageDecorators: {
            leftNav: function(page) {
                if (log.isDebugEnabled()) {
                    log.debug('Using default leftNav');
                }
                switch (page.meta.pageName) {
                    case 'list':
                        page.leftNav = buildListLeftNav(page, this);
                        break;
                    case 'create':
                        page.leftNav = buildAddLeftNav(page, this);
                        break;
                    case 'statistics':
                        page.leftNav = buildListLeftNav(page, this);
                        break;
                    default:
                        page.leftNav = buildDefaultLeftNav(page, this);
                        break;
                }
                if (page.leftNav) {
                    for (var navItem in page.leftNav) {
                        if (page.leftNav[navItem].name) {
                            page.leftNav[navItem].id = page.leftNav[navItem].name.replace(/\s/g, "");
                        }
                    }
                }
                return page;
            },
            ribbon: function(page) {
                var ribbon = page.ribbon = {};
                var DEFAULT_ICON = 'fw fw-circle';
                var assetTypes = [];
                var assetType;
                var assetList = ctx.rxtManager.listRxtTypeDetails();
                for (var index in assetList) {
                    assetType = assetList[index];
                    //Only populate the link if the asset type is activated and the logged in user has permission to that asset
                    if ((isActivatedAsset(assetType.shortName)) && (permissionAPI.hasAssetPermission(permissionAPI.ASSET_LIST, assetType.shortName, ctx.session))) {
                        assetTypes.push({
                            url: this.buildBaseUrl(assetType.shortName) + '/list',
                            assetIcon: assetType.ui.icon || DEFAULT_ICON,
                            assetTitle: assetType.singularLabel
                        });
                    }
                }
                ribbon.currentType = page.rxt.singularLabel;
                ribbon.currentTitle = page.rxt.singularLabel;
                ribbon.currentUrl = this.buildBaseUrl(type) + '/list'; //page.meta.currentPage;
                ribbon.shortName = page.rxt.singularLabel;
                ribbon.query = 'Query';
                ribbon.breadcrumb = assetTypes;
                return page;
            },
            populateAttachedLifecycles: function(page) {
                if (page.assets.id) {
                    require('/modules/page-decorators.js').pageDecorators.populateAttachedLifecycles(ctx, page, this);
                }
            },
            populateAssetVersionDetails: function(page) {
                if (page.assets.id) {
                    require('/modules/page-decorators.js').pageDecorators.populateAssetVersionDetails(ctx, page, this);
                }
            },
            populateGroupingFeatureDetails: function(page) {
                require('/modules/page-decorators.js').pageDecorators.populateGroupingFeatureDetails(ctx, page);
            },
            populateTags: function(page){
                if(page.assets.id){
                    require('/modules/page-decorators.js').pageDecorators.populateTagDetails(ctx,page);
                }
            },
            sorting: function(page){
                require('/modules/page-decorators.js').pageDecorators.sorting(ctx,page);
            }
        }
    };
};
<commit_msg>making version a readonly field<commit_after>/*
 * Copyright (c) WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 *   Licensed 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.
 */
asset.manager = function(ctx) {
    var notifier = require('store').notificationManager;
    var storeConstants = require('store').storeConstants;
    var COMMENT = 'User comment';
    var carbon = require('carbon');
    var social = carbon.server.osgiService('org.wso2.carbon.social.core.service.SocialActivityService');
    var log = new Log('default-asset');
    return {
        create: function(options) {
            var ref = require('utils').time;
            //Check if the options object has a createdtime attribute and populate it
            if ((options.attributes) && ctx.rxtManager.getRxtField(ctx.assetType, 'overview_createdtime')) {
                options.attributes.overview_createdtime = ref.getCurrentTime();
            }
            this._super.create.call(this, options);
            var asset = this.get(options.id); //TODO avoid get: expensive operation
            var assetPath = asset.path;
            var user = ctx.username;
            var userRoles = ctx.userManager.getRoleListOfUser(user);
            try {
                social.warmUpRatingCache(ctx.assetType + ':' + options.id);
            } catch (e) {
                log.warn("Unable to publish the asset: " + ctx.assetType + ":" + options.id + " to social cache. This may affect on sort by popularity function.");
            }
            //Check whether the user has admin role
            var endpoint = storeConstants.PRIVATE_ROLE_ENDPOINT + user;
            for (var role in userRoles) {
                if (userRoles.hasOwnProperty(role) && userRoles[role] == storeConstants.ADMIN_ROLE) {
                    endpoint = storeConstants.ADMIN_ROLE_ENDPOINT;
                }
            }

            var provider = ctx.username;

            if(options.attributes.overview_provider){
                provider = options.attributes.overview_provider;
            }
            provider = provider.replace(':', '@');
            //Subscribe the asset author for LC update event and asset update event
            notifier.subscribeToEvent(provider, assetPath, endpoint, storeConstants.LC_STATE_CHANGE);
            notifier.subscribeToEvent(provider, assetPath, endpoint, storeConstants.ASSET_UPDATE);
        },
        update: function(options) {
            this._super.update.call(this, options);
            var asset = this.get(options.id); //TODO avoid get: expensive operation
            //trigger notification on asset update
            notifier.notifyEvent(storeConstants.ASSET_UPDATE_EVENT, asset.type, asset.name, null, asset.path, ctx.tenantId);
        },
        search: function(query, paging) {
            return this._super.search.call(this, query, paging);
        },
        list: function(paging) {
            return this._super.list.call(this, paging);
        },
        get: function(id) {
            return this._super.get.call(this, id);
        },
        invokeLcAction: function(asset, action, lcName) {
            var success;
            if (lcName) {
                success = this._super.invokeLcAction.call(this, asset, action, lcName);
            } else {
                success = this._super.invokeLcAction.call(this, asset, action);
            }
            //trigger notification on LC state change
            notifier.notifyEvent(storeConstants.LC_STATE_CHANGE_EVENT, asset.type, asset.name, COMMENT, asset.path, ctx.tenantId);
            return success;
        }
    };
};
asset.server = function(ctx) {
    var type = ctx.type;
    return {
        onUserLoggedIn: function() {},
        endpoints: {
            apis: [{
                url: 'assets',
                path: 'assets.jag'
            }, {
                url: 'asset',
                path: 'asset.jag'
            }, {
                url: 'statistics',
                path: 'statistics.jag'
            }],
            pages: [{
                title: 'Asset: ' + type,
                url: 'asset',
                path: 'asset.jag'
            }, {
                title: 'Assets ' + type,
                url: 'assets',
                path: 'assets.jag'
            }, {
                title: 'Create ' + type,
                url: 'create',
                path: 'create.jag',
                permission: 'ASSET_CREATE'
            }, {
                title: 'Update ' + type,
                url: 'update',
                path: 'update.jag',
                permission: 'ASSET_UPDATE'
            }, {
                title: 'Details ' + type,
                url: 'details',
                path: 'details.jag'
            }, {
                title: 'List ' + type,
                url: 'list',
                path: 'list.jag',
                permission: 'ASSET_LIST'
            }, {
                title: 'Lifecycle',
                url: 'lifecycle',
                path: 'lifecycle.jag',
                permission: 'ASSET_LIFECYCLE'
            }, {
                title: 'Old lifecycle ',
                url: 'old-lifecycle',
                path: 'old-lifecycle.jag'
            }, {
                title: 'Statistics',
                url: 'statistics',
                path: 'statistics.jag'
            }, {
                title: 'Copy ' + type,
                url: 'copy',
                path: 'copy.jag',
                permission: 'ASSET_CREATE'
            }, {
                title: 'Delete ' + type,
                url: 'delete',
                path: 'delete.jag'
            }]
        }
    };
};
asset.configure = function() {
    return {
        table: {
            overview: {
                fields: {
                    provider: {
                        auto: true
                    },
                    name: {
                        name: {
                            name: 'name',
                            label: 'Name'
                        },
                        readonly: true,
                        validation: function() {}
                    },
                    version: {
                        name: {
                            label: 'Version'
                        },
                        readonly: true
                    },
                    createdtime: {
                        hidden: true
                    }
                }
            },
            images: {
                fields: {
                    thumbnail: {
                        type: 'file'
                    },
                    banner: {
                        type: 'file'
                    }
                }
            }
        },
        meta: {
            lifecycle: {
                name: 'SampleLifeCycle2',
                commentRequired: false,
                defaultLifecycleEnabled: true,
                defaultAction: 'Promote',
                deletableStates: ['Unpublished'],
                publishedStates: ['Published'],
                lifecycleEnabled: true
            },
            ui: {
                icon: 'fw fw-resource'
            },
            categories: {
                categoryField: 'overview_category'
            },
            thumbnail: 'images_thumbnail',
            banner: 'images_banner',
            nameAttribute: 'overview_name',
            versionAttribute: 'overview_version',
            providerAttribute: 'overview_provider',
            timestamp: 'overview_createdtime',
            grouping: {
                groupingEnabled: false,
                groupingAttributes: ['overview_name']
            },
            permissions: {
                configureRegistryPermissions: function(ctx) {
                }
            }
        }
    };
};
asset.renderer = function(ctx) {
    var type = ctx.assetType;
    var permissionAPI = require('rxt').permissions;
    var isAssetWithLifecycle = function(asset) {
        if ((asset.lifecycle) && (asset.lifecycleState)) {
            return true;
        }
        if (log.isDebugEnabled()) {
            log.debug('asset: ' + asset.name + ' does not have a lifecycle or a state.The lifecycle view will not be rendered for this asset');
        }
        return false;
    };
    var buildListLeftNav = function(page, util) {
        var navList = util.navList();
        if (permissionAPI.hasAssetPermission(permissionAPI.ASSET_CREATE, ctx.assetType, ctx.session)) {
            navList.push('Add ' + type, 'btn-add-new', util.buildUrl('create'));
            navList.push('Statistics', 'btn-stats', '/assets/' + type + '/statistics');
        }
        //navList.push('Configuration', 'icon-dashboard', util.buildUrl('configuration'));
        return navList.list();
    };
    var buildDefaultLeftNav = function(page, util) {
        var id = page.assets.id;
        var path = page.assets.path;
        var navList = util.navList();
        var isLCViewEnabled = ctx.rxtManager.isLifecycleViewEnabled(ctx.assetType);
        if (permissionAPI.hasActionPermissionforPath(path, 'write', ctx.session)) {
            navList.push('Edit', 'btn-edit', util.buildUrl('update') + '/' + id);
            navList.push('Version', 'btn-copy', util.buildUrl('copy') + '/' + id);
        }
        navList.push('Overview', 'btn-overview', util.buildUrl('details') + '/' + id);
        //Only render the view if the asset has a
        if ((isLCViewEnabled) && (isAssetWithLifecycle(page.assets))) {
            if (permissionAPI.hasAssetPermission(permissionAPI.ASSET_LIFECYCLE, ctx.assetType, ctx.session)) {
                navList.push('Life Cycle', 'btn-lifecycle', util.buildUrl('lifecycle') + '/' + id);
            }
        }
        if (permissionAPI.hasActionPermissionforPath(path, 'delete', ctx.session)) {
            navList.push('Delete', 'btn-delete', util.buildUrl('delete') + '/' + id);
        }
        return navList.list();
    };
    var buildAddLeftNav = function(page, util) {
        return [];
    };
    var isActivatedAsset = function(assetType) {
        var app = require('rxt').app;
        var activatedAssets = app.getUIActivatedAssets(ctx.tenantId); //ctx.tenantConfigs.assets;
        //return true;
        if (!activatedAssets) {
            throw 'Unable to load all activated assets for current tenant: ' + ctx.tenatId + '.Make sure that the assets property is present in the tenant config';
        }
        for (var index in activatedAssets) {
            if (activatedAssets[index] == assetType) {
                return true;
            }
        }
        return false;
    };
    return {
        list: function(page) {
            var assets = page.assets;
            for (var index in assets) {
                var asset = assets[index];
                var timestampAttribute = ctx.rxtManager.getTimeStampAttribute(ctx.assetType);
                if (asset.attributes.hasOwnProperty(timestampAttribute)) {
                    var value = asset.attributes[timestampAttribute];
                    var date = new Date();
                    date.setTime(value);
                    asset.attributes[timestampAttribute] = date.toUTCString();
                }
            }
            require('/modules/page-decorators.js').pageDecorators.assetCategoryDetails(ctx, page, this);
        },
        details: function(page) {
            var tables = page.assets.tables;
            //TODO:This cannot be hardcoded
            var timestampAttribute = 'createdtime'; //ctx.rxtManager.getTimeStampAttribute(this.assetType);
            for (var index in tables) {
                var table = tables[index];
                if ((table.name == 'overview') && (table.fields.hasOwnProperty(timestampAttribute))) {
                    var value = table.fields[timestampAttribute].value || '';
                    var date = new Date();
                    date.setTime(value);
                    table.fields[timestampAttribute].value = date.toUTCString();
                }
            }
        },
        create: function(page) {
            var tables = page.assets.tables;
            var providerAttribute = 'provider';
            for (var index in tables) {
                var table = tables[index];
                if ((table.name == 'overview') && (table.fields.hasOwnProperty(providerAttribute))) {
                    table.fields[providerAttribute].value = page.cuser.username;
                }
            }
        },
        update: function(page) {
            var tables = page.assets.tables;
            var timestampAttribute = 'createdtime';
            for (var index in tables) {
                var table = tables[index];
                if ((table.name == 'overview') && (table.fields.hasOwnProperty(timestampAttribute))) {
                    var value = table.fields[timestampAttribute].value;
                    var date = new Date();
                    date.setTime(value);
                    table.fields[timestampAttribute].value = date.toUTCString();
                }
            }
        },
        pageDecorators: {
            leftNav: function(page) {
                if (log.isDebugEnabled()) {
                    log.debug('Using default leftNav');
                }
                switch (page.meta.pageName) {
                    case 'list':
                        page.leftNav = buildListLeftNav(page, this);
                        break;
                    case 'create':
                        page.leftNav = buildAddLeftNav(page, this);
                        break;
                    case 'statistics':
                        page.leftNav = buildListLeftNav(page, this);
                        break;
                    default:
                        page.leftNav = buildDefaultLeftNav(page, this);
                        break;
                }
                if (page.leftNav) {
                    for (var navItem in page.leftNav) {
                        if (page.leftNav[navItem].name) {
                            page.leftNav[navItem].id = page.leftNav[navItem].name.replace(/\s/g, "");
                        }
                    }
                }
                return page;
            },
            ribbon: function(page) {
                var ribbon = page.ribbon = {};
                var DEFAULT_ICON = 'fw fw-circle';
                var assetTypes = [];
                var assetType;
                var assetList = ctx.rxtManager.listRxtTypeDetails();
                for (var index in assetList) {
                    assetType = assetList[index];
                    //Only populate the link if the asset type is activated and the logged in user has permission to that asset
                    if ((isActivatedAsset(assetType.shortName)) && (permissionAPI.hasAssetPermission(permissionAPI.ASSET_LIST, assetType.shortName, ctx.session))) {
                        assetTypes.push({
                            url: this.buildBaseUrl(assetType.shortName) + '/list',
                            assetIcon: assetType.ui.icon || DEFAULT_ICON,
                            assetTitle: assetType.singularLabel
                        });
                    }
                }
                ribbon.currentType = page.rxt.singularLabel;
                ribbon.currentTitle = page.rxt.singularLabel;
                ribbon.currentUrl = this.buildBaseUrl(type) + '/list'; //page.meta.currentPage;
                ribbon.shortName = page.rxt.singularLabel;
                ribbon.query = 'Query';
                ribbon.breadcrumb = assetTypes;
                return page;
            },
            populateAttachedLifecycles: function(page) {
                if (page.assets.id) {
                    require('/modules/page-decorators.js').pageDecorators.populateAttachedLifecycles(ctx, page, this);
                }
            },
            populateAssetVersionDetails: function(page) {
                if (page.assets.id) {
                    require('/modules/page-decorators.js').pageDecorators.populateAssetVersionDetails(ctx, page, this);
                }
            },
            populateGroupingFeatureDetails: function(page) {
                require('/modules/page-decorators.js').pageDecorators.populateGroupingFeatureDetails(ctx, page);
            },
            populateTags: function(page){
                if(page.assets.id){
                    require('/modules/page-decorators.js').pageDecorators.populateTagDetails(ctx,page);
                }
            },
            sorting: function(page){
                require('/modules/page-decorators.js').pageDecorators.sorting(ctx,page);
            }
        }
    };
};
<|endoftext|>


<commit_before>// ==========================================================================
//                          DG.CasePlotView
//
//  Author:   William Finzer
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed 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.
// ==========================================================================

sc_require('components/graph/plots/plot_view');

/** @class DG.CasePlotView - A plot of dots placed according to numeric values

 @extends DG.PlotView
 */
DG.CasePlotView = DG.PlotView.extend(
    /** @scope DG.CasePlotView.prototype */
    (function () {

      function dataToCoordinate(iValue, iMin, iMax, iMargin) {
        return iMin + iMargin + iValue * (iMax - iMin - 2 * iMargin);
      }

      function addHandlers(iView, iElOrPointSet) {
        function changeCaseValues(iIndex, iWorldValues) {
          iView.get('model').setWorldCoords(iIndex, iWorldValues);
        }

        var tIsDragging = false,
            kOpaque = 1,
            tInitialTransform = null;
        if (iElOrPointSet.events) {
          iElOrPointSet.events.forEach(function (iHandler) {
            iHandler.unbind();
          });
          iElOrPointSet.events.length = 0;
        }

        iElOrPointSet
            .hover(function (event) {  // over
                  // Note that Firefox can come through here repeatedly so we have to check for existence
                  if (!tIsDragging && SC.none(tInitialTransform)) {
                    tInitialTransform = this.transform();
                    this.animate({
                      opacity: kOpaque,
                      transform: DG.PlotUtilities.kDataHoverTransform
                    }, DG.PlotUtilities.kDataTipShowTime);
                    iView.showDataTip(this, this.index);
                  }
                },
                function (event) { // out
                  iView.hideDataTip();
                  if (!tIsDragging) {
                    this.stop();
                    this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                    tInitialTransform = null;
                  }
                })
            .mousedown(function (iEvent) {
              iView.get('model').selectCaseByIndex(this.index, iEvent.shiftKey);
            })
            .drag(function (dx, dy) { // continue
                  // TODO: drag all selected cases, not just this case.
                  var tWorldX = iView.get('xAxisView').coordinateToData(this.ox + dx),
                      tWorldY = iView.get('yAxisView').coordinateToData(this.oy + dy),
                      tPoint = {x: tWorldX, y: tWorldY},
                      tRC = iView.createRenderContext(),
                      tCurrTransform = this.transform();
                  if (isFinite(tPoint.x) && isFinite(tPoint.y)) {
                    // Put the element into the initial transformed state so that changing case values
                    // will not be affected by the scaling in the current transform.
                    this.transform(tInitialTransform);
                    changeCaseValues(this.index, tPoint);
                    this.transform(tCurrTransform);
                  }
                  iView.setCircleCoordinate(tRC, iView.getPath('model.cases').at(this.index), this.index);
                },
                function (x, y) { // begin
                  tIsDragging = true;
                  this.ox = this.attr("cx");
                  this.oy = this.attr("cy");
                  this.animate({opacity: kOpaque}, DG.PlotUtilities.kDataTipShowTime, "bounce");
                  this.toFront();
                },
                function () {  // end
                  this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                  tIsDragging = false;
                });
      }

      return {

        handlersMustBeInstalledToExistingElements: true,

        init: function () {
          sc_super();
        },

        dataRangeDidChange: function (iSource, iQuestion, iKey, iChanges) {
          var this_ = this,
              tPlotElementLength = this.get('plottedElements').length,
              tCases = this.getPath('model.cases'),
              tRC = this.createRenderContext();

          // iChanges can be a single index or an array of indices
          var tChanges = (SC.typeOf(iChanges) === SC.T_NUMBER ? [iChanges] : iChanges);
          tChanges = tChanges || [];
          tChanges.forEach(function (iIndex) {
            var tCase = tCases.at(iIndex);
            if (iIndex >= tPlotElementLength)
              this_.callCreateElement(tCase, iIndex, this_._createAnimationOn);
            this_.setCircleCoordinate(tRC, tCase, iIndex);
          });
          sc_super();
        },

        /**
         * Override because we do things a little differently
         * @param iElement
         * @param iIndex
         * @param iAnimate
         * @return {{events}|*}
         */
        assignElementAttributes: function (iElement, iIndex, iAnimate) {
          addHandlers( this, iElement);
          iElement.index = iIndex;
          if (iAnimate)
            DG.PlotUtilities.doCreateCircleAnimation(iElement);
          return iElement;
        },

        /**
         * Set the coordinates and other attributes of the case circle (a Rafael element in this.get('plottedElements')).
         * @param iRC {} case-invariant Render Context
         * @param iCase {DG.Case} the case data
         * @param iIndex {number} index of case in collection
         * @param iAnimate {Boolean} (optional) want changes to be animated into place?
         * @param iCallback {Function} Will be called when animation finished
         * @returns {cx {Number},cy {Number}} final coordinates or null if not defined (hidden plot element)
         */
        setCircleCoordinate: function (iRC, iCase, iIndex, iAnimate, iCallback) {
          DG.assert(iRC && iRC.xAxisView, 'incomplete render context');
          DG.assert(iCase, 'no case');
          DG.assert(DG.MathUtilities.isInIntegerRange(iIndex, 0, this.get('plottedElements').length),
              'out of bounds', 'Index for circle element');

          var tCircle = this.get('plottedElements')[iIndex],
              tRadius = this.radiusForCircleElement(tCircle),
              tWorldCoords = this.get('model').getWorldCoords(iIndex),
              tCoordX = dataToCoordinate(tWorldCoords.x,
                  iRC.xAxisView.get('pixelMin'), iRC.xAxisView.get('pixelMax'), tRadius),
              tCoordY = dataToCoordinate(tWorldCoords.y,
                  iRC.yAxisView.get('pixelMin'), iRC.yAxisView.get('pixelMax'), tRadius),
              tIsMissingCase = SC.none(tCoordX) || SC.none(tCoordY);

          // show or hide if needed, then update if shown.
          if (this.showHidePlottedElement(tCircle, tIsMissingCase, iIndex)) {
            var tAttrs = {
              cx: tCoordX,
              cy: tCoordY,
              r: tRadius,
              fill: iRC.calcCaseColorString(iCase),
              stroke: iRC.calcStrokeColorString(iCase),
              'fill-opacity': iRC.transparency,
              'stroke-opacity': iRC.strokeTransparency
            };
            this.updatePlottedElement(tCircle, tAttrs, iAnimate, iCallback);
            return {cx: tCoordX, cy: tCoordY, r: tRadius};
          }
          return null;
        },

        createElement: function (iDatum, iIndex, iAnimate) {

          var tPaper = this.get('paper'),
              tCircle = tPaper.circle(tPaper.width / 2, tPaper.height / 2, this._pointRadius)
                  .attr({cursor: "pointer"})
                  .addClass(DG.PlotUtilities.kDotClassName);
          tCircle.index = iIndex;
          tCircle.node.setAttribute('shape-rendering', 'geometric-precision');
          addHandlers(this, tCircle);
          return tCircle;
        },

        /**
         We may clear and draw everything from scratch if required.
         */
        drawData: function () {
          if (!this.get('paper') || !this.get('model') || !this.getPath('model.cases') || this.getPath('model.isAnimating'))
            return;

          if (this.handlersMustBeInstalledToExistingElements) {
            var this_ = this;
            this.get('plottedElements').forEach(function (iElement) {
              addHandlers(this_, iElement);
            });
            this.handlersMustBeInstalledToExistingElements = false;
          }

          if (this.getPath('model.isAnimating'))
            return; // Points are animating to new position

          function createCircleDescription(iIndex) {
            var tWorldCoords = tModel.getWorldCoords(iIndex);
            return {
              type: 'circle',
              cx: dataToCoordinate(tWorldCoords.x, tXPixelMin, tXPixelMax, kMargin),
              cy: dataToCoordinate(tWorldCoords.y, tYPixelMin, tYPixelMax, kMargin),
              r: tR / 2,
              fill: 'yellow',
              stroke: tStrokeParams.strokeColor,
              'stroke-opacity': tStrokeParams.strokeTransparency,
              cursor: 'pointer'
            };
          }

          var tRC = this.createRenderContext(),
              tXPixelMin = this.getPath('xAxisView.pixelMin'),
              tXPixelMax = this.getPath('xAxisView.pixelMax'),
              tYPixelMax = this.getPath('yAxisView.pixelMin'),
              tYPixelMin = this.getPath('yAxisView.pixelMax'),

              tPointSet,
              tPlottedElements = this.get('plottedElements'),
              tModel = this.get('model'),
              tNumCases = tModel.get('cases').length(),
              tR = this.calcPointRadius(),
              kMargin = tR,
              tColor = tModel.getPointColor ? tModel.getPointColor() : DG.PlotUtilities.kDefaultPointColor,
              tStrokeParams = this.getStrokeParams(),
              tLayer = this.getPath('layerManager.' + DG.LayerNames.kPoints);
          if (!tPlottedElements || tPlottedElements.length === 0) {
            var tInc = 200,
                i,
                loop = function () {
                  if (tPlottedElements.length >= tNumCases)
                    return;
                  var tStart = tPlottedElements.length,
                      tDescriptions = [];
                  tXPixelMax = this.getPath('xAxisView.pixelMax');
                  tYPixelMax = this.getPath('yAxisView.pixelMin');
                  if (tXPixelMax < 30 || tYPixelMax < 30) {
                    this.invokeLater(loop, 100);
                    return;
                  }
                  for (i = tStart; i < tStart + tInc && i < tNumCases; i++) {
                    tDescriptions.push(createCircleDescription(i));
                  }
                  tPointSet = this.get('paper').add(tDescriptions).forEach(function (iElement, iIndex) {
                    tPlottedElements.push(iElement);
                    tLayer.push(iElement);
                    iElement.addClass(DG.PlotUtilities.kDotClassName);
                    iElement.index = tStart + iIndex;
                    iElement.node.setAttribute('shape-rendering', 'geometric-precision');
                    return true;
                  });
                  addHandlers(this, tPointSet);
                  this.setPath('model.isAnimating', true);
                  tPointSet.animate({
                        r: tR,
                        fill: tColor
                      },
                      DG.PlotUtilities.kDefaultAnimationTime, '<>');
                  tStart += tInc;
                  tInc *= 2;
                  if (tStart < tNumCases) {
                    this.invokeLater(loop, 10);
                  }
                  else {
                    this.setPath('model.isAnimating', false);
                  }
                  tPointSet.clear();
                }.bind(this);
            loop();
            this._elementOrderIsValid = false;
          }
          else if (tPlottedElements.length > 0 && !this.getPath('model.isAnimating')) {
            tPlottedElements.forEach(function (iCircle, iIndex) {
              var tCoords = tModel.getWorldCoords(iIndex);
              iCircle.attr({
                fill: tColor,
                'fill-opacity': tRC.transparency,
                'opacity': tRC.transparency
              });
              iCircle.show();
              iCircle.animate({
                cx: dataToCoordinate(tCoords.x, tXPixelMin, tXPixelMax, kMargin),
                cy: dataToCoordinate(tCoords.y, tYPixelMin, tYPixelMax, kMargin)
              }, DG.PlotUtilities.kDefaultAnimationTime, '<>');
            });
          }
        },

        /**
         Generate the svg needed to display the plot
         */
        doDraw: function doDraw() {
          sc_super();

          this.drawData();

          this.updateSelection();

        },

        /**
         * The array object my model uses to store world coords has been swapped out. We want to animate to new positions.
         */
        worldValuesChanged: function () {
          var tPlottedElements = this.get('plottedElements'),
              tLastIndex = tPlottedElements.length - 1,
              kMargin = this.calcPointRadius(),
              tXPixelMin = this.getPath('xAxisView.pixelMin'),
              tXPixelMax = this.getPath('xAxisView.pixelMax'),
              tYPixelMax = this.getPath('yAxisView.pixelMin'),
              tYPixelMin = this.getPath('yAxisView.pixelMax'),
              tModel = this.get('model');
          tModel.set('isAnimating', true);
          tPlottedElements.forEach(function (iElement, iIndex) {
                var tCoords = tModel.getWorldCoords(iIndex);
                iElement.animate({
                      cx: dataToCoordinate(tCoords.x, tXPixelMin, tXPixelMax, kMargin),
                      cy: dataToCoordinate(tCoords.y, tYPixelMin, tYPixelMax, kMargin)
                    },
                    DG.PlotUtilities.kDefaultAnimationTime, '<>',
                    iIndex === tLastIndex ? function () {
                      tModel.set('isAnimating', false);
                    } : null);
              }
          );
          this._isRenderingValid = false;

          // this.displayDidChange();
        }.observes('.model.worldValues')
      };
    }()));<commit_msg>[#177070138] Bug fix: Display only selected cases doesn't behave correctly when there are no axis attributes<commit_after>// ==========================================================================
//                          DG.CasePlotView
//
//  Author:   William Finzer
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed 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.
// ==========================================================================

sc_require('components/graph/plots/plot_view');

/** @class DG.CasePlotView - A plot of dots placed according to numeric values

 @extends DG.PlotView
 */
DG.CasePlotView = DG.PlotView.extend(
    /** @scope DG.CasePlotView.prototype */
    (function () {

      function dataToCoordinate(iValue, iMin, iMax, iMargin) {
        return iMin + iMargin + iValue * (iMax - iMin - 2 * iMargin);
      }

      function addHandlers(iView, iElOrPointSet) {
        function changeCaseValues(iIndex, iWorldValues) {
          iView.get('model').setWorldCoords(iIndex, iWorldValues);
        }

        var tIsDragging = false,
            kOpaque = 1,
            tInitialTransform = null;
        if (iElOrPointSet.events) {
          iElOrPointSet.events.forEach(function (iHandler) {
            iHandler.unbind();
          });
          iElOrPointSet.events.length = 0;
        }

        iElOrPointSet
            .hover(function (event) {  // over
                  // Note that Firefox can come through here repeatedly so we have to check for existence
                  if (!tIsDragging && SC.none(tInitialTransform)) {
                    tInitialTransform = this.transform();
                    this.animate({
                      opacity: kOpaque,
                      transform: DG.PlotUtilities.kDataHoverTransform
                    }, DG.PlotUtilities.kDataTipShowTime);
                    iView.showDataTip(this, this.index);
                  }
                },
                function (event) { // out
                  iView.hideDataTip();
                  if (!tIsDragging) {
                    this.stop();
                    this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                    tInitialTransform = null;
                  }
                })
            .mousedown(function (iEvent) {
              iView.get('model').selectCaseByIndex(this.index, iEvent.shiftKey);
            })
            .drag(function (dx, dy) { // continue
                  // TODO: drag all selected cases, not just this case.
                  var tWorldX = iView.get('xAxisView').coordinateToData(this.ox + dx),
                      tWorldY = iView.get('yAxisView').coordinateToData(this.oy + dy),
                      tPoint = {x: tWorldX, y: tWorldY},
                      tRC = iView.createRenderContext(),
                      tCurrTransform = this.transform();
                  if (isFinite(tPoint.x) && isFinite(tPoint.y)) {
                    // Put the element into the initial transformed state so that changing case values
                    // will not be affected by the scaling in the current transform.
                    this.transform(tInitialTransform);
                    changeCaseValues(this.index, tPoint);
                    this.transform(tCurrTransform);
                  }
                  iView.setCircleCoordinate(tRC, iView.getPath('model.cases').at(this.index), this.index);
                },
                function (x, y) { // begin
                  tIsDragging = true;
                  this.ox = this.attr("cx");
                  this.oy = this.attr("cy");
                  this.animate({opacity: kOpaque}, DG.PlotUtilities.kDataTipShowTime, "bounce");
                  this.toFront();
                },
                function () {  // end
                  this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                  tIsDragging = false;
                });
      }

      return {

        handlersMustBeInstalledToExistingElements: true,

        init: function () {
          sc_super();
        },

        dataRangeDidChange: function (iSource, iQuestion, iKey, iChanges) {
          var this_ = this,
              tPlotElementLength = this.get('plottedElements').length,
              tCases = this.getPath('model.cases'),
              tRC = this.createRenderContext();

          // iChanges can be a single index or an array of indices
          var tChanges = (SC.typeOf(iChanges) === SC.T_NUMBER ? [iChanges] : iChanges);
          tChanges = tChanges || [];
          tChanges.forEach(function (iIndex) {
            var tCase = tCases.at(iIndex);
            if (iIndex >= tPlotElementLength)
              this_.callCreateElement(tCase, iIndex, this_._createAnimationOn);
            this_.setCircleCoordinate(tRC, tCase, iIndex);
          });
          sc_super();
        },

        /**
         * Override because we do things a little differently
         * @param iElement
         * @param iIndex
         * @param iAnimate
         * @return {{events}|*}
         */
        assignElementAttributes: function (iElement, iIndex, iAnimate) {
          addHandlers( this, iElement);
          iElement.index = iIndex;
          if (iAnimate)
            DG.PlotUtilities.doCreateCircleAnimation(iElement);
          return iElement;
        },

        /**
         * Set the coordinates and other attributes of the case circle (a Rafael element in this.get('plottedElements')).
         * @param iRC {} case-invariant Render Context
         * @param iCase {DG.Case} the case data
         * @param iIndex {number} index of case in collection
         * @param iAnimate {Boolean} (optional) want changes to be animated into place?
         * @param iCallback {Function} Will be called when animation finished
         * @returns {cx {Number},cy {Number}} final coordinates or null if not defined (hidden plot element)
         */
        setCircleCoordinate: function (iRC, iCase, iIndex, iAnimate, iCallback) {
          DG.assert(iRC && iRC.xAxisView, 'incomplete render context');
          DG.assert(iCase, 'no case');
          DG.assert(DG.MathUtilities.isInIntegerRange(iIndex, 0, this.get('plottedElements').length),
              'out of bounds', 'Index for circle element');

          var tCircle = this.get('plottedElements')[iIndex],
              tRadius = this.radiusForCircleElement(tCircle),
              tWorldCoords = this.get('model').getWorldCoords(iIndex),
              tCoordX = dataToCoordinate(tWorldCoords.x,
                  iRC.xAxisView.get('pixelMin'), iRC.xAxisView.get('pixelMax'), tRadius),
              tCoordY = dataToCoordinate(tWorldCoords.y,
                  iRC.yAxisView.get('pixelMin'), iRC.yAxisView.get('pixelMax'), tRadius),
              tIsMissingCase = SC.none(tCoordX) || SC.none(tCoordY);

          // show or hide if needed, then update if shown.
          if (this.showHidePlottedElement(tCircle, tIsMissingCase, iIndex)) {
            var tAttrs = {
              cx: tCoordX,
              cy: tCoordY,
              r: tRadius,
              fill: iRC.calcCaseColorString(iCase),
              stroke: iRC.calcStrokeColorString(iCase),
              'fill-opacity': iRC.transparency,
              'stroke-opacity': iRC.strokeTransparency
            };
            this.updatePlottedElement(tCircle, tAttrs, iAnimate, iCallback);
            return {cx: tCoordX, cy: tCoordY, r: tRadius};
          }
          return null;
        },

        createElement: function (iDatum, iIndex, iAnimate) {

          var tPaper = this.get('paper'),
              tCircle = tPaper.circle(tPaper.width / 2, tPaper.height / 2, this._pointRadius)
                  .attr({cursor: "pointer"})
                  .addClass(DG.PlotUtilities.kDotClassName);
          tCircle.index = iIndex;
          tCircle.node.setAttribute('shape-rendering', 'geometric-precision');
          addHandlers(this, tCircle);
          return tCircle;
        },

        /**
         We may clear and draw everything from scratch if required.
         */
        drawData: function () {
          var this_ = this;
          if (!this.get('paper') || !this.get('model') || !this.getPath('model.cases') || this.getPath('model.isAnimating'))
            return;

          if (this.handlersMustBeInstalledToExistingElements) {
            this.get('plottedElements').forEach(function (iElement) {
              addHandlers(this_, iElement);
            });
            this.handlersMustBeInstalledToExistingElements = false;
          }

          if (this.getPath('model.isAnimating'))
            return; // Points are animating to new position

          function createCircleDescription(iIndex) {
            var tWorldCoords = tModel.getWorldCoords(iIndex);
            return {
              type: 'circle',
              cx: dataToCoordinate(tWorldCoords.x, tXPixelMin, tXPixelMax, kMargin),
              cy: dataToCoordinate(tWorldCoords.y, tYPixelMin, tYPixelMax, kMargin),
              r: tR / 2,
              fill: 'yellow',
              stroke: tStrokeParams.strokeColor,
              'stroke-opacity': tStrokeParams.strokeTransparency,
              cursor: 'pointer'
            };
          }

          function doCreatePlotElements() {
            var tInc = 200,
                i,
                loop = function () {
                  if (tPlottedElements.length >= tNumCases)
                    return;
                  var tStart = tPlottedElements.length,
                      tDescriptions = [];
                  tXPixelMax = this_.getPath('xAxisView.pixelMax');
                  tYPixelMax = this_.getPath('yAxisView.pixelMin');
                  if (tXPixelMax < 30 || tYPixelMax < 30) {
                    this_.invokeLater(loop, 100);
                    return;
                  }
                  for (i = tStart; i < tStart + tInc && i < tNumCases; i++) {
                    tDescriptions.push(createCircleDescription(i));
                  }
                  tPointSet = this_.get('paper').add(tDescriptions).forEach(function (iElement, iIndex) {
                    tPlottedElements.push(iElement);
                    tLayer.push(iElement);
                    iElement.addClass(DG.PlotUtilities.kDotClassName);
                    iElement.index = tStart + iIndex;
                    iElement.node.setAttribute('shape-rendering', 'geometric-precision');
                    return true;
                  });
                  addHandlers(this_, tPointSet);
                  this_.setPath('model.isAnimating', true);
                  tPointSet.animate({
                        r: tR,
                        fill: tColor
                      },
                      DG.PlotUtilities.kDefaultAnimationTime, '<>');
                  tStart += tInc;
                  tInc *= 2;
                  if (tStart < tNumCases) {
                    this_.invokeLater(loop, 10);
                  } else {
                    this_.setPath('model.isAnimating', false);
                  }
                  tPointSet.clear();
                }.bind(this_);
            loop();
            this_._elementOrderIsValid = false;
          }

          var tRC = this.createRenderContext(),
              tXPixelMin = this.getPath('xAxisView.pixelMin'),
              tXPixelMax = this.getPath('xAxisView.pixelMax'),
              tYPixelMax = this.getPath('yAxisView.pixelMin'),
              tYPixelMin = this.getPath('yAxisView.pixelMax'),

              tPointSet,
              tPlottedElements = this.get('plottedElements'),
              tModel = this.get('model'),
              tNumCases = tModel.get('cases').length(),
              tR = this.calcPointRadius(),
              kMargin = tR,
              tColor = tModel.getPointColor ? tModel.getPointColor() : DG.PlotUtilities.kDefaultPointColor,
              tStrokeParams = this.getStrokeParams(),
              tLayer = this.getPath('layerManager.' + DG.LayerNames.kPoints);
          if (!tPlottedElements || tPlottedElements.length < tNumCases) {
            doCreatePlotElements();
          } else if (tPlottedElements.length > 0 && !this.getPath('model.isAnimating')) {
            if (tPlottedElements.length > tNumCases) {
              var tLayerManager = this.get('layerManager');
              for (var index = tNumCases; index < tPlottedElements.length; index++) {
                if (!SC.none(tPlottedElements[index])) {
                  tPlottedElements[index].stop();
                  tLayerManager.removeElement(tPlottedElements[index]);
                  DG.PlotUtilities.doHideRemoveAnimation(tPlottedElements[index], tLayerManager);
                }
              }
              tPlottedElements.length = tNumCases;
            }
            tPlottedElements.forEach(function (iCircle, iIndex) {
              var tCoords = tModel.getWorldCoords(iIndex);
              iCircle.attr({
                fill: tColor,
                'fill-opacity': tRC.transparency,
                'opacity': tRC.transparency
              });
              iCircle.show();
              iCircle.animate({
                cx: dataToCoordinate(tCoords.x, tXPixelMin, tXPixelMax, kMargin),
                cy: dataToCoordinate(tCoords.y, tYPixelMin, tYPixelMax, kMargin)
              }, DG.PlotUtilities.kDefaultAnimationTime, '<>');
            });
          }
        },

        /**
         Generate the svg needed to display the plot
         */
        doDraw: function doDraw() {
          sc_super();

          this.drawData();

          this.updateSelection();

        },

        /**
         * The array object my model uses to store world coords has been swapped out. We want to animate to new positions.
         */
        worldValuesChanged: function () {
          var tPlottedElements = this.get('plottedElements'),
              tLastIndex = tPlottedElements.length - 1,
              kMargin = this.calcPointRadius(),
              tXPixelMin = this.getPath('xAxisView.pixelMin'),
              tXPixelMax = this.getPath('xAxisView.pixelMax'),
              tYPixelMax = this.getPath('yAxisView.pixelMin'),
              tYPixelMin = this.getPath('yAxisView.pixelMax'),
              tModel = this.get('model');
          tModel.set('isAnimating', true);
          tPlottedElements.forEach(function (iElement, iIndex) {
                var tCoords = tModel.getWorldCoords(iIndex);
                iElement.animate({
                      cx: dataToCoordinate(tCoords.x, tXPixelMin, tXPixelMax, kMargin),
                      cy: dataToCoordinate(tCoords.y, tYPixelMin, tYPixelMax, kMargin)
                    },
                    DG.PlotUtilities.kDefaultAnimationTime, '<>',
                    iIndex === tLastIndex ? function () {
                      tModel.set('isAnimating', false);
                    } : null);
              }
          );
          this._isRenderingValid = false;

          // this.displayDidChange();
        }.observes('.model.worldValues')
      };
    }()));<|endoftext|>


<commit_before>/**
 * modules/analytics data store: accounts.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from '.';

const { createRegistrySelector } = Data;

// Actions
const FETCH_ACCOUNTS_PROPERTIES_PROFILES = 'FETCH_ACCOUNTS_PROPERTIES_PROFILES';
const RECEIVE_ACCOUNTS = 'RECEIVE_ACCOUNTS';
const RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED = 'RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED';
// Values
const ACCOUNT_CREATE = 'account_create';

export const INITIAL_STATE = {
	accounts: undefined,
	isFetchingAccountsPropertiesProfiles: false,
};

export const actions = {
	fetchAccountsPropertiesProfiles() {
		return {
			payload: {},
			type: FETCH_ACCOUNTS_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Creates an action for receiving accounts.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} accounts Accounts to receive.
	 * @return {Object} action object.
	 */
	receiveAccounts( accounts ) {
		invariant( Array.isArray( accounts ), 'accounts must be an array.' );

		return {
			payload: { accounts },
			type: RECEIVE_ACCOUNTS,
		};
	},

	receiveAccountsPropertiesProfilesCompleted() {
		return {
			payload: {},
			type: RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receiveAccountsPropertiesProfilesFailed( error ) {
		invariant( error, 'error is required.' );

		return {
			payload: { error },
			type: RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_ACCOUNTS_PROPERTIES_PROFILES ]: () => {
		return API.get( 'modules', 'analytics', 'accounts-properties-profiles' );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_ACCOUNTS_PROPERTIES_PROFILES: {
			return {
				...state,
				isFetchingAccountsPropertiesProfiles: true,
			};
		}

		case RECEIVE_ACCOUNTS: {
			const { accounts } = payload;

			return {
				...state,
				accounts: [ ...accounts ],
			};
		}

		case RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED: {
			return {
				...state,
				isFetchingAccountsPropertiesProfiles: false,
			};
		}

		case RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED: {
			const { error } = payload;

			return {
				...state,
				error,
				isFetchingAccountsPropertiesProfiles: false,
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getAccounts() {
		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingAccounts = registry.select( STORE_NAME ).getAccounts();

			// If there are already accounts loaded in state, we don't want to make this request
			// and consider this resolver fulfilled.
			if ( existingAccounts ) {
				return;
			}

			const response = yield actions.fetchAccountsPropertiesProfiles();
			const { accounts, properties, profiles, matchedProperty } = response;

			yield actions.receiveAccounts( accounts );
			yield registry.dispatch( STORE_NAME ).receiveProperties( properties );
			yield registry.dispatch( STORE_NAME ).receiveProfiles( profiles );

			if ( matchedProperty ) {
				yield registry.dispatch( STORE_NAME ).receiveMatchedProperty( matchedProperty );
			}

			return yield actions.receiveAccountsPropertiesProfilesCompleted();
		} catch ( err ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveAccountsPropertiesProfilesFailed( err );
		}
	},
};

export const selectors = {
	/**
	 * Gets all Google Analytics accounts this user can access.
	 *
	 * Returns an array of all analytics accounts.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Array.<Object>} An array of Analytics accounts; `undefined` if not loaded.
	 */
	getAccounts( state ) {
		const { accounts } = state;

		return accounts;
	},

	/**
	 * Gets an error encountered by this store or its side effects.
	 *
	 * Returns an object with the shape when there is an error:
	 * ```
	 * {
	 *   code,
	 *   message,
	 * }
	 * ```
	 *
	 * Returns `null` if there was no error.
	 *
	 * Marked as private, because in the future we'll have more robust error
	 * handling.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Object} Any error encountered with requests in state.
	 */
	getError( state ) {
		const { error } = state;

		return error || null;
	},

	/**
	 * Checks whether an Analytics account should be created or not.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @return {boolean} Whether an Analytics account should be created or not.
	 */
	isCreateAccount: createRegistrySelector( ( select ) => () => {
		return ACCOUNT_CREATE === select( STORE_NAME ).getAccountID();
	} ),

	/**
	 * Checks whether accounts are currently being fetched.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {boolean} Whether accounts are currently being fetched or not.
	 */
	isFetchingAccounts( state ) {
		return !! state.isFetchingAccountsPropertiesProfiles;
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Export ACCOUNT_CREATE for use in component.<commit_after>/**
 * modules/analytics data store: accounts.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from '.';

const { createRegistrySelector } = Data;

// Actions
const FETCH_ACCOUNTS_PROPERTIES_PROFILES = 'FETCH_ACCOUNTS_PROPERTIES_PROFILES';
const RECEIVE_ACCOUNTS = 'RECEIVE_ACCOUNTS';
const RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED = 'RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED';
// Values
export const ACCOUNT_CREATE = 'account_create';

export const INITIAL_STATE = {
	accounts: undefined,
	isFetchingAccountsPropertiesProfiles: false,
};

export const actions = {
	fetchAccountsPropertiesProfiles() {
		return {
			payload: {},
			type: FETCH_ACCOUNTS_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Creates an action for receiving accounts.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} accounts Accounts to receive.
	 * @return {Object} action object.
	 */
	receiveAccounts( accounts ) {
		invariant( Array.isArray( accounts ), 'accounts must be an array.' );

		return {
			payload: { accounts },
			type: RECEIVE_ACCOUNTS,
		};
	},

	receiveAccountsPropertiesProfilesCompleted() {
		return {
			payload: {},
			type: RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receiveAccountsPropertiesProfilesFailed( error ) {
		invariant( error, 'error is required.' );

		return {
			payload: { error },
			type: RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_ACCOUNTS_PROPERTIES_PROFILES ]: () => {
		return API.get( 'modules', 'analytics', 'accounts-properties-profiles' );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_ACCOUNTS_PROPERTIES_PROFILES: {
			return {
				...state,
				isFetchingAccountsPropertiesProfiles: true,
			};
		}

		case RECEIVE_ACCOUNTS: {
			const { accounts } = payload;

			return {
				...state,
				accounts: [ ...accounts ],
			};
		}

		case RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_COMPLETED: {
			return {
				...state,
				isFetchingAccountsPropertiesProfiles: false,
			};
		}

		case RECEIVE_ACCOUNTS_PROPERTIES_PROFILES_FAILED: {
			const { error } = payload;

			return {
				...state,
				error,
				isFetchingAccountsPropertiesProfiles: false,
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getAccounts() {
		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingAccounts = registry.select( STORE_NAME ).getAccounts();

			// If there are already accounts loaded in state, we don't want to make this request
			// and consider this resolver fulfilled.
			if ( existingAccounts ) {
				return;
			}

			const response = yield actions.fetchAccountsPropertiesProfiles();
			const { accounts, properties, profiles, matchedProperty } = response;

			yield actions.receiveAccounts( accounts );
			yield registry.dispatch( STORE_NAME ).receiveProperties( properties );
			yield registry.dispatch( STORE_NAME ).receiveProfiles( profiles );

			if ( matchedProperty ) {
				yield registry.dispatch( STORE_NAME ).receiveMatchedProperty( matchedProperty );
			}

			return yield actions.receiveAccountsPropertiesProfilesCompleted();
		} catch ( err ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveAccountsPropertiesProfilesFailed( err );
		}
	},
};

export const selectors = {
	/**
	 * Gets all Google Analytics accounts this user can access.
	 *
	 * Returns an array of all analytics accounts.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Array.<Object>} An array of Analytics accounts; `undefined` if not loaded.
	 */
	getAccounts( state ) {
		const { accounts } = state;

		return accounts;
	},

	/**
	 * Gets an error encountered by this store or its side effects.
	 *
	 * Returns an object with the shape when there is an error:
	 * ```
	 * {
	 *   code,
	 *   message,
	 * }
	 * ```
	 *
	 * Returns `null` if there was no error.
	 *
	 * Marked as private, because in the future we'll have more robust error
	 * handling.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Object} Any error encountered with requests in state.
	 */
	getError( state ) {
		const { error } = state;

		return error || null;
	},

	/**
	 * Checks whether an Analytics account should be created or not.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @return {boolean} Whether an Analytics account should be created or not.
	 */
	isCreateAccount: createRegistrySelector( ( select ) => () => {
		return ACCOUNT_CREATE === select( STORE_NAME ).getAccountID();
	} ),

	/**
	 * Checks whether accounts are currently being fetched.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {boolean} Whether accounts are currently being fetched or not.
	 */
	isFetchingAccounts( state ) {
		return !! state.isFetchingAccountsPropertiesProfiles;
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before>/**
 * `modules/analytics` data store: settings.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { MODULES_TAGMANAGER } from '../../tagmanager/datastore/constants';
import { MODULES_ANALYTICS_4 } from '../../analytics-4/datastore/constants';
import { INVARIANT_DOING_SUBMIT_CHANGES, INVARIANT_SETTINGS_NOT_CHANGED } from '../../../googlesitekit/data/create-settings-store';
import { TYPE_MODULES } from '../../../components/data/constants';
import { invalidateCacheGroup } from '../../../components/data/invalidate-cache-group';
import {
	isValidAccountID,
	isValidInternalWebPropertyID,
	isValidPropertySelection,
	isValidProfileSelection,
	isValidPropertyID,
	isValidProfileName,
	isValidAdsConversionID,
} from '../util';
import { STORE_NAME, PROPERTY_CREATE, PROFILE_CREATE, FORM_SETUP } from './constants';
import { createStrictSelect } from '../../../googlesitekit/data/utils';
import { isFeatureEnabled } from '../../../features';

// Invariant error messages.
export const INVARIANT_INVALID_ACCOUNT_ID = 'a valid accountID is required to submit changes';
export const INVARIANT_INVALID_PROPERTY_SELECTION = 'a valid propertyID is required to submit changes';
export const INVARIANT_INVALID_PROFILE_SELECTION = 'a valid profileID is required to submit changes';
export const INVARIANT_INVALID_CONVERSION_ID = 'a valid adsConversionID is required to submit changes';
export const INVARIANT_INSUFFICIENT_GTM_TAG_PERMISSIONS = 'cannot submit changes without having permissions for GTM property ID';
export const INVARIANT_INVALID_PROFILE_NAME = 'a valid profile name is required to submit changes';
export const INVARIANT_INVALID_INTERNAL_PROPERTY_ID = 'cannot submit changes with incorrect internal webPropertyID';
export const INVARIANT_INSUFFICIENT_TAG_PERMISSIONS = 'cannot submit without proper permissions';

export async function submitChanges( { select, dispatch } ) {
	let propertyID = select( STORE_NAME ).getPropertyID();
	if ( propertyID === PROPERTY_CREATE ) {
		const accountID = select( STORE_NAME ).getAccountID();
		const { response: property, error } = await dispatch( STORE_NAME ).createProperty( accountID );

		if ( error ) {
			return { error };
		}

		propertyID = property.id;
		await dispatch( STORE_NAME ).setPropertyID( property.id );
		await dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId ); // eslint-disable-line sitekit/acronym-case
	}

	const profileID = select( STORE_NAME ).getProfileID();
	if ( profileID === PROFILE_CREATE ) {
		const profileName = select( CORE_FORMS ).getValue( FORM_SETUP, 'profileName' );
		const accountID = select( STORE_NAME ).getAccountID();
		const { response: profile, error } = await dispatch( STORE_NAME ).createProfile( accountID, propertyID, { profileName } );

		if ( error ) {
			return { error };
		}

		await dispatch( STORE_NAME ).setProfileID( profile.id );
	}

	// This action shouldn't be called if settings haven't changed,
	// but this prevents errors in tests.
	if ( select( STORE_NAME ).haveSettingsChanged() ) {
		const { error } = await dispatch( STORE_NAME ).saveSettings();

		if ( error ) {
			return { error };
		}
	}

	await API.invalidateCache( 'modules', 'analytics' );
	// TODO: Remove once legacy dataAPI is no longer used.
	invalidateCacheGroup( TYPE_MODULES, 'analytics' );

	if ( isFeatureEnabled( 'ga4setup' ) ) {
		if ( select( MODULES_ANALYTICS_4 ).haveSettingsChanged() ) {
			await dispatch( MODULES_ANALYTICS_4 ).submitChanges();
		}
	}

	return {};
}

export function validateCanSubmitChanges( select ) {
	const strictSelect = createStrictSelect( select );
	const {
		getAccountID,
		getAdsConversionID,
		getInternalWebPropertyID,
		getProfileID,
		getPropertyID,
		hasExistingTagPermission,
		hasTagPermission,
		haveSettingsChanged,
		isDoingSubmitChanges,
	} = strictSelect( STORE_NAME );

	// Note: these error messages are referenced in test assertions.
	invariant( ! isDoingSubmitChanges(), INVARIANT_DOING_SUBMIT_CHANGES );

	const gtmIsActive = strictSelect( CORE_MODULES ).isModuleActive( 'tagmanager' );
	if ( gtmIsActive ) {
		const gtmAnalyticsPropertyID = strictSelect( MODULES_TAGMANAGER ).getSingleAnalyticsPropertyID();
		invariant(
			! isValidPropertyID( gtmAnalyticsPropertyID ) || hasTagPermission( gtmAnalyticsPropertyID ) !== false,
			INVARIANT_INSUFFICIENT_GTM_TAG_PERMISSIONS
		);
	}

	invariant( haveSettingsChanged(), INVARIANT_SETTINGS_NOT_CHANGED );
	invariant( isValidAccountID( getAccountID() ), INVARIANT_INVALID_ACCOUNT_ID );
	invariant( isValidPropertySelection( getPropertyID() ), INVARIANT_INVALID_PROPERTY_SELECTION );
	invariant( isValidProfileSelection( getProfileID() ), INVARIANT_INVALID_PROFILE_SELECTION );

	if ( getAdsConversionID() ) {
		invariant( isValidAdsConversionID( getAdsConversionID() ), INVARIANT_INVALID_CONVERSION_ID );
	}

	if ( getProfileID() === PROFILE_CREATE ) {
		const profileName = select( CORE_FORMS ).getValue( FORM_SETUP, 'profileName' );
		invariant( isValidProfileName( profileName ), INVARIANT_INVALID_PROFILE_NAME );
	}

	// If the property ID is valid (non-create) the internal ID must be valid as well.
	invariant(
		! isValidPropertyID( getPropertyID() ) || isValidInternalWebPropertyID( getInternalWebPropertyID() ),
		INVARIANT_INVALID_INTERNAL_PROPERTY_ID
	);

	// Do existing tag check last.
	invariant( hasExistingTagPermission() !== false, INVARIANT_INSUFFICIENT_TAG_PERMISSIONS );

	if ( isFeatureEnabled( 'ga4setup' ) ) {
		select( MODULES_ANALYTICS_4 ).__dangerousCanSubmitChanges();
	}
}
<commit_msg>Update analytics settings datastore.<commit_after>/**
 * `modules/analytics` data store: settings.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { MODULES_TAGMANAGER } from '../../tagmanager/datastore/constants';
import { MODULES_ANALYTICS_4 } from '../../analytics-4/datastore/constants';
import { INVARIANT_DOING_SUBMIT_CHANGES, INVARIANT_SETTINGS_NOT_CHANGED } from '../../../googlesitekit/data/create-settings-store';
import { TYPE_MODULES } from '../../../components/data/constants';
import { invalidateCacheGroup } from '../../../components/data/invalidate-cache-group';
import {
	isValidAccountID,
	isValidInternalWebPropertyID,
	isValidPropertySelection,
	isValidProfileSelection,
	isValidPropertyID,
	isValidProfileName,
	isValidAdsConversionID,
} from '../util';
import { STORE_NAME, PROPERTY_CREATE, PROFILE_CREATE, FORM_SETUP } from './constants';
import { createStrictSelect } from '../../../googlesitekit/data/utils';
import { isFeatureEnabled } from '../../../features';
import { isPermissionScopeError } from '../../../util/errors';

// Invariant error messages.
export const INVARIANT_INVALID_ACCOUNT_ID = 'a valid accountID is required to submit changes';
export const INVARIANT_INVALID_PROPERTY_SELECTION = 'a valid propertyID is required to submit changes';
export const INVARIANT_INVALID_PROFILE_SELECTION = 'a valid profileID is required to submit changes';
export const INVARIANT_INVALID_CONVERSION_ID = 'a valid adsConversionID is required to submit changes';
export const INVARIANT_INSUFFICIENT_GTM_TAG_PERMISSIONS = 'cannot submit changes without having permissions for GTM property ID';
export const INVARIANT_INVALID_PROFILE_NAME = 'a valid profile name is required to submit changes';
export const INVARIANT_INVALID_INTERNAL_PROPERTY_ID = 'cannot submit changes with incorrect internal webPropertyID';
export const INVARIANT_INSUFFICIENT_TAG_PERMISSIONS = 'cannot submit without proper permissions';

export async function submitChanges( { select, dispatch } ) {
	let propertyID = select( STORE_NAME ).getPropertyID();
	if ( propertyID === PROPERTY_CREATE ) {
		const accountID = select( STORE_NAME ).getAccountID();
		const { response: property, error } = await dispatch( STORE_NAME ).createProperty( accountID );

		if ( error ) {
			return { error };
		}

		propertyID = property.id;
		dispatch( STORE_NAME ).setPropertyID( property.id );
		dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId ); // eslint-disable-line sitekit/acronym-case
	}

	const profileID = select( STORE_NAME ).getProfileID();
	if ( profileID === PROFILE_CREATE ) {
		const profileName = select( CORE_FORMS ).getValue( FORM_SETUP, 'profileName' );
		const accountID = select( STORE_NAME ).getAccountID();
		const { response: profile, error } = await dispatch( STORE_NAME ).createProfile( accountID, propertyID, { profileName } );

		if ( error ) {
			return { error };
		}

		dispatch( STORE_NAME ).setProfileID( profile.id );
	}

	// This action shouldn't be called if settings haven't changed,
	// but this prevents errors in tests.
	if ( select( STORE_NAME ).haveSettingsChanged() ) {
		const { error } = await dispatch( STORE_NAME ).saveSettings();

		if ( error ) {
			return { error };
		}
	}

	await API.invalidateCache( 'modules', 'analytics' );
	// TODO: Remove once legacy dataAPI is no longer used.
	invalidateCacheGroup( TYPE_MODULES, 'analytics' );

	if ( isFeatureEnabled( 'ga4setup' ) ) {
		if ( select( MODULES_ANALYTICS_4 ).haveSettingsChanged() ) {
			const { error } = await dispatch( MODULES_ANALYTICS_4 ).submitChanges();
			if ( isPermissionScopeError( error ) ) {
				return { error };
			}
		}
	}

	return {};
}

export function validateCanSubmitChanges( select ) {
	const strictSelect = createStrictSelect( select );
	const {
		getAccountID,
		getAdsConversionID,
		getInternalWebPropertyID,
		getProfileID,
		getPropertyID,
		hasExistingTagPermission,
		hasTagPermission,
		haveSettingsChanged,
		isDoingSubmitChanges,
	} = strictSelect( STORE_NAME );

	// Note: these error messages are referenced in test assertions.
	invariant( ! isDoingSubmitChanges(), INVARIANT_DOING_SUBMIT_CHANGES );

	const gtmIsActive = strictSelect( CORE_MODULES ).isModuleActive( 'tagmanager' );
	if ( gtmIsActive ) {
		const gtmAnalyticsPropertyID = strictSelect( MODULES_TAGMANAGER ).getSingleAnalyticsPropertyID();
		invariant(
			! isValidPropertyID( gtmAnalyticsPropertyID ) || hasTagPermission( gtmAnalyticsPropertyID ) !== false,
			INVARIANT_INSUFFICIENT_GTM_TAG_PERMISSIONS
		);
	}

	invariant( haveSettingsChanged(), INVARIANT_SETTINGS_NOT_CHANGED );
	invariant( isValidAccountID( getAccountID() ), INVARIANT_INVALID_ACCOUNT_ID );
	invariant( isValidPropertySelection( getPropertyID() ), INVARIANT_INVALID_PROPERTY_SELECTION );
	invariant( isValidProfileSelection( getProfileID() ), INVARIANT_INVALID_PROFILE_SELECTION );

	if ( getAdsConversionID() ) {
		invariant( isValidAdsConversionID( getAdsConversionID() ), INVARIANT_INVALID_CONVERSION_ID );
	}

	if ( getProfileID() === PROFILE_CREATE ) {
		const profileName = select( CORE_FORMS ).getValue( FORM_SETUP, 'profileName' );
		invariant( isValidProfileName( profileName ), INVARIANT_INVALID_PROFILE_NAME );
	}

	// If the property ID is valid (non-create) the internal ID must be valid as well.
	invariant(
		! isValidPropertyID( getPropertyID() ) || isValidInternalWebPropertyID( getInternalWebPropertyID() ),
		INVARIANT_INVALID_INTERNAL_PROPERTY_ID
	);

	// Do existing tag check last.
	invariant( hasExistingTagPermission() !== false, INVARIANT_INSUFFICIENT_TAG_PERMISSIONS );

	if ( isFeatureEnabled( 'ga4setup' ) ) {
		select( MODULES_ANALYTICS_4 ).__dangerousCanSubmitChanges();
	}
}
<|endoftext|>


<commit_before>/**
 * The gulp-cmd plugin for embedding style text in JavaScript code
 */

'use strict';

var styleNode;
var doc = document;
var toString = ({}).property.toString;
var head = doc.getElementsByTagName('head')[0] || doc.documentElement;

/**
 * Is string
 * @param value
 * @returns {boolean}
 */

function isString(value){
  return {}.toString.call(value) === "[object String]";
}

// Exports
module.exports = function (cssText, imports){
  var element;

  imports = isString(imports) ? imports : '';

  // Don't share styleNode when id is spectied
  if (!styleNode) {
    element = doc.createElement('style');

    // Adds to DOM first to avoid the css hack invalid
    head.appendChild(element);

    // Cache style node
    styleNode = element;
  } else {
    element = styleNode;
  }

  // IE
  if (element.styleSheet !== undefined) {

    // http://support.microsoft.com/kb/262161
    if (doc.getElementsByTagName('style').length > 31) {
      throw new Error('Exceed the maximal count of style tags in IE');
    }

    element.styleSheet.cssText = imports + element.styleSheet.cssText + cssText;
  }
  // W3C
  else {
    element.insertBefore(doc.createTextNode(imports), element.firstChild);
    element.appendChild(doc.createTextNode(cssText));
  }
};
<commit_msg>add source<commit_after>/**
 * The gulp-cmd plugin for embedding style text in JavaScript code
 */

'use strict';

var styleNode;
var doc = document;
var head = doc.getElementsByTagName('head')[0] || doc.documentElement;

/**
 * Is string
 * @param value
 * @returns {boolean}
 */

function isString(value){
  return {}.toString.call(value) === "[object String]";
}

// Exports
module.exports = function (cssText, imports){
  var element;

  imports = isString(imports) ? imports : '';

  // Don't share styleNode when id is spectied
  if (!styleNode) {
    element = doc.createElement('style');

    // Adds to DOM first to avoid the css hack invalid
    head.appendChild(element);

    // Cache style node
    styleNode = element;
  } else {
    element = styleNode;
  }

  // IE
  if (element.styleSheet !== undefined) {

    // http://support.microsoft.com/kb/262161
    if (doc.getElementsByTagName('style').length > 31) {
      throw new Error('Exceed the maximal count of style tags in IE');
    }

    element.styleSheet.cssText = imports + element.styleSheet.cssText + cssText;
  }
  // W3C
  else {
    element.insertBefore(doc.createTextNode(imports), element.firstChild);
    element.appendChild(doc.createTextNode(cssText));
  }
};
<|endoftext|>