<commit_before>/**
 * Copyright 2015-2019 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../core/utilsNew.js";
import "../loading-spinner.js";
import CatalogGridFormatter from "../commons/catalog-grid-formatter.js";
import GridCommons from "../commons/grid-commons.js";
import "../commons/opencb-grid-toolbar.js";

export default class OpencgaVariantSamples extends LitElement {

    constructor() {
        super();

        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            variantId: {
                type: String
            },
            active: {
                type: Boolean
            }
        };
    }

    _init() {
        this._prefix = "ovcs" + UtilsNew.randomString(6);

        this.active = false;
        this.gridId = this._prefix + "SampleTable";
        this.toolbarConfig = {};
    }

    connectedCallback() {
        this.config = this.getDefaultConfig();
        this.gridCommons = new GridCommons(this.gridId, this, this.config);
        super.connectedCallback();
    }

    updated(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            // this.catalogGridFormatter = new CatalogGridFormatter(this.opencgaSession);
        }

        if ((changedProperties.has("variantId") || changedProperties.has("active")) && this.active) {
            this.renderTable();
        }
    }

    genoypeFormatter(value, row, index) {
        if (value && value.data && value.data.length > 0) {
            const gt = value.data[0];
            const color = gt === "0/1" || gt === "0|1" || gt === "1|0" ? "darkorange" : "red";
            return `<span style="color: ${color}">${value.data[0]}</span>`;
        } else {
            return "-";
        }
    }

    variantFormatter(value, row, index) {
        if (value && value.file && value.dataKeys && value.data && value.dataKeys.length === value.data.length) {
            const fileInfo = `Filter: ${value.file.data["FILTER"]}; Qual: ${value.file.data["QUAL"]}`;
            const sampleFormat = [];
            for (let i = 0; i < value.dataKeys.length; i++) {
                sampleFormat.push(`${value.dataKeys[i]}=${value.data[i]}`);
            }
            return fileInfo + "
" + sampleFormat.join("; ");
        } else {
            return "-";
        }
    }

    individualFormatter(value, row, index) {
        if (value) {
            return value;
        } else {
            return "-";
        }
    }

    sexFormatter(value, row, index) {
        // debugger
        if (value) {
            return `${value.sex} (${value.karyotypicSex})`;
        } else {
            return "-";
        }
    }

    disorderFormatter(value, row, index) {
        if (value && value.length) {
            return value.map(disorder => `<p>${disorder.id}</p>`).join("");
        } else {
            return "-";
        }
    }

    renderTable() {
        if (!this.opencgaSession) {
            return;
        }
        this.table = $("#" + this.gridId);
        this.table.bootstrapTable("destroy");
        this.table.bootstrapTable({
            pagination: true,
            sidePagination: "server",
            columns: this.getColumns(),
            formatShowingRows: this.gridCommons.formatShowingRows,
            formatLoadingMessage: () => "
<loading-spinner></loading-spinner>
",
            ajax: async params => {
                const tableOptions = this.table.bootstrapTable("getOptions");
                // let limit = tableOptions.pageSize || 10;
                // let skip = tableOptions.pageNumber ? tableOptions.pageNumber * limit - limit : 0;
                const query = {
                    variant: this.variantId,
                    study: this.opencgaSession.study.fqn,
                    limit: params.data.limit || tableOptions.pageSize,
                    skip: params.data.offset || 0,
                    count: !tableOptions.pageNumber || tableOptions.pageNumber === 1,
                    genotype: "0/1,1/1,0/2,1/2,2/2"
                };
                try {
                    const data = await this.fetchData(query);
                    params.success(data);
                } catch (e) {
                    console.log(e);
                    params.error(e);
                }
            },
            responseHandler: response => ({
                total: response.total,
                rows: response.rows
            }),
            onClickRow: (row, selectedElement, field) => this.gridCommons.onClickRow(row.id, row, selectedElement),
            onLoadSuccess: data => {
                this.gridCommons.onLoadSuccess(data, 2);
            },
            onLoadError: (e, restResponse) => this.gridCommons.onLoadError(e, restResponse)
        });
    }

    async fetchData(query, batchSize) {
        try {

            const variantResponse = await this.opencgaSession.opencgaClient.variants().querySample(query);
            const result = variantResponse.getResult(0);
            this.numUserTotalSamples = 0;

            // Get the total number of samples
            // TODO count only the genotypes filtered
            // _this.numSamples = result.studies[0].samples.length;
            this.numSamples = 0;
            const stats = result.studies[0].stats;
            for (const stat of stats) {
                if (stat.cohortId === "ALL") {
                    for (const gt of Object.keys(stat.genotypeCount)) {
                        if (gt !== "0/0" && gt !== "./." && gt !== "./1" && gt !== "./0") {
                            this.numSamples += stat.genotypeCount[gt];
                        }
                    }
                    // this.numSamples = stat.genotypeCount["0/1"] + stat.genotypeCount["1/1"]
                    // this.numSamples = stat.sampleCount - stat.genotypeCount["0/0"]
                    break;
                }
            }

            // Prepare sample variant data for next query
            const variantSamples = result.studies[0].samples;

            if (variantSamples && variantSamples.length > 0) {
                const variantSampleInfo = {};
                const sampleIds = [];
                const samples = [];
                for (const variantSample of variantSamples) {
                    sampleIds.push(variantSample.sampleId);
                    variantSampleInfo[variantSample.sampleId] = {
                        id: variantSample.sampleId,
                        file: result.studies[0].files[variantSample.fileIndex],
                        dataKeys: result.studies[0].sampleDataKeys,
                        data: variantSample.data
                    };
                }

                const batch = batchSize ?? sampleIds.length;
                for (let i = 0; i < sampleIds.length;) {
                    let sampleChunk = [];
                    const sampleIdChunk = sampleIds.slice(i, i + batch);
                    i = i + batch;

                    const sampleResponse = await this.opencgaSession.opencgaClient.samples().info(sampleIdChunk.join(","), {study: this.opencgaSession.study.fqn, includeIndividual: true});

                    sampleChunk = sampleResponse.getResults();
                    for (const sample of sampleChunk) {
                        sample.attributes.OPENCGA_VARIANT = variantSampleInfo[sample.id];
                    }

                    // Fetch clinical analysis to display the Case ID
                    const caseResponse = await this.opencgaSession.opencgaClient.clinical().search(
                        {
                            individual: sampleChunk
                                .map(sample => sample.individualId)
                                .filter(id => id && id.length > 0)
                                .join(","),
                            limit: batch,
                            study: this.opencgaSession.study.fqn,
                            include: "id,proband.id,family.members"
                        });
                    sampleResponse.getResults().forEach(sample => {
                        for (const clinicalAnalysis of caseResponse.getResults()) {
                            if (clinicalAnalysis?.proband?.id === sample.individualId || clinicalAnalysis?.family?.members.find(member => member.id === sample.individualId)) {
                                if (sample?.attributes?.OPENCGA_CLINICAL_ANALYSIS) {
                                    sample.attributes.OPENCGA_CLINICAL_ANALYSIS.push(clinicalAnalysis);
                                } else {
                                    sample.attributes.OPENCGA_CLINICAL_ANALYSIS = [clinicalAnalysis];
                                }
                            }
                        }
                    });
                    samples.push(...sampleChunk);
                }
                // Samples the user can see according to permissions
                this.numUserTotalSamples = variantResponse.getResponse(0).attributes.numTotalSamples;
                this.approximateCount = variantResponse.getResponse(0).attributes.approximateCount;
                this.requestUpdate();
                return {
                    total: this.numUserTotalSamples,
                    rows: samples
                };
            } else {
                this.requestUpdate();
                await Promise.reject("No samples found");
            }
        } catch (e) {
            await Promise.reject(e);
        }
    }

    getColumns() {
        return [
            [
                {
                    title: "Sample ID",
                    field: "id",
                    rowspan: 2,
                    colspan: 1,
                    // formatter: this.variantFormatter,
                    halign: "center"
                },
                {
                    title: "Genotype",
                    field: "attributes.OPENCGA_VARIANT",
                    rowspan: 2,
                    colspan: 1,
                    formatter: this.genoypeFormatter,
                    halign: "center"
                },
                {
                    title: "Variant Data",
                    field: "attributes.OPENCGA_VARIANT",
                    rowspan: 2,
                    colspan: 1,
                    formatter: this.variantFormatter,
                    halign: "center"
                },
                {
                    title: "Individual",
                    rowspan: 1,
                    colspan: 4,
                    // formatter: this.variantFormatter,
                    halign: "center"
                },
                {
                    title: "Case ID",
                    field: "attributes.OPENCGA_CLINICAL_ANALYSIS",
                    rowspan: 2,
                    colspan: 1,
                    formatter: (value, row) => CatalogGridFormatter.caseFormatter(value, row, row.individualId, this.opencgaSession),
                    halign: "center"
                }
            ],
            [
                {
                    title: "ID",
                    field: "individualId",
                    colspan: 1,
                    rowspan: 1,
                    formatter: this.individualFormatter,
                    halign: "center"
                },
                {
                    title: "Sex",
                    field: "attributes.OPENCGA_INDIVIDUAL",
                    colspan: 1,
                    rowspan: 1,
                    formatter: this.sexFormatter,
                    halign: "center"
                },
                {
                    title: "Disorders",
                    field: "attributes.OPENCGA_INDIVIDUAL.disorders",
                    colspan: 1,
                    rowspan: 1,
                    formatter: disorders => {
                        const result = disorders?.map(disorder => CatalogGridFormatter.disorderFormatter(disorder)).join("
");
                        return result ? result : "-";
                    },
                    halign: "center"
                },
                {
                    title: "Phenotypes",
                    field: "attributes.OPENCGA_INDIVIDUAL.phenotypes",
                    colspan: 1,
                    rowspan: 1,
                    formatter: CatalogGridFormatter.phenotypesFormatter,
                    halign: "center"
                }
            ]
        ];
    }

    async onDownload(e) {
        try {
            this.toolbarConfig = {...this.toolbarConfig, downloading: true};
            this.requestUpdate();
            await this.updateComplete;
            // batch size for sample query
            const BATCH_SIZE = 100;
            const query = {
                variant: this.variantId,
                study: this.opencgaSession.study.fqn,
                limit: 1000,
                genotype: "0/1,1/1,0/2,1/2,2/2"
            };

            const samples = await this.fetchData(query, BATCH_SIZE);
            const header = ["Sample ID", "Genotype", "Variant Data", "Individual ID", "Individual Sex", "Phenotypes", "Disorders", "Case ID"];
            if (samples?.rows?.length) {
                const rows = samples.rows.map(sample => {
                    return [
                        sample.id,
                        sample?.attributes?.OPENCGA_VARIANT?.data[0] ?? "-",
                        this.variantFormatter(sample?.attributes?.OPENCGA_VARIANT),
                        sample.individualId,
                        this.sexFormatter(sample?.attributes?.OPENCGA_INDIVIDUAL),
                        sample?.attributes?.OPENCGA_INDIVIDUAL?.phenotypes?.map(p => p.id) ?? "-",
                        sample?.attributes?.OPENCGA_INDIVIDUAL?.disorders?.map(d => d.id) ?? "-",
                        sample?.attributes?.OPENCGA_CLINICAL_ANALYSIS?.id ?? "-"
                    ].join("\t");
                });
                if (e.detail.option.toLowerCase() === "tab") {
                    const dataString = [
                        header.join("\t"),
                        rows.join("\n")];
                    UtilsNew.downloadData(dataString, "variant_samples_" + this.opencgaSession.study.id + ".txt", "text/plain");
                } else {
                    UtilsNew.downloadData(JSON.stringify(samples, null, "\t"), this.opencgaSession.study.id + ".json", "application/json");
                }
            }
        } catch (e) {
            UtilsNew.notifyError(e);
        }
        this.toolbarConfig = {...this.toolbarConfig, downloading: false};
        this.requestUpdate();
    }

    getDefaultConfig() {
        return {
            pagination: true,
            pageSize: 10,
            pageList: [10, 25, 50],
            showExport: false
        };
    }

    render() {
        return html`

                ${this.numSamples !== this.numUserTotalSamples ?
                    html`

                            Number of samples found is ${this.numSamples}${this.approximateCount === true ? html` (<i>please note this is an estimated number</i>)` : ""}, and
                            your user account has permission to view ${this.numUserTotalSamples} samples.
                            Note that you might not have permission to view all samples for any variant.

` :
                    null
                }
                <opencb-grid-toolbar .config="${this.toolbarConfig}"
                                     @columnChange="${this.onColumnChange}"
                                     @download="${this.onDownload}"
                                     @sharelink="${this.onShare}">
                </opencb-grid-toolbar>

            `;
    }

}

customElements.define("opencga-variant-samples", OpencgaVariantSamples);
<commit_msg>wc: opencga-variant-samples.js toolbarConfig fixed<commit_after>/**
 * Copyright 2015-2019 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../core/utilsNew.js";
import "../loading-spinner.js";
import CatalogGridFormatter from "../commons/catalog-grid-formatter.js";
import GridCommons from "../commons/grid-commons.js";
import "../commons/opencb-grid-toolbar.js";

export default class OpencgaVariantSamples extends LitElement {

    constructor() {
        super();

        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            variantId: {
                type: String
            },
            active: {
                type: Boolean
            }
        };
    }

    _init() {
        this._prefix = "ovcs" + UtilsNew.randomString(6);

        this.active = false;
        this.gridId = this._prefix + "SampleTable";
        this.toolbarConfig = {
            showColumns: true,
            showExport: false,
            showDownload: true
        };
    }

    connectedCallback() {
        this.config = this.getDefaultConfig();
        this.gridCommons = new GridCommons(this.gridId, this, this.config);
        super.connectedCallback();
    }

    updated(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            // this.catalogGridFormatter = new CatalogGridFormatter(this.opencgaSession);
        }

        if ((changedProperties.has("variantId") || changedProperties.has("active")) && this.active) {
            this.renderTable();
        }
    }

    genoypeFormatter(value, row, index) {
        if (value && value.data && value.data.length > 0) {
            const gt = value.data[0];
            const color = gt === "0/1" || gt === "0|1" || gt === "1|0" ? "darkorange" : "red";
            return `<span style="color: ${color}">${value.data[0]}</span>`;
        } else {
            return "-";
        }
    }

    variantFormatter(value, row, index) {
        if (value && value.file && value.dataKeys && value.data && value.dataKeys.length === value.data.length) {
            const fileInfo = `Filter: ${value.file.data["FILTER"]}; Qual: ${value.file.data["QUAL"]}`;
            const sampleFormat = [];
            for (let i = 0; i < value.dataKeys.length; i++) {
                sampleFormat.push(`${value.dataKeys[i]}=${value.data[i]}`);
            }
            return fileInfo + "
" + sampleFormat.join("; ");
        } else {
            return "-";
        }
    }

    individualFormatter(value, row, index) {
        if (value) {
            return value;
        } else {
            return "-";
        }
    }

    sexFormatter(value, row, index) {
        // debugger
        if (value) {
            return `${value.sex} (${value.karyotypicSex})`;
        } else {
            return "-";
        }
    }

    disorderFormatter(value, row, index) {
        if (value && value.length) {
            return value.map(disorder => `<p>${disorder.id}</p>`).join("");
        } else {
            return "-";
        }
    }

    renderTable() {
        if (!this.opencgaSession) {
            return;
        }
        this.table = $("#" + this.gridId);
        this.table.bootstrapTable("destroy");
        this.table.bootstrapTable({
            pagination: true,
            sidePagination: "server",
            columns: this.getColumns(),
            formatShowingRows: this.gridCommons.formatShowingRows,
            formatLoadingMessage: () => "
<loading-spinner></loading-spinner>
",
            ajax: async params => {
                const tableOptions = this.table.bootstrapTable("getOptions");
                // let limit = tableOptions.pageSize || 10;
                // let skip = tableOptions.pageNumber ? tableOptions.pageNumber * limit - limit : 0;
                const query = {
                    variant: this.variantId,
                    study: this.opencgaSession.study.fqn,
                    limit: params.data.limit || tableOptions.pageSize,
                    skip: params.data.offset || 0,
                    count: !tableOptions.pageNumber || tableOptions.pageNumber === 1,
                    genotype: "0/1,1/1,0/2,1/2,2/2"
                };
                try {
                    const data = await this.fetchData(query);
                    params.success(data);
                } catch (e) {
                    console.log(e);
                    params.error(e);
                }
            },
            responseHandler: response => ({
                total: response.total,
                rows: response.rows
            }),
            onClickRow: (row, selectedElement, field) => this.gridCommons.onClickRow(row.id, row, selectedElement),
            onLoadSuccess: data => {
                this.gridCommons.onLoadSuccess(data, 2);
            },
            onLoadError: (e, restResponse) => this.gridCommons.onLoadError(e, restResponse)
        });
    }

    async fetchData(query, batchSize) {
        try {

            const variantResponse = await this.opencgaSession.opencgaClient.variants().querySample(query);
            const result = variantResponse.getResult(0);
            this.numUserTotalSamples = 0;

            // Get the total number of samples
            // TODO count only the genotypes filtered
            // _this.numSamples = result.studies[0].samples.length;
            this.numSamples = 0;
            const stats = result.studies[0].stats;
            for (const stat of stats) {
                if (stat.cohortId === "ALL") {
                    for (const gt of Object.keys(stat.genotypeCount)) {
                        if (gt !== "0/0" && gt !== "./." && gt !== "./1" && gt !== "./0") {
                            this.numSamples += stat.genotypeCount[gt];
                        }
                    }
                    // this.numSamples = stat.genotypeCount["0/1"] + stat.genotypeCount["1/1"]
                    // this.numSamples = stat.sampleCount - stat.genotypeCount["0/0"]
                    break;
                }
            }

            // Prepare sample variant data for next query
            const variantSamples = result.studies[0].samples;

            if (variantSamples && variantSamples.length > 0) {
                const variantSampleInfo = {};
                const sampleIds = [];
                const samples = [];
                for (const variantSample of variantSamples) {
                    sampleIds.push(variantSample.sampleId);
                    variantSampleInfo[variantSample.sampleId] = {
                        id: variantSample.sampleId,
                        file: result.studies[0].files[variantSample.fileIndex],
                        dataKeys: result.studies[0].sampleDataKeys,
                        data: variantSample.data
                    };
                }

                const batch = batchSize ?? sampleIds.length;
                for (let i = 0; i < sampleIds.length;) {
                    let sampleChunk = [];
                    const sampleIdChunk = sampleIds.slice(i, i + batch);
                    i = i + batch;

                    const sampleResponse = await this.opencgaSession.opencgaClient.samples().info(sampleIdChunk.join(","), {study: this.opencgaSession.study.fqn, includeIndividual: true});

                    sampleChunk = sampleResponse.getResults();
                    for (const sample of sampleChunk) {
                        sample.attributes.OPENCGA_VARIANT = variantSampleInfo[sample.id];
                    }

                    // Fetch clinical analysis to display the Case ID
                    const caseResponse = await this.opencgaSession.opencgaClient.clinical().search(
                        {
                            individual: sampleChunk
                                .map(sample => sample.individualId)
                                .filter(id => id && id.length > 0)
                                .join(","),
                            limit: batch,
                            study: this.opencgaSession.study.fqn,
                            include: "id,proband.id,family.members"
                        });
                    sampleResponse.getResults().forEach(sample => {
                        for (const clinicalAnalysis of caseResponse.getResults()) {
                            if (clinicalAnalysis?.proband?.id === sample.individualId || clinicalAnalysis?.family?.members.find(member => member.id === sample.individualId)) {
                                if (sample?.attributes?.OPENCGA_CLINICAL_ANALYSIS) {
                                    sample.attributes.OPENCGA_CLINICAL_ANALYSIS.push(clinicalAnalysis);
                                } else {
                                    sample.attributes.OPENCGA_CLINICAL_ANALYSIS = [clinicalAnalysis];
                                }
                            }
                        }
                    });
                    samples.push(...sampleChunk);
                }
                // Samples the user can see according to permissions
                this.numUserTotalSamples = variantResponse.getResponse(0).attributes.numTotalSamples;
                this.approximateCount = variantResponse.getResponse(0).attributes.approximateCount;
                this.requestUpdate();
                return {
                    total: this.numUserTotalSamples,
                    rows: samples
                };
            } else {
                this.requestUpdate();
                await Promise.reject(new Error("No samples found"));
            }
        } catch (e) {
            await Promise.reject(e);
        }
    }

    getColumns() {
        return [
            [
                {
                    title: "Sample ID",
                    field: "id",
                    rowspan: 2,
                    colspan: 1,
                    // formatter: this.variantFormatter,
                    halign: "center"
                },
                {
                    title: "Genotype",
                    field: "attributes.OPENCGA_VARIANT",
                    rowspan: 2,
                    colspan: 1,
                    formatter: this.genoypeFormatter,
                    halign: "center"
                },
                {
                    title: "Variant Data",
                    field: "attributes.OPENCGA_VARIANT",
                    rowspan: 2,
                    colspan: 1,
                    formatter: this.variantFormatter,
                    halign: "center"
                },
                {
                    title: "Individual",
                    rowspan: 1,
                    colspan: 4,
                    // formatter: this.variantFormatter,
                    halign: "center"
                },
                {
                    title: "Case ID",
                    field: "attributes.OPENCGA_CLINICAL_ANALYSIS",
                    rowspan: 2,
                    colspan: 1,
                    formatter: (value, row) => CatalogGridFormatter.caseFormatter(value, row, row.individualId, this.opencgaSession),
                    halign: "center"
                }
            ],
            [
                {
                    title: "ID",
                    field: "individualId",
                    colspan: 1,
                    rowspan: 1,
                    formatter: this.individualFormatter,
                    halign: "center"
                },
                {
                    title: "Sex",
                    field: "attributes.OPENCGA_INDIVIDUAL",
                    colspan: 1,
                    rowspan: 1,
                    formatter: this.sexFormatter,
                    halign: "center"
                },
                {
                    title: "Disorders",
                    field: "attributes.OPENCGA_INDIVIDUAL.disorders",
                    colspan: 1,
                    rowspan: 1,
                    formatter: disorders => {
                        const result = disorders?.map(disorder => CatalogGridFormatter.disorderFormatter(disorder)).join("
");
                        return result ? result : "-";
                    },
                    halign: "center"
                },
                {
                    title: "Phenotypes",
                    field: "attributes.OPENCGA_INDIVIDUAL.phenotypes",
                    colspan: 1,
                    rowspan: 1,
                    formatter: CatalogGridFormatter.phenotypesFormatter,
                    halign: "center"
                }
            ]
        ];
    }

    async onDownload(e) {
        try {
            this.toolbarConfig = {...this.toolbarConfig, downloading: true};
            this.requestUpdate();
            await this.updateComplete;
            // batch size for sample query
            const BATCH_SIZE = 100;
            const query = {
                variant: this.variantId,
                study: this.opencgaSession.study.fqn,
                limit: 1000,
                genotype: "0/1,1/1,0/2,1/2,2/2"
            };

            const samples = await this.fetchData(query, BATCH_SIZE);
            const header = ["Sample ID", "Genotype", "Variant Data", "Individual ID", "Individual Sex", "Phenotypes", "Disorders", "Case ID"];
            if (samples?.rows?.length) {
                const rows = samples.rows.map(sample => {
                    return [
                        sample.id,
                        sample?.attributes?.OPENCGA_VARIANT?.data[0] ?? "-",
                        this.variantFormatter(sample?.attributes?.OPENCGA_VARIANT),
                        sample.individualId,
                        this.sexFormatter(sample?.attributes?.OPENCGA_INDIVIDUAL),
                        sample?.attributes?.OPENCGA_INDIVIDUAL?.phenotypes?.map(p => p.id) ?? "-",
                        sample?.attributes?.OPENCGA_INDIVIDUAL?.disorders?.map(d => d.id) ?? "-",
                        sample?.attributes?.OPENCGA_CLINICAL_ANALYSIS?.id ?? "-"
                    ].join("\t");
                });
                if (e.detail.option.toLowerCase() === "tab") {
                    const dataString = [
                        header.join("\t"),
                        rows.join("\n")];
                    UtilsNew.downloadData(dataString, "variant_samples_" + this.opencgaSession.study.id + ".txt", "text/plain");
                } else {
                    UtilsNew.downloadData(JSON.stringify(samples, null, "\t"), this.opencgaSession.study.id + ".json", "application/json");
                }
            }
        } catch (e) {
            UtilsNew.notifyError(e);
        }
        this.toolbarConfig = {...this.toolbarConfig, downloading: false};
        this.requestUpdate();
    }

    getDefaultConfig() {
        return {
            pagination: true,
            pageSize: 10,
            pageList: [10, 25, 50],
            showExport: false
        };
    }

    render() {
        return html`

                ${this.numSamples !== this.numUserTotalSamples ?
                    html`

                            Number of samples found is ${this.numSamples}${this.approximateCount === true ? html` (<i>please note this is an estimated number</i>)` : ""}, and
                            your user account has permission to view ${this.numUserTotalSamples} samples.
                            Note that you might not have permission to view all samples for any variant.

` :
                    null
                }
                <opencb-grid-toolbar .config="${this.toolbarConfig}"
                                     @columnChange="${this.onColumnChange}"
                                     @download="${this.onDownload}"
                                     @sharelink="${this.onShare}">
                </opencb-grid-toolbar>

            `;
    }

}

customElements.define("opencga-variant-samples", OpencgaVariantSamples);
<|endoftext|>


<commit_before>angular.module('app.tasks', ['ngMaterial'])
.config(function($mdThemingProvider) {
  $mdThemingProvider.theme('default')
    .primaryPalette('green')
    .accentPalette('indigo')
    .warnPalette('deep-orange')
    .backgroundPalette('green', {
      default: '50'
    });
})
.controller('TasksController', function($scope, Tasks, Users, Auth) {
	$scope.showAddTaskButton = true;
  $scope.data = {};

  $scope.data.tasks = [];
  $scope.getTasks = function() {
		// $scope.data.tasks = Tasks.getAll();
      Tasks.getAll()
		  .then(function(tasks) {
		  	$scope.data.tasks = tasks;
		  })
	    .catch(function(err) {
        console.log(err);
	    });
	};
	$scope.getTasks();

  $scope.getUsers = function() {
    Users.getAll()
    .then(function(users) {
      $scope.data.users = users;
    })
    .catch(function(err) {
      console.log(err);
    });
  };
  $scope.getUsers();

  $scope.updateTask = function(task) {
    // check for a blank form
    if (!task) {
      return;
    }

    if (task.assigned === '') {
      task.users = [];
    } else if (!task.users) {
      if (task.assigned) {
        task.users = [task.assigned];
      } else {
        // otherwise its unassigned
        task.users = [];
      }
    }

    var found = false;

    // if the task already exists, update it
    for (var i = 0; i < $scope.data.tasks.length; i++) {
      var currentTask = $scope.data.tasks[i];
      if ( task._id && task._id === currentTask._id ) {
        Tasks.updateTask(task)
          .then(function(resp) {
            $scope.getTasks();
          })
          .catch(function(err) {
            console.log(err);
          });

        found = true;
        break;
     }
    }

    // if the task does not already exists, create a new task
    if ( !found ) {
      Tasks.createTask(task)
        .then(function(resp) {
          $scope.getTasks();
        })
        .catch(function(err) {
          console.log(err);
        });
    }
  };

  $scope.deleteTask = function(task) {
    Tasks.deleteTask(task)
      .then(function() {
        $scope.getTasks();
      })
      .catch(function(err) {
        console.log(err);
      });
  };

	$scope.loadTaskDetails = function(task) {
    // hide the 'Add Task' button
		$scope.showAddTaskButton=false;
    // show the task form
    $scope.showTaskForm = true;

    // load the task details into the form
    $scope.task = {};
    $scope.task._id = task._id;
		$scope.task.name = task.name;
		$scope.task.users = task.users.length > 0 ? task.users[0].username : null;
		$scope.task.description = task.description;
	};

	$scope.resetTaskDetails = function() {
    // reset the task form if it was dirty
    if ( $scope.task ) {
      $scope.task.name = null;
      $scope.task.assigned = null;
      $scope.task.description = null;
      $scope.taskForm.$setUntouched();
    }

    // hide the task form
    $scope.showTaskForm = false;
    // show the 'Add Task' button
    $scope.showAddTaskButton = true;
	};

  $scope.stagingFilter = function(task) {
    return !task.isCompleted && task.users.length === 0 ? true : false;
  };

  $scope.assignedFilter = function(task) {
  	return !task.isCompleted && task.users.length > 0 ? true : false;
  };

  $scope.completedFilter = function(task) {
  	return task.isCompleted ? true : false;
  };

  $scope.signout = Auth.signout;
});
<commit_msg>refactoring<commit_after>angular.module('app.tasks', ['ngMaterial'])
.config(function($mdThemingProvider) {
  $mdThemingProvider.theme('default')
    .primaryPalette('green')
    .accentPalette('indigo')
    .warnPalette('deep-orange')
    .backgroundPalette('green', {
      default: '50'
    });
})
.controller('TasksController', function($scope, Tasks, Users, Auth) {
	$scope.showAddTaskButton = true;
  $scope.data = {};

  $scope.data.tasks = [];
  $scope.getTasks = function() {
      Tasks.getAll()
		  .then(function(tasks) {
		  	$scope.data.tasks = tasks;
		  })
	    .catch(function(err) {
        console.log(err);
	    });
	};
	$scope.getTasks();

  $scope.getUsers = function() {
    Users.getAll()
    .then(function(users) {
      $scope.data.users = users;
    })
    .catch(function(err) {
      console.log(err);
    });
  };
  $scope.getUsers();

  $scope.updateTask = function(task) {
    // check for a blank form
    if (!task) {
      return;
    }

    if (task.assigned === '') {
      task.users = [];
    } else if (!task.users) {
      if (task.assigned) {
        task.users = [task.assigned];
      } else {
        // otherwise its unassigned
        task.users = [];
      }
    }

    var found = false;

    // if the task already exists, update it
    for (var i = 0; i < $scope.data.tasks.length; i++) {
      var currentTask = $scope.data.tasks[i];
      if ( task._id && task._id === currentTask._id ) {
        Tasks.updateTask(task)
          .then(function(resp) {
            $scope.getTasks();
          })
          .catch(function(err) {
            console.log(err);
          });

        found = true;
        break;
     }
    }

    // if the task does not already exists, create a new task
    if ( !found ) {
      Tasks.createTask(task)
        .then(function(resp) {
          $scope.getTasks();
        })
        .catch(function(err) {
          console.log(err);
        });
    }
  };

  $scope.deleteTask = function(task) {
    Tasks.deleteTask(task)
      .then(function() {
        $scope.getTasks();
      })
      .catch(function(err) {
        console.log(err);
      });
  };

	$scope.loadTaskDetails = function(task) {
    // hide the 'Add Task' button
		$scope.showAddTaskButton=false;
    // show the task form
    $scope.showTaskForm = true;

    // load the task details into the form
    $scope.task = {};
    $scope.task._id = task._id;
		$scope.task.name = task.name;
		$scope.task.users = task.users.length > 0 ? task.users[0].username : null;
		$scope.task.description = task.description;
	};

	$scope.resetTaskDetails = function() {
    // reset the task form if it was dirty
    if ( $scope.task ) {
      $scope.task.name = null;
      $scope.task.assigned = null;
      $scope.task.description = null;
      $scope.taskForm.$setUntouched();
    }

    // hide the task form
    $scope.showTaskForm = false;
    // show the 'Add Task' button
    $scope.showAddTaskButton = true;
	};

  $scope.stagingFilter = function(task) {
    return !task.isCompleted && task.users.length === 0 ? true : false;
  };

  $scope.assignedFilter = function(task) {
  	return !task.isCompleted && task.users.length > 0 ? true : false;
  };

  $scope.completedFilter = function(task) {
  	return task.isCompleted ? true : false;
  };

  $scope.signout = Auth.signout;
});
<|endoftext|>


<commit_before>/*
 * where-test.js: Tests for the Where reactor stream.
 *
 * (C) 2012, Nodejitsu Inc.
 *
 */

var assert = require('assert'),
    vows = require('vows'),
    godot = require('../../lib/godot'),
    macros = require('../macros').reactor;

vows.describe('godot/reactor/where').addBatch({
  "Godot where": {
    "service, */health/heartbeat": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where('service', '*/health/heartbeat'),
      'health',
      2
    ),
    "service, /.*/heartbeat$/": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where('service', /.*\/heartbeat$/),
      'health',
      2
    ),
    "{ service: '*/health/heartbeat' }": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where({ service: '*/health/heartbeat' }),
      'health',
      2
    )
  }
}).export(module);<commit_msg>[test] add test case for the where reactor negate case<commit_after>/*
 * where-test.js: Tests for the Where reactor stream.
 *
 * (C) 2012, Nodejitsu Inc.
 *
 */

var assert = require('assert'),
    vows = require('vows'),
    godot = require('../../lib/godot'),
    macros = require('../macros').reactor;

vows.describe('godot/reactor/where').addBatch({
  "Godot where": {
    "service, */health/heartbeat": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where('service', '*/health/heartbeat'),
      'health',
      2
    ),
    "service, /.*/heartbeat$/": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where('service', /.*\/heartbeat$/),
      'health',
      2
    ),
    "{ service: '*/health/heartbeat' }": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where({ service: '*/health/heartbeat' }),
      'health',
      2
    ),
    "service, '*/health/heartbeat', {negate: true}": macros.shouldEmitDataSync(
      godot
        .reactor()
        .where('service', '*/health/heartbeat', {negate: true}),
      'health',
      1
    )
  }
}).export(module);
<|endoftext|>


<commit_before>/*global expect, describe, it, beforeEach, spyOn, afterEach, runs, waitsFor */
/*global Backbone, jQuery, zeit*/
(function ($) {
    "use strict";

    var editor_container,
        preview_container,
        setUp = function() {
            var editor, preview, variant,
                flag = false;

            // Create temporary DOM
            editor_container = $(
                '
');
            preview_container = $('
');
            $('body').append(editor_container);
            $('body').append(preview_container);

            // Mock AJAX calls to return hard coded response
            spyOn($, 'ajax').andCallFake(function (options) {
                var d = $.Deferred(),
                    response = {
                        focus_x: 0.5,
                        focus_y: 0.5,
                        zoom: 0.3,
                        brightness: 0.5,
                        contrast: 0.5,
                        saturation: 0.5,
                        sharpness: 0.5,
                        is_default: true,
                        url: '/fanstatic/zeit.content.image.test/master_image.jpg'
                    };
                d.resolve(response);
                options.success(response);
                return d.promise();
            });

            // Setup Editor and wait that it was rendered
            preview = new zeit.content.image.browser.VariantList();
            variant = new zeit.content.image.Variant({'id': 'square'});
            zeit.content.image.VARIANTS.add(variant);
            zeit.content.image.VARIANTS.trigger('reset');

            editor = new zeit.content.image.browser.VariantEditor();

            runs(function() {
                editor.on('render', function() {
                    flag = true;
                });
                editor.prepare();
            });

            waitsFor(function () {
                return flag;
            }, "VariantEditor did not render", 500);

            return {editor: editor, preview: preview, variant: variant};
        };

    describe("VariantEditor", function () {
        beforeEach(function() {
            var self = this,
                result = setUp();
            self.editor = result.editor;
            self.preview = result.preview;
            self.variant = result.variant;
        });

        afterEach(function () {
            editor_container.remove();
            preview_container.remove();
        });

        it("should display circle relative to given focus point", function () {
            var self = this;
            runs(function() {
                expect(self.editor.focuspoint.position()).toEqual({left: 110, top: 62});
            });
        });

        it("should save focus point after drag", function () {
            var self = this;
            runs(function() {
                var spy = spyOn(Backbone.Model.prototype, "save").andCallThrough();
                self.editor.focuspoint.css('left', '55px');
                self.editor.focuspoint.css('top', '31px');
                self.editor.focuspoint.trigger('dragstop');
                expect(spy).toHaveBeenCalledWith(
                    {"focus_x": 0.25, "focus_y": 0.25, "zoom": 0.3}
                );
            });
        });

        it("should display stored zoom value on load", function() {
            var self = this;
            runs(function() {
                // Since we want to inverse the default zoom-bar behaviour of
                // jqueryui, we also must expect the inverse value, i.e. 100-X
                expect(self.editor.zoom_bar.slider('value')).toEqual(100 - 30);
            });
        });

        it("should store zoom value on change", function() {
            var self = this;
            runs(function() {
                var spy = spyOn(Backbone.Model.prototype, "save").andCallThrough();
                // Since we want to inverse the default zoom-bar behaviour of
                // jqueryui, we also must set the inverse value, i.e. 100-X
                self.editor.zoom_bar.slider('value', 100 - 60);
                self.editor.zoom_bar.trigger('slidestop');
                expect(spy).toHaveBeenCalledWith(
                    {"focus_x": 0.5, "focus_y": 0.5, "zoom": 0.6}
                );
            });
        });

        it("should have 4 different image enhancements", function() {
            var self = this;
            runs(function() {
                expect(self.editor.image_enhancements.length).toBe(4);
            });
        });

        it("should display value of enhancements on load", function() {
            var self = this;
            runs(function() {
                $.each(self.editor.image_enhancements, function(i, name) {
                    var input = self.editor[name + '_input'],
                        bar = self.editor[name + '_bar'];

                    expect(input.val()).toEqual('-100');
                    expect(bar.slider("value")).toEqual(-100);
                });
            });
        });

        it("should store enhancement when changing slider", function() {
            var self = this;
            runs(function() {
                var spy = spyOn(Backbone.Model.prototype, "set").andCallThrough();
                $.each(self.editor.image_enhancements, function(i, name) {
                    var bar = self.editor[name + '_bar'];
                    bar.slider("value", 100);
                    bar.trigger("slidestop");
                    expect(spy).toHaveBeenCalledWith(name, 1.5);
                });
            });
        });

        it("should store enhancement when changing input field", function() {
            var self = this;
            runs(function() {
                var spy = spyOn(Backbone.Model.prototype, "set").andCallThrough();
                $.each(self.editor.image_enhancements, function(i, name) {
                    var input_field = self.editor[name + '_input'];
                    input_field.val(100);
                    input_field.trigger("blur");
                    expect(spy).toHaveBeenCalledWith(name, 1.5);
                });
            });
        });

        it("should switch back to default variant on save", function() {
            var self = this;
            runs(function() {
                self.editor.switch_focus(
                    self.variant, self.preview.model_views[self.variant.id]);
                expect(self.editor.current_model.id).toBe('square');
                $('input[value=Speichern]').click();
                expect(self.editor.current_model.id).toBe('default');
            });
        });

        it("should switch back to default variant on delete", function() {
            var self = this;
            runs(function() {
                self.editor.switch_focus(
                    self.variant, self.preview.model_views[self.variant.id]);
                expect(self.editor.current_model.id).toBe('square');
                $('input[value=Verwerfen]').click();
                expect(self.editor.current_model.id).toBe('default');
            });
        });

        it("should call destroy on every variant on reset", function() {
            var self = this,
                spy = spyOn(Backbone.Model.prototype, "destroy").andCallThrough();
            $("input[value='Alle Formate zurücksetzen']").click();
            expect(spy.calls.length).toEqual(1);
        });

        it("should update images of all variants on reset", function() {
            var self = this,
                image = self.preview.$('img.preview'),
                image_url = image.attr('src');
            $("input[value='Alle Formate zurücksetzen']").click();
            expect(image.attr('src')).not.toBe(image_url);
        });

        it("should update master image on save", function() {
            var self = this,
                image = self.editor.$('img.editor'),
                image_url = image.attr('src');
            self.editor.save();
            expect(image.attr('src')).not.toBe(image_url);
        });
    });
}(jQuery));
<commit_msg>re DEV-826: Make sure lines in test_variant.js never exceed 80 characters.<commit_after>/*global expect, describe, it, beforeEach, spyOn, afterEach, runs, waitsFor */
/*global Backbone, jQuery, zeit*/
(function ($) {
    "use strict";

    var editor_container,
        preview_container,
        setUp = function() {
            var editor, preview, variant,
                url = '/fanstatic/zeit.content.image.test/master_image.jpg',
                flag = false;

            // Create temporary DOM
            editor_container = $(
                '
');
            preview_container = $('
');
            $('body').append(editor_container);
            $('body').append(preview_container);

            // Mock AJAX calls to return hard coded response
            spyOn($, 'ajax').andCallFake(function (options) {
                var d = $.Deferred(),
                    response = {
                        focus_x: 0.5,
                        focus_y: 0.5,
                        zoom: 0.3,
                        brightness: 0.5,
                        contrast: 0.5,
                        saturation: 0.5,
                        sharpness: 0.5,
                        is_default: true,
                        url: url
                    };
                d.resolve(response);
                options.success(response);
                return d.promise();
            });

            // Setup Editor and wait that it was rendered
            preview = new zeit.content.image.browser.VariantList();
            variant = new zeit.content.image.Variant({'id': 'square'});
            zeit.content.image.VARIANTS.add(variant);
            zeit.content.image.VARIANTS.trigger('reset');

            editor = new zeit.content.image.browser.VariantEditor();

            runs(function() {
                editor.on('render', function() {
                    flag = true;
                });
                editor.prepare();
            });

            waitsFor(function () {
                return flag;
            }, "VariantEditor did not render", 500);

            return {editor: editor, preview: preview, variant: variant};
        };

    describe("VariantEditor", function () {
        beforeEach(function() {
            var self = this,
                result = setUp();
            self.editor = result.editor;
            self.preview = result.preview;
            self.variant = result.variant;

            // Define shorthand to spy a Backbone method
            self.spy = function(method_name) {
                return spyOn(
                    Backbone.Model.prototype,
                    method_name
                ).andCallThrough();
            };
        });

        afterEach(function () {
            editor_container.remove();
            preview_container.remove();
        });

        it("should display circle relative to given focus point", function () {
            var self = this;
            runs(function() {
                expect(
                    self.editor.focuspoint.position()
                ).toEqual(
                    {left: 110, top: 62}
                );
            });
        });

        it("should save focus point after drag", function () {
            var self = this;
            runs(function() {
                var spy = self.spy("save");
                self.editor.focuspoint.css('left', '55px');
                self.editor.focuspoint.css('top', '31px');
                self.editor.focuspoint.trigger('dragstop');
                expect(spy).toHaveBeenCalledWith(
                    {"focus_x": 0.25, "focus_y": 0.25, "zoom": 0.3}
                );
            });
        });

        it("should display stored zoom value on load", function() {
            var self = this;
            runs(function() {
                // Since we want to inverse the default zoom-bar behaviour of
                // jqueryui, we also must expect the inverse value, i.e. 100-X
                expect(self.editor.zoom_bar.slider('value')).toEqual(100 - 30);
            });
        });

        it("should store zoom value on change", function() {
            var self = this;
            runs(function() {
                var spy = self.spy("save");
                // Since we want to inverse the default zoom-bar behaviour of
                // jqueryui, we also must set the inverse value, i.e. 100-X
                self.editor.zoom_bar.slider('value', 100 - 60);
                self.editor.zoom_bar.trigger('slidestop');
                expect(spy).toHaveBeenCalledWith(
                    {"focus_x": 0.5, "focus_y": 0.5, "zoom": 0.6}
                );
            });
        });

        it("should have 4 different image enhancements", function() {
            var self = this;
            runs(function() {
                expect(self.editor.image_enhancements.length).toBe(4);
            });
        });

        it("should display value of enhancements on load", function() {
            var self = this;
            runs(function() {
                $.each(self.editor.image_enhancements, function(i, name) {
                    var input = self.editor[name + '_input'],
                        bar = self.editor[name + '_bar'];

                    expect(input.val()).toEqual('-100');
                    expect(bar.slider("value")).toEqual(-100);
                });
            });
        });

        it("should store enhancement when changing slider", function() {
            var self = this;
            runs(function() {
                var spy = self.spy("set");
                $.each(self.editor.image_enhancements, function(i, name) {
                    var bar = self.editor[name + '_bar'];
                    bar.slider("value", 100);
                    bar.trigger("slidestop");
                    expect(spy).toHaveBeenCalledWith(name, 1.5);
                });
            });
        });

        it("should store enhancement when changing input field", function() {
            var self = this;
            runs(function() {
                var spy = self.spy("set");
                $.each(self.editor.image_enhancements, function(i, name) {
                    var input_field = self.editor[name + '_input'];
                    input_field.val(100);
                    input_field.trigger("blur");
                    expect(spy).toHaveBeenCalledWith(name, 1.5);
                });
            });
        });

        it("should switch back to default variant on save", function() {
            var self = this;
            runs(function() {
                self.editor.switch_focus(
                    self.variant, self.preview.model_views[self.variant.id]);
                expect(self.editor.current_model.id).toBe('square');
                $('input[value=Speichern]').click();
                expect(self.editor.current_model.id).toBe('default');
            });
        });

        it("should switch back to default variant on delete", function() {
            var self = this;
            runs(function() {
                self.editor.switch_focus(
                    self.variant, self.preview.model_views[self.variant.id]);
                expect(self.editor.current_model.id).toBe('square');
                $('input[value=Verwerfen]').click();
                expect(self.editor.current_model.id).toBe('default');
            });
        });

        it("should call destroy on every variant on reset", function() {
            var self = this,
                spy = self.spy("destroy");
            $("input[value='Alle Formate zurücksetzen']").click();
            expect(spy.calls.length).toEqual(1);
        });

        it("should update images of all variants on reset", function() {
            var self = this,
                image = self.preview.$('img.preview'),
                image_url = image.attr('src');
            $("input[value='Alle Formate zurücksetzen']").click();
            expect(image.attr('src')).not.toBe(image_url);
        });

        it("should update master image on save", function() {
            var self = this,
                image = self.editor.$('img.editor'),
                image_url = image.attr('src');
            self.editor.save();
            expect(image.attr('src')).not.toBe(image_url);
        });
    });
}(jQuery));
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/thirdparty/jquery",
	"sap/ui/base/Metadata",
	"sap/ui/core/Core",
	"sap/ui/core/CustomData",
	"sap/ui/core/Configuration",
	"sap/ui/base/ManagedObjectObserver",
	"./AnchorBar",
	"sap/m/Button",
	"sap/m/MenuButton",
	"sap/m/Menu",
	"sap/m/MenuItem",
	"sap/ui/core/IconPool"
], function (jQuery, Metadata, Core, CustomData, Configuration, ManagedObjectObserver, AnchorBar, Button, MenuButton, Menu, MenuItem, IconPool) {
	"use strict";

	// animation modes that support animation upon scroll to section
	var SCROLL_ANIMATION_MODES = [Configuration.AnimationMode.full, Configuration.AnimationMode.basic];

	var ABHelper = Metadata.createClass("sap.uxap._helpers.AB", {
		/**
		 * @private
		 * @param {sap.uxap.ObjectPageLayout} oObjectPageLayout Object Page layout instance
		 */
		constructor: function (oObjectPageLayout) {
			this._oObjectPageLayout = oObjectPageLayout;
			this._iScrollDuration = oObjectPageLayout._iScrollToSectionDuration;
			this._iFocusMoveDelay = this._iScrollDuration - 100;
			this._oObserver = new ManagedObjectObserver(this._proxyStateChanges.bind(this));
			this._aMenusWithAttachPressHandler = [];
		}
	});

	ABHelper.prototype.getObjectPageLayout = function () {
		return this._oObjectPageLayout;
	};

	ABHelper.prototype._proxyStateChanges = function (oChanges) {
		var oObject = oChanges.object,
			oObjectClone = this._findExistingClone(oObject),
			sPropertyName = oChanges.name,
			vCurrentValue = oChanges.current,
			sSetter = "set" + fnCapitalize(sPropertyName);

			if (oObjectClone) {
				oObjectClone[sSetter].call(oObjectClone, vCurrentValue);
			}
	};

	ABHelper.prototype._findExistingClone = function (oObject) {
		var oClone,
			sCloneIdPrefix = oObject.getId() + "-__clone",
			oAnchorBar = this._getAnchorBar(),
			aAnchorBarButtons = oAnchorBar.getContent();

			aAnchorBarButtons.some(function(oButton) {
				if (oButton.getId().indexOf(sCloneIdPrefix) === 0) {
					oClone = oButton;
					return true;
				}
			});

			return oClone;
	};

	/**
	 * Lazy loads the hidden Aggregation "_anchorBar"
	 * @returns {sap.uxap.AnchorBar} AnchorBar
	 * @private
	 */
	ABHelper.prototype._getAnchorBar = function () {
		var oObjectPageLayout = this.getObjectPageLayout(),
			oAnchorBar = oObjectPageLayout.getAggregation("_anchorBar");

		if (!oAnchorBar) {

			oAnchorBar = new AnchorBar({
				id: oObjectPageLayout.getId() + "-anchBar",
				showPopover: oObjectPageLayout.getShowAnchorBarPopover(),
				backgroundDesign: oObjectPageLayout.getBackgroundDesignAnchorBar()
			});

			this.getObjectPageLayout().setAggregation("_anchorBar", oAnchorBar, true);
		}

		return oAnchorBar;
	};

	ABHelper.prototype._setCustomData = function (oButtonForSectionBase, oSectionBase, oObjectPageLayout, bIsSection) {
			//update the section info
		oObjectPageLayout._oSectionInfo[oSectionBase.getId()].buttonId = oButtonForSectionBase.getId();

		//the AnchorBar needs to know the sectionId for automatic horizontal scrolling
		oButtonForSectionBase.addCustomData(new CustomData({
			key: "sectionId",
			value: oSectionBase.getId()
		}));

		//the AnchorBar needs to know whether the title is actually displayed or not (so the AnchorBar is really reflecting the ObjectPage layout state)
		oButtonForSectionBase.addCustomData(new CustomData({
			key: "bTitleVisible",
			value: oSectionBase._getInternalTitleVisible()
		}));

		if (!bIsSection) {
			//the AnchorBar needs to know that this is a second section because it will handle responsive scenarios
			oButtonForSectionBase.addCustomData(new CustomData({
				key: "secondLevel",
				value: true
			}));
		}
	};

	/**
	 * build the anchorBar and all the anchorBar buttons
	 * @private
	 */
	ABHelper.prototype._buildAnchorBar = function () {
		var oObjectPageLayout = this.getObjectPageLayout(),
			aSections = oObjectPageLayout.getSections() || [],
			oAnchorBar = this._getAnchorBar(),
			fnPressHandler = jQuery.proxy(oAnchorBar._handleDirectScroll, oAnchorBar),
			sButtonTitle,
			sButtonIcon,
			oMenuItem,
			oCustomButton;

		//tablet & desktop mechanism
		if (oAnchorBar && this.getObjectPageLayout().getShowAnchorBar()) {

			oAnchorBar._resetControl();
			this._oObserver.disconnect(); // unobserve all previousy observed objects

			//first level
			aSections.forEach(function (oSection) {

				if (!oSection.getVisible() || !oSection._getInternalVisible()) {
					return;
				}

				var oButtonClone,
					aSubSections = oSection.getSubSections() || [];

				oButtonClone = this._buildAnchorBarButton(oSection, true);

				if (oButtonClone) {
					oAnchorBar.addContent(oButtonClone);

					if (oButtonClone instanceof MenuButton) {
						var oMenu = new Menu({}),
							oRb = sap.ui.getCore().getLibraryResourceBundle("sap.uxap");

						// the focus goes to the internal SplitButton, so we need to enhance its accessibility properties also
						oButtonClone.enhanceAccessibilityState = function (oElement, mAriaProps) {
							var oContent = oAnchorBar.getContent(),
								iIndex = oContent.indexOf(oElement.getParent());

							if (iIndex !== -1) {
								mAriaProps.role = "menuitemradio";
								mAriaProps.roledescription = oRb.getText("ANCHOR_BAR_MENUITEM");
								mAriaProps.setsize = oContent.length;
								mAriaProps.posinset = iIndex + 1;
							}
						};

						oMenu._setCustomEnhanceAccStateFunction(function (oElement, mAriaProps) {
							mAriaProps.controls = oElement.data("sectionId");
						});

						oButtonClone.setMenu(oMenu);
					}

					//second Level
					aSubSections.forEach(function (oSubSection) {

						if (!oSubSection.getVisible() || !oSubSection._getInternalVisible()) {
							return;
						}

						var oSecondLevelButtonClone = this._buildAnchorBarButton(oSubSection, false),
							sId = oAnchorBar.getId() + "-" + oSubSection.getId() + "-anchor";

						if (oSecondLevelButtonClone) {
							oAnchorBar.addContent(oSecondLevelButtonClone);
						}

						if (oButtonClone instanceof MenuButton) {
							oCustomButton = oSubSection.getCustomAnchorBarButton();

							if (oCustomButton) {
								sButtonTitle = oCustomButton.getText();
								sButtonIcon = oCustomButton.getIcon();
							} else {
								sButtonTitle = oSubSection._getInternalTitle() || oSubSection.getTitle();
								sButtonIcon = '';
							}

							oMenuItem = new MenuItem(sId, {"text": sButtonTitle , "icon": sButtonIcon});

							oMenuItem.addCustomData(new CustomData({
								key: "sectionId",
								value: oSubSection.getId()
							}));

							oMenuItem.attachPress(fnPressHandler);
							this._setCustomData(oMenuItem, oSubSection, oObjectPageLayout, false);

							oButtonClone.getMenu().addItem(oMenuItem);
						}

					}, this);
				}

			}, this);
		}
	};

	/**
	 * Moves focus on the corresponding subsection when MenuItem is selected
	 * @param {sap.ui.core.Control} oSourceControl: selected Item
	 * @private
	 */
	ABHelper.prototype._moveFocusOnSection = function (oSourceControl) {
		var oSourceData = oSourceControl.data(),
			oSection = sap.ui.getCore().byId(oSourceData.sectionId),
			oObjectPage = this.getObjectPageLayout(),
			bIsSubSection = oSection.isA("sap.uxap.ObjectPageSubSection"),
			bAllowFocusMove = (oSection && !oObjectPage.getUseIconTabBar()) || (oObjectPage.getUseIconTabBar() && bIsSubSection),
			iFocusMoveDelay = this._iFocusMoveDelay;

		if (bAllowFocusMove && this._isScrollAnimationEnabled()) {
			setTimeout(oSection.$()["focus"].bind(oSection.$()), iFocusMoveDelay);
		} else if (bAllowFocusMove) {
			oSection.$().focus();
		}

		// Handle the case of SubSection first rendering in IconTabBar mode
		if (oObjectPage.getUseIconTabBar() && bIsSubSection) {
			var oDelegate = {"onAfterRendering": function () {
				this.removeEventDelegate(oDelegate);
				setTimeout(this.$()["focus"].bind(this.$()), iFocusMoveDelay);
			}.bind(oSection)};

			oSection.addEventDelegate(oDelegate);
		}
	};

	ABHelper.prototype._isScrollAnimationEnabled = function () {
		return SCROLL_ANIMATION_MODES.indexOf(Core.getConfiguration().getAnimationMode()) >= 0;
	};

	ABHelper.prototype._instantiateAnchorBarButton = function (bIsMenuButton, sAriaDescribedBy, sId) {
		var fnClass, oSettings;

		if (bIsMenuButton) {
			fnClass = MenuButton;
			oSettings = {
				type: "Transparent",
				buttonMode: "Split",
				useDefaultActionOnly: true,
				ariaDescribedBy: sAriaDescribedBy
			};
		} else {
			fnClass = Button;
			oSettings = {
				ariaDescribedBy: sAriaDescribedBy
			};
		}

		if (sId) {
			oSettings.id = sId;
		}

		return new fnClass(oSettings);
	};

	/**
	 * build an sap.m.button equivalent to a section or sub section for insertion in the anchorBar
	 * also registers the section info for further dom position updates
	 * @param oSectionBase
	 * @param {boolean} bIsSection
	 * @returns {null}
	 * @private
	 */
	ABHelper.prototype._buildAnchorBarButton = function (oSectionBase, bIsSection) {
		var oButtonClone = null,
			oObjectPageLayout = this.getObjectPageLayout(),
			oButton,
			oAnchorBar = this._getAnchorBar(),
			sId,
			bHasSubMenu,
			iVisibleSubSections,
			aSubSections = oSectionBase.getAggregation("subSections"),
			fnPressHandler = jQuery.proxy(oAnchorBar._handleDirectScroll, oAnchorBar);

		if (oSectionBase.getVisible() && oSectionBase._getInternalVisible()) {
			oButton = oSectionBase.getCustomAnchorBarButton();

			//by default we get create a button with the section title as text
			if (!oButton) {
				sId = oAnchorBar.getId() + "-" + oSectionBase.getId() + "-anchor";

				if (bIsSection) {
					if (aSubSections && aSubSections.length > 1) {
						iVisibleSubSections = aSubSections.filter(function (oSubSection) {
							return oSubSection.getVisible() && oSubSection._getInternalVisible();
						}).length;
					}
				}

				bHasSubMenu = bIsSection && iVisibleSubSections > 1 && oAnchorBar.getShowPopover();

				if (bHasSubMenu) {
					oButtonClone = this._instantiateAnchorBarButton(true, oSectionBase, sId);

					oButtonClone.attachDefaultAction(fnPressHandler);
					oButtonClone._getButtonControl().attachPress(function () {
						this.getParent().focus();
					});

					oButtonClone._getButtonControl().attachArrowPress(function () {
						var oButtonControl = oButtonClone._getButtonControl();

						if (this._aMenusWithAttachPressHandler[oButtonControl.getId()]) {
							return;
						}

						oButtonClone.getMenu().attachItemSelected(function (oEvent) {
							this._moveFocusOnSection(oEvent.getParameter("item"));
						}, this);

						this._aMenusWithAttachPressHandler[oButtonControl.getId()] = true;
					}, this);

					oButtonClone.addCustomData(new CustomData({
						key: "bHasSubMenu",
						value: true
					}));
				} else if (bIsSection){
					oButtonClone = this._instantiateAnchorBarButton(false, oSectionBase, sId);
					oButtonClone.attachPress(function (oEvent) {
						this._moveFocusOnSection(oEvent.getSource());
					}, this);
					oButtonClone.attachPress(fnPressHandler);
				} else {
					oButtonClone = this._instantiateAnchorBarButton(false, oSectionBase);
				}

				//has a ux rule been applied that we need to reflect here?
				var sTitle = (oSectionBase._getInternalTitle() != "") ? oSectionBase._getInternalTitle() : oSectionBase.getTitle();
				oButtonClone.setText(sTitle);
			} else {
				oButtonClone = oButton.clone(); //keep original button parent control hierarchy
				oButtonClone.attachPress(function (oEvent) {
					this._moveFocusOnSection(oEvent.getSource());
				}, this);
				oButtonClone.attachPress(fnPressHandler);
				this._oObserver.observe(oButton, {
					properties: true
				});
			}
			this._setCustomData(oButtonClone, oSectionBase, oObjectPageLayout, bIsSection);
		}

		return oButtonClone;
	};

	function fnCapitalize(sName) {
		return sName.substring(0, 1).toUpperCase() + sName.substring(1);
	}

	return ABHelper;

});<commit_msg>[FIX] sap.uxap.AnchorBar: Removed unstable ids<commit_after>/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/thirdparty/jquery",
	"sap/ui/base/Metadata",
	"sap/ui/core/Core",
	"sap/ui/core/CustomData",
	"sap/ui/core/Configuration",
	"sap/ui/base/ManagedObjectObserver",
	"./AnchorBar",
	"sap/m/Button",
	"sap/m/MenuButton",
	"sap/m/Menu",
	"sap/m/MenuItem",
	"sap/ui/core/IconPool"
], function (jQuery, Metadata, Core, CustomData, Configuration, ManagedObjectObserver, AnchorBar, Button, MenuButton, Menu, MenuItem, IconPool) {
	"use strict";

	// animation modes that support animation upon scroll to section
	var SCROLL_ANIMATION_MODES = [Configuration.AnimationMode.full, Configuration.AnimationMode.basic];

	var ABHelper = Metadata.createClass("sap.uxap._helpers.AB", {
		/**
		 * @private
		 * @param {sap.uxap.ObjectPageLayout} oObjectPageLayout Object Page layout instance
		 */
		constructor: function (oObjectPageLayout) {
			this._oObjectPageLayout = oObjectPageLayout;
			this._iScrollDuration = oObjectPageLayout._iScrollToSectionDuration;
			this._iFocusMoveDelay = this._iScrollDuration - 100;
			this._oObserver = new ManagedObjectObserver(this._proxyStateChanges.bind(this));
			this._aMenusWithAttachPressHandler = [];
		}
	});

	ABHelper.prototype.getObjectPageLayout = function () {
		return this._oObjectPageLayout;
	};

	ABHelper.prototype._proxyStateChanges = function (oChanges) {
		var oObject = oChanges.object,
			oObjectClone = this._findExistingClone(oObject),
			sPropertyName = oChanges.name,
			vCurrentValue = oChanges.current,
			sSetter = "set" + fnCapitalize(sPropertyName);

			if (oObjectClone) {
				oObjectClone[sSetter].call(oObjectClone, vCurrentValue);
			}
	};

	ABHelper.prototype._findExistingClone = function (oObject) {
		var oClone,
			sCloneIdPrefix = oObject.getId() + "-__clone",
			oAnchorBar = this._getAnchorBar(),
			aAnchorBarButtons = oAnchorBar.getContent();

			aAnchorBarButtons.some(function(oButton) {
				if (oButton.getId().indexOf(sCloneIdPrefix) === 0) {
					oClone = oButton;
					return true;
				}
			});

			return oClone;
	};

	/**
	 * Lazy loads the hidden Aggregation "_anchorBar"
	 * @returns {sap.uxap.AnchorBar} AnchorBar
	 * @private
	 */
	ABHelper.prototype._getAnchorBar = function () {
		var oObjectPageLayout = this.getObjectPageLayout(),
			oAnchorBar = oObjectPageLayout.getAggregation("_anchorBar");

		if (!oAnchorBar) {

			oAnchorBar = new AnchorBar({
				id: oObjectPageLayout.getId() + "-anchBar",
				showPopover: oObjectPageLayout.getShowAnchorBarPopover(),
				backgroundDesign: oObjectPageLayout.getBackgroundDesignAnchorBar()
			});

			this.getObjectPageLayout().setAggregation("_anchorBar", oAnchorBar, true);
		}

		return oAnchorBar;
	};

	ABHelper.prototype._setCustomData = function (oButtonForSectionBase, oSectionBase, oObjectPageLayout, bIsSection) {
			//update the section info
		oObjectPageLayout._oSectionInfo[oSectionBase.getId()].buttonId = oButtonForSectionBase.getId();

		//the AnchorBar needs to know the sectionId for automatic horizontal scrolling
		oButtonForSectionBase.addCustomData(new CustomData({
			key: "sectionId",
			value: oSectionBase.getId()
		}));

		//the AnchorBar needs to know whether the title is actually displayed or not (so the AnchorBar is really reflecting the ObjectPage layout state)
		oButtonForSectionBase.addCustomData(new CustomData({
			key: "bTitleVisible",
			value: oSectionBase._getInternalTitleVisible()
		}));

		if (!bIsSection) {
			//the AnchorBar needs to know that this is a second section because it will handle responsive scenarios
			oButtonForSectionBase.addCustomData(new CustomData({
				key: "secondLevel",
				value: true
			}));
		}
	};

	/**
	 * build the anchorBar and all the anchorBar buttons
	 * @private
	 */
	ABHelper.prototype._buildAnchorBar = function () {
		var oObjectPageLayout = this.getObjectPageLayout(),
			aSections = oObjectPageLayout.getSections() || [],
			oAnchorBar = this._getAnchorBar(),
			fnPressHandler = jQuery.proxy(oAnchorBar._handleDirectScroll, oAnchorBar),
			sButtonTitle,
			sButtonIcon,
			oMenuItem,
			oCustomButton;

		//tablet & desktop mechanism
		if (oAnchorBar && this.getObjectPageLayout().getShowAnchorBar()) {

			oAnchorBar._resetControl();
			this._oObserver.disconnect(); // unobserve all previousy observed objects

			//first level
			aSections.forEach(function (oSection) {

				if (!oSection.getVisible() || !oSection._getInternalVisible()) {
					return;
				}

				var oButtonClone,
					aSubSections = oSection.getSubSections() || [];

				oButtonClone = this._buildAnchorBarButton(oSection, true);

				if (oButtonClone) {
					oAnchorBar.addContent(oButtonClone);

					if (oButtonClone instanceof MenuButton) {
						var oMenu = new Menu({}),
							oRb = sap.ui.getCore().getLibraryResourceBundle("sap.uxap");

						// the focus goes to the internal SplitButton, so we need to enhance its accessibility properties also
						oButtonClone.enhanceAccessibilityState = function (oElement, mAriaProps) {
							var oContent = oAnchorBar.getContent(),
								iIndex = oContent.indexOf(oElement.getParent());

							if (iIndex !== -1) {
								mAriaProps.role = "menuitemradio";
								mAriaProps.roledescription = oRb.getText("ANCHOR_BAR_MENUITEM");
								mAriaProps.setsize = oContent.length;
								mAriaProps.posinset = iIndex + 1;
							}
						};

						oMenu._setCustomEnhanceAccStateFunction(function (oElement, mAriaProps) {
							mAriaProps.controls = oElement.data("sectionId");
						});

						oButtonClone.setMenu(oMenu);
					}

					//second Level
					aSubSections.forEach(function (oSubSection) {

						if (!oSubSection.getVisible() || !oSubSection._getInternalVisible()) {
							return;
						}

						var oSecondLevelButtonClone = this._buildAnchorBarButton(oSubSection, false),
							sId = oAnchorBar.getId() + "-" + oSubSection.getId() + "-anchor";

						if (oSecondLevelButtonClone) {
							oAnchorBar.addContent(oSecondLevelButtonClone);
						}

						if (oButtonClone instanceof MenuButton) {
							oCustomButton = oSubSection.getCustomAnchorBarButton();

							if (oCustomButton) {
								sButtonTitle = oCustomButton.getText();
								sButtonIcon = oCustomButton.getIcon();
							} else {
								sButtonTitle = oSubSection._getInternalTitle() || oSubSection.getTitle();
								sButtonIcon = '';
							}

							oMenuItem = new MenuItem(sId, {"text": sButtonTitle , "icon": sButtonIcon});

							oMenuItem.addCustomData(new CustomData({
								key: "sectionId",
								value: oSubSection.getId()
							}));

							oMenuItem.attachPress(fnPressHandler);
							this._setCustomData(oMenuItem, oSubSection, oObjectPageLayout, false);

							oButtonClone.getMenu().addItem(oMenuItem);
						}

					}, this);
				}

			}, this);
		}
	};

	/**
	 * Moves focus on the corresponding subsection when MenuItem is selected
	 * @param {sap.ui.core.Control} oSourceControl: selected Item
	 * @private
	 */
	ABHelper.prototype._moveFocusOnSection = function (oSourceControl) {
		var oSourceData = oSourceControl.data(),
			oSection = sap.ui.getCore().byId(oSourceData.sectionId),
			oObjectPage = this.getObjectPageLayout(),
			bIsSubSection = oSection.isA("sap.uxap.ObjectPageSubSection"),
			bAllowFocusMove = (oSection && !oObjectPage.getUseIconTabBar()) || (oObjectPage.getUseIconTabBar() && bIsSubSection),
			iFocusMoveDelay = this._iFocusMoveDelay;

		if (bAllowFocusMove && this._isScrollAnimationEnabled()) {
			setTimeout(oSection.$()["focus"].bind(oSection.$()), iFocusMoveDelay);
		} else if (bAllowFocusMove) {
			oSection.$().focus();
		}

		// Handle the case of SubSection first rendering in IconTabBar mode
		if (oObjectPage.getUseIconTabBar() && bIsSubSection) {
			var oDelegate = {"onAfterRendering": function () {
				this.removeEventDelegate(oDelegate);
				setTimeout(this.$()["focus"].bind(this.$()), iFocusMoveDelay);
			}.bind(oSection)};

			oSection.addEventDelegate(oDelegate);
		}
	};

	ABHelper.prototype._isScrollAnimationEnabled = function () {
		return SCROLL_ANIMATION_MODES.indexOf(Core.getConfiguration().getAnimationMode()) >= 0;
	};

	ABHelper.prototype._instantiateAnchorBarButton = function (bIsMenuButton, sAriaDescribedBy, sId) {
		var fnClass, oSettings;

		if (bIsMenuButton) {
			fnClass = MenuButton;
			oSettings = {
				type: "Transparent",
				buttonMode: "Split",
				useDefaultActionOnly: true,
				ariaDescribedBy: sAriaDescribedBy
			};
		} else {
			fnClass = Button;
			oSettings = {
				ariaDescribedBy: sAriaDescribedBy
			};
		}

		if (sId) {
			oSettings.id = sId;
		}

		return new fnClass(oSettings);
	};

	/**
	 * build an sap.m.button equivalent to a section or sub section for insertion in the anchorBar
	 * also registers the section info for further dom position updates
	 * @param oSectionBase
	 * @param {boolean} bIsSection
	 * @returns {null}
	 * @private
	 */
	ABHelper.prototype._buildAnchorBarButton = function (oSectionBase, bIsSection) {
		var oButtonClone = null,
			oObjectPageLayout = this.getObjectPageLayout(),
			oButton,
			oAnchorBar = this._getAnchorBar(),
			sId,
			sSubId,
			bHasSubMenu,
			iVisibleSubSections,
			aSubSections = oSectionBase.getAggregation("subSections"),
			fnPressHandler = jQuery.proxy(oAnchorBar._handleDirectScroll, oAnchorBar);

		if (oSectionBase.getVisible() && oSectionBase._getInternalVisible()) {
			oButton = oSectionBase.getCustomAnchorBarButton();

			//by default we get create a button with the section title as text
			if (!oButton) {
				sId = oAnchorBar.getId() + "-" + oSectionBase.getId() + "-anchor";

				if (bIsSection) {
					if (aSubSections && aSubSections.length > 1) {
						iVisibleSubSections = aSubSections.filter(function (oSubSection) {
							return oSubSection.getVisible() && oSubSection._getInternalVisible();
						}).length;
					}
				}

				bHasSubMenu = bIsSection && iVisibleSubSections > 1 && oAnchorBar.getShowPopover();

				if (bHasSubMenu) {
					oButtonClone = this._instantiateAnchorBarButton(true, oSectionBase, sId);

					oButtonClone.attachDefaultAction(fnPressHandler);
					oButtonClone._getButtonControl().attachPress(function () {
						this.getParent().focus();
					});

					oButtonClone._getButtonControl().attachArrowPress(function () {
						var oButtonControl = oButtonClone._getButtonControl();

						if (this._aMenusWithAttachPressHandler[oButtonControl.getId()]) {
							return;
						}

						oButtonClone.getMenu().attachItemSelected(function (oEvent) {
							this._moveFocusOnSection(oEvent.getParameter("item"));
						}, this);

						this._aMenusWithAttachPressHandler[oButtonControl.getId()] = true;
					}, this);

					oButtonClone.addCustomData(new CustomData({
						key: "bHasSubMenu",
						value: true
					}));
				} else if (bIsSection){
					oButtonClone = this._instantiateAnchorBarButton(false, oSectionBase, sId);
					oButtonClone.attachPress(function (oEvent) {
						this._moveFocusOnSection(oEvent.getSource());
					}, this);
					oButtonClone.attachPress(fnPressHandler);
				} else {
					sSubId = oAnchorBar.getId() + "-" + oSectionBase.getId() + "-sub-anchor";
					oButtonClone = this._instantiateAnchorBarButton(false, oSectionBase, sSubId);
				}

				//has a ux rule been applied that we need to reflect here?
				var sTitle = (oSectionBase._getInternalTitle() != "") ? oSectionBase._getInternalTitle() : oSectionBase.getTitle();
				oButtonClone.setText(sTitle);
			} else {
				oButtonClone = oButton.clone(); //keep original button parent control hierarchy
				oButtonClone.attachPress(function (oEvent) {
					this._moveFocusOnSection(oEvent.getSource());
				}, this);
				oButtonClone.attachPress(fnPressHandler);
				this._oObserver.observe(oButton, {
					properties: true
				});
			}
			this._setCustomData(oButtonClone, oSectionBase, oObjectPageLayout, bIsSection);
		}

		return oButtonClone;
	};

	function fnCapitalize(sName) {
		return sName.substring(0, 1).toUpperCase() + sName.substring(1);
	}

	return ABHelper;

});<|endoftext|>


<commit_before>// we will re-enable the broken browsers once saucelabs will fix all the timeout issues
module.exports = {
  browsers: {
    /*
    slIphone5: {
      base: 'SauceLabs',
      browserName: 'iphone',
      platform: 'OS X 10.10',
      version: '8.1'
    },
    */
/*    slIphone6: {
      base: 'SauceLabs',
      device: 'iPhone 6',
      platform: 'OS X 10.10',
      os: 'ios',
      version: '9.2'
    },
    slIpad: {
      base: 'SauceLabs',
      browserName: 'ipad',
      platform: 'OS X 10.10',
      deviceName: 'iPad 2',
      version: '9.2'
    },*/
    slSafari7: {
      base: 'SauceLabs',
      browserName: 'safari',
      platform: 'OS X 10.9'
    },
    /*
    slSafari8: {
      base: 'SauceLabs',
      browserName: 'safari',
      platform: 'OS X 10.10'
    },
    */
  /*  slSafari9: {
      base: 'SauceLabs',
      browserName: 'safari',
      platform: 'OS X 10.11'
    },*/
    slIE9: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 7',
      version: '9'
    },
    slIE10: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 8',
      version: '10'
    },
    slIE11: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 8.1',
      version: '11'
    },
    slEdge: {
      base: 'SauceLabs',
      browserName: 'MicrosoftEdge',
      platform: 'Windows 10',
      version: '20.10240'
    },
    slChrome: {
      base: 'SauceLabs',
      browserName: 'chrome'
    },
    slFirefox: {
      base: 'SauceLabs',
      browserName: 'firefox'
    },
    slAndroid4: {
      base: 'SauceLabs',
      browserName: 'android',
      version: '4.0'
    },
    slAndroid5: {
      base: 'SauceLabs',
      browserName: 'android',
      version: '5.1'
    }
  }
}
<commit_msg>removed: edge from the saucelabs tests<commit_after>// we will re-enable the broken browsers once saucelabs will fix all the timeout issues
module.exports = {
  browsers: {
    /*
    slIphone5: {
      base: 'SauceLabs',
      browserName: 'iphone',
      platform: 'OS X 10.10',
      version: '8.1'
    },
    */
/*    slIphone6: {
      base: 'SauceLabs',
      device: 'iPhone 6',
      platform: 'OS X 10.10',
      os: 'ios',
      version: '9.2'
    },
    slIpad: {
      base: 'SauceLabs',
      browserName: 'ipad',
      platform: 'OS X 10.10',
      deviceName: 'iPad 2',
      version: '9.2'
    },*/
    slSafari7: {
      base: 'SauceLabs',
      browserName: 'safari',
      platform: 'OS X 10.9'
    },
    /*
    slSafari8: {
      base: 'SauceLabs',
      browserName: 'safari',
      platform: 'OS X 10.10'
    },
    */
  /*  slSafari9: {
      base: 'SauceLabs',
      browserName: 'safari',
      platform: 'OS X 10.11'
    },*/
    slIE9: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 7',
      version: '9'
    },
    slIE10: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 8',
      version: '10'
    },
    slIE11: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 8.1',
      version: '11'
    },
/*    slEdge: {
      base: 'SauceLabs',
      browserName: 'MicrosoftEdge',
      platform: 'Windows 10',
      version: '20.10240'
    },*/
    slChrome: {
      base: 'SauceLabs',
      browserName: 'chrome'
    },
    slFirefox: {
      base: 'SauceLabs',
      browserName: 'firefox'
    },
    slAndroid4: {
      base: 'SauceLabs',
      browserName: 'android',
      version: '4.0'
    },
    slAndroid5: {
      base: 'SauceLabs',
      browserName: 'android',
      version: '5.1'
    }
  }
}
<|endoftext|>


<commit_before>/*global expect, describe, it, beforeEach, spyOn, afterEach, runs, waitsFor */
/*global Backbone, jQuery, zeit*/
(function ($) {
    "use strict";

    describe("Focuspoint Test", function () {
        beforeEach(function() {
            this.container = $('
');
            $('body').append(this.container);

            spyOn($, 'ajax').andCallFake(function (options) {
                var d = $.Deferred(),
                    response = {
                        focus_x: 0.5,
                        focus_y: 0.5,
                        url: '/fanstatic/zeit.content.image.test/master_image.jpg'
                    };
                d.resolve(response);
                options.success(response);
                return d.promise();
            });
        });

        afterEach(function () {
            this.container.remove();
        });

        it("should display circle relative to given focus point", function () {
            var flag = false,
                view = new zeit.content.image.browser.VariantEditor();

            runs(function() {
                view.on('render', function() {
                    flag = true;
                });
                view.prepare();
            });

            waitsFor(function () {
                return flag;
            }, "Image did not render", 500);

            runs(function() {
                expect(view.circle.position()).toEqual({left: 110, top: 62});
            });
        });

        it("should save focus point after drag", function () {
            var flag = false,
                view = new zeit.content.image.browser.VariantEditor();

            runs(function() {
                view.on('render', function() {
                    flag = true;
                });
                view.prepare();
            });

            waitsFor(function () {
                return flag;
            }, "Image did not render", 500);

            runs(function() {
                var spy = spyOn(Backbone.Model.prototype, "save").andCallThrough();
                view.circle.css('left', '55px');
                view.circle.css('top', '31px');
                view.circle.trigger('dragstop');
                expect(spy).toHaveBeenCalledWith(
                    {"focus_x": 0.25, "focus_y": 0.25});
            });
        });
    });
}(jQuery));<commit_msg>re DEV-787: Fix Jasmine tests by setting a fixed container width. (Belongs to commit 4ad8757)<commit_after>/*global expect, describe, it, beforeEach, spyOn, afterEach, runs, waitsFor */
/*global Backbone, jQuery, zeit*/
(function ($) {
    "use strict";

    describe("Focuspoint Test", function () {
        beforeEach(function() {
            this.container = $('
');
            $('body').append(this.container);

            spyOn($, 'ajax').andCallFake(function (options) {
                var d = $.Deferred(),
                    response = {
                        focus_x: 0.5,
                        focus_y: 0.5,
                        url: '/fanstatic/zeit.content.image.test/master_image.jpg'
                    };
                d.resolve(response);
                options.success(response);
                return d.promise();
            });
        });

        afterEach(function () {
            this.container.remove();
        });

        it("should display circle relative to given focus point", function () {
            var flag = false,
                view = new zeit.content.image.browser.VariantEditor();

            runs(function() {
                view.on('render', function() {
                    flag = true;
                });
                view.prepare();
            });

            waitsFor(function () {
                return flag;
            }, "Image did not render", 500);

            runs(function() {
                expect(view.circle.position()).toEqual({left: 110, top: 62});
            });
        });

        it("should save focus point after drag", function () {
            var flag = false,
                view = new zeit.content.image.browser.VariantEditor();

            runs(function() {
                view.on('render', function() {
                    flag = true;
                });
                view.prepare();
            });

            waitsFor(function () {
                return flag;
            }, "Image did not render", 500);

            runs(function() {
                var spy = spyOn(Backbone.Model.prototype, "save").andCallThrough();
                view.circle.css('left', '55px');
                view.circle.css('top', '31px');
                view.circle.trigger('dragstop');
                expect(spy).toHaveBeenCalledWith(
                    {"focus_x": 0.25, "focus_y": 0.25});
            });
        });
    });
}(jQuery));<|endoftext|>


<commit_before>const { AuthenticationBaseStrategy } = require('@feathersjs/authentication');
const { NotAuthenticated } = require('@feathersjs/errors');

const logger = require('../../../logger');
const {
	verifyToken,
	decryptToken,
	createUserAndAccount,
	findSchool,
	ENTITY_SOURCE, SOURCE_ID_ATTRIBUTE,
} = require('../../sync/strategies/TSP/TSP');
const { SYNCER_TARGET } = require('../../sync/strategies/TSP/TSPSchoolSyncer');

class TSPStrategy extends AuthenticationBaseStrategy {
	get configuration() {
		const authConfig = this.authentication.configuration;
		const config = super.configuration || {};
		return {
			service: authConfig.service,
			entity: authConfig.entity,
			entityId: authConfig.entityId,
			errorMessage: 'Invalid login',
			entityUsernameField: config.usernameField,
			...config,
		};
	}

	verifyConfiguration() {}

	async findUser(app, token) {
		const sourceOptions = {
			[SOURCE_ID_ATTRIBUTE]: token.authUID,
		};
		const [user] = await app.service('users').find({
			query: {
				source: ENTITY_SOURCE,
				sourceOptions,
				$limit: 1,
			},
			paginate: false,
		});
		return user;
	}

	async authenticate(authentication, params) {
		const { ticket } = authentication;
		let decryptedTicket;
		try {
			const verifiedToken = await verifyToken(ticket);
			decryptedTicket = await decryptToken(verifiedToken);
			// todo: verify iat and exp dates
		} catch (err) {
			logger.error('TSP ticket not valid.', err);
			throw new NotAuthenticated('TSP ticket is not valid.');
		}

		// translate TSP roles into SC roles
		let roles;
		if (decryptedTicket.ptscListRolle && typeof decryptedTicket.ptscListRolle === 'string') {
			roles = decryptedTicket.ptscListRolle.split(',').map((tspRole) => ({
				schueler: 'student',
				lehrer: 'teacher',
				admin: 'administrator',
			}[tspRole.toLowerCase()])).filter((r) => r);
		}

		const { app } = this;
		let user = await this.findUser(app, decryptedTicket);
		if (!user) {
			// User might have been created since the last sync, so we'll create him from the ticket info
			const sourceOptions = {
				[SOURCE_ID_ATTRIBUTE]: decryptedTicket.authUID,
			};
			const school = await findSchool(app, decryptedTicket.ptscSchuleNummer);
			const systemId = school.systems[0];
			user = await createUserAndAccount(
				app,
				{
					namePrefix: decryptedTicket.personTitel,
					firstName: decryptedTicket.personVorname,
					lastName: decryptedTicket.personNachname,
					schoolId: school._id,
					source: ENTITY_SOURCE,
					sourceOptions,
				},
				roles,
				systemId,
			);
		} else if (Array.isArray(roles)) {
			// if we know the user and roles were supplied, we need to reflect role changes
			await app.service('users').patch(user._id, { roles });
		}

		const oneDayInMilliseconds = 864e5;
		const timeOfLastSync = Date.now() - oneDayInMilliseconds;

		// trigger an asynchronous TSP sync to reflect changes to classes
		app.service('sync').find({
			query: {
				target: SYNCER_TARGET,
				config: {
					schoolIdentifier: decryptedTicket.ptscSchuleNummer,
					lastChange: timeOfLastSync,
				},
			},
		});

		const [account] = await app.service('accounts').find({
			query: {
				userId: user._id,
				$limit: 1,
			},
			paginate: false,
		});
		const { entity } = this.configuration;
		return {
			authentication: { strategy: this.name },
			[entity]: account,
			payload: {
				accountId: account._id,
				userId: user._id,
				systemId: account.systemId,
			},
		};
	}
}

module.exports = TSPStrategy;
<commit_msg>Remove ambiguity<commit_after>const { AuthenticationBaseStrategy } = require('@feathersjs/authentication');
const { NotAuthenticated } = require('@feathersjs/errors');

const logger = require('../../../logger');
const {
	verifyToken,
	decryptToken,
	createUserAndAccount,
	findSchool,
	ENTITY_SOURCE, SOURCE_ID_ATTRIBUTE,
} = require('../../sync/strategies/TSP/TSP');
const { SYNCER_TARGET } = require('../../sync/strategies/TSP/TSPSchoolSyncer');

class TSPStrategy extends AuthenticationBaseStrategy {
	get configuration() {
		const authConfig = this.authentication.configuration;
		const config = super.configuration || {};
		return {
			service: authConfig.service,
			entity: authConfig.entity,
			entityId: authConfig.entityId,
			errorMessage: 'Invalid login',
			entityUsernameField: config.usernameField,
			...config,
		};
	}

	verifyConfiguration() {}

	async findUser(app, token) {
		const sourceOptions = {
			[SOURCE_ID_ATTRIBUTE]: token.authUID,
		};
		const [user] = await app.service('users').find({
			query: {
				source: ENTITY_SOURCE,
				sourceOptions,
				$limit: 1,
			},
			paginate: false,
		});
		return user;
	}

	async authenticate(authentication, params) {
		const { ticket } = authentication;
		let decryptedTicket;
		try {
			const verifiedToken = await verifyToken(ticket);
			decryptedTicket = await decryptToken(verifiedToken);
			// todo: verify iat and exp dates
		} catch (err) {
			logger.error('TSP ticket not valid.', err);
			throw new NotAuthenticated('TSP ticket is not valid.');
		}

		// translate TSP roles into SC roles
		let roles;
		if (decryptedTicket.ptscListRolle && typeof decryptedTicket.ptscListRolle === 'string') {
			roles = decryptedTicket.ptscListRolle.split(',').map((tspRole) => ({
				schueler: 'student',
				lehrer: 'teacher',
				admin: 'administrator',
			}[tspRole.toLowerCase()])).filter((r) => r);
		}

		const { app } = this;
		let user = await this.findUser(app, decryptedTicket);
		if (!user) {
			// User has been created since the last sync, so we'll create him from the ticket info
			const sourceOptions = {
				[SOURCE_ID_ATTRIBUTE]: decryptedTicket.authUID,
			};
			const school = await findSchool(app, decryptedTicket.ptscSchuleNummer);
			const systemId = school.systems[0];
			user = await createUserAndAccount(
				app,
				{
					namePrefix: decryptedTicket.personTitel,
					firstName: decryptedTicket.personVorname,
					lastName: decryptedTicket.personNachname,
					schoolId: school._id,
					source: ENTITY_SOURCE,
					sourceOptions,
				},
				roles,
				systemId,
			);
		} else if (Array.isArray(roles)) {
			// if we know the user and roles were supplied, we need to reflect role changes
			await app.service('users').patch(user._id, { roles });
		}

		const oneDayInMilliseconds = 864e5;
		const timeOfLastSync = Date.now() - oneDayInMilliseconds;

		// trigger an asynchronous TSP sync to reflect changes to classes
		app.service('sync').find({
			query: {
				target: SYNCER_TARGET,
				config: {
					schoolIdentifier: decryptedTicket.ptscSchuleNummer,
					lastChange: timeOfLastSync,
				},
			},
		});

		const [account] = await app.service('accounts').find({
			query: {
				userId: user._id,
				$limit: 1,
			},
			paginate: false,
		});
		const { entity } = this.configuration;
		return {
			authentication: { strategy: this.name },
			[entity]: account,
			payload: {
				accountId: account._id,
				userId: user._id,
				systemId: account.systemId,
			},
		};
	}
}

module.exports = TSPStrategy;
<|endoftext|>