diff --git a/.github/workflows/build-multi-arch-pinot-docker-image.yml b/.github/workflows/build-multi-arch-pinot-docker-image.yml new file mode 100644 index 00000000000..d6f41d059d0 --- /dev/null +++ b/.github/workflows/build-multi-arch-pinot-docker-image.yml @@ -0,0 +1,108 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +name: Pinot Multi-Arch Platform Docker Image Build and Publish + +on: + workflow_dispatch: + inputs: + gitUrl: + description: "The Pinot git repo to check out to build, use https." + default: "https://github.com/apache/pinot.git" + required: true + commit: + description: "The branch/commit to check out to build Pinot image." + default: "master" + required: true + dockerImageName: + description: "The docker image name, default to 'apachepinot/pinot'." + default: "apachepinot/pinot" + required: true + tags: + description: "Tags to push of the image, comma separated, e.g. tag1,tag2,tag3" + default: "" + +jobs: + generate-build-info: + name: Generate Build Info + runs-on: ubuntu-latest + outputs: + commit-id: ${{ steps.generate-build-info.outputs.commit-id }} + tags: ${{ steps.generate-build-info.outputs.tags }} + steps: + - uses: actions/checkout@v3 + - name: Generate Build Info + id: generate-build-info + env: + PINOT_GIT_URL: ${{ github.event.inputs.gitUrl }} + PINOT_BRANCH: ${{ github.event.inputs.commit }} + TAGS: ${{ github.event.inputs.tags }} + run: | + .github/workflows/scripts/docker/.pinot_build_info_gen.sh + build-pinot-docker-image: + runs-on: ubuntu-latest + strategy: + matrix: + arch: [ "amd64", "arm64" ] + base-image-tag: [ "11-amazoncorretto", "11-ms-openjdk" ] + name: Build Pinot Docker Image on ${{ matrix.arch }} with base image ${{ matrix.base-image-tag }} + needs: [ generate-build-info ] + steps: + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/setup-qemu-action@v2 + name: Set up QEMU + with: + platforms: linux/${{ matrix.arch }} + - uses: docker/setup-buildx-action@v2 + name: Set up Docker Buildx + - uses: actions/checkout@v3 + - name: Build and push the Docker image + env: + DOCKER_FILE_BASE_DIR: "docker/images/pinot" + DOCKER_IMAGE_NAME: ${{ github.event.inputs.dockerImageName }} + BUILD_PLATFORM: "linux/${{ matrix.arch }}" + BASE_IMAGE_TAG: ${{ matrix.base-image-tag }} + PINOT_GIT_URL: ${{ github.event.inputs.gitUrl }} + PINOT_BRANCH: "${{needs.generate-build-info.outputs.commit-id}}" + TAGS: "${{needs.generate-build-info.outputs.tags}}" + run: .github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh + create-multi-arch-manifest: + name: Create Multi-Arch Manifest + runs-on: ubuntu-latest + needs: [ generate-build-info, build-pinot-docker-image ] + steps: + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/setup-qemu-action@v2 + name: Set up QEMU + - uses: docker/setup-buildx-action@v2 + name: Set up Docker Buildx + - uses: actions/checkout@v3 + - name: Create Multi-Arch Manifest + env: + TAGS: "${{needs.generate-build-info.outputs.tags}}" + BUILD_PLATFORM: "linux/arm64,linux/amd64" + BASE_IMAGE_TAGS: "11-amazoncorretto,11-ms-openjdk" + run: .github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh diff --git a/.github/workflows/build-pinot-base-docker-image.yml b/.github/workflows/build-pinot-base-docker-image.yml new file mode 100644 index 00000000000..b540ffd61ef --- /dev/null +++ b/.github/workflows/build-pinot-base-docker-image.yml @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +name: Pinot Base Docker Image Build and Publish + +on: + workflow_dispatch: + inputs: { } + +jobs: + build-pinot-build-docker-image: + name: Build Pinot Base Docker Image + runs-on: ubuntu-latest + strategy: + matrix: + baseImageType: [ "build", "runtime" ] + openJdkDist: [ "amazoncorretto", "ms-openjdk" ] + steps: + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/setup-qemu-action@v2 + name: Set up QEMU + - uses: docker/setup-buildx-action@v2 + name: Set up Docker Buildx + - uses: actions/checkout@v3 + - name: Build and push the Docker image + env: + OPEN_JDK_DIST: ${{ matrix.openJdkDist }} + BASE_IMAGE_TYPE: ${{ matrix.baseImageType }} + BUILD_PLATFORM: "linux/amd64,linux/arm64" + TAG: "11-${{ matrix.openJdkDist }}" + run: .github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh diff --git a/.github/workflows/build-pinot-docker-image.yml b/.github/workflows/build-pinot-docker-image.yml index 345440750e9..93ab5850a70 100644 --- a/.github/workflows/build-pinot-docker-image.yml +++ b/.github/workflows/build-pinot-docker-image.yml @@ -47,13 +47,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v2 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 name: Set up Docker Buildx - uses: actions/checkout@v3 - name: Build and push the Docker image diff --git a/.github/workflows/build-presto-docker-image.yml b/.github/workflows/build-presto-docker-image.yml index 7ae35253615..43b09ea5def 100644 --- a/.github/workflows/build-presto-docker-image.yml +++ b/.github/workflows/build-presto-docker-image.yml @@ -47,13 +47,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v2 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 name: Set up Docker Buildx - uses: actions/checkout@v3 - name: Build and push the Docker image diff --git a/.github/workflows/build-superset-docker-image.yml b/.github/workflows/build-superset-docker-image.yml index c0e87199169..55d28786252 100644 --- a/.github/workflows/build-superset-docker-image.yml +++ b/.github/workflows/build-superset-docker-image.yml @@ -43,13 +43,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v2 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 name: Set up Docker Buildx - uses: actions/checkout@v3 - name: Build and push the Docker image diff --git a/.github/workflows/pinot_compatibility_tests.yml b/.github/workflows/pinot_compatibility_tests.yml index acb0b1974ca..4059852163f 100644 --- a/.github/workflows/pinot_compatibility_tests.yml +++ b/.github/workflows/pinot_compatibility_tests.yml @@ -36,11 +36,23 @@ jobs: test_suite: [ "compatibility-verifier/sample-test-suite" ] name: Pinot Compatibility Regression Testing against ${{ github.event.inputs.oldCommit }} and ${{ github.event.inputs.newCommit }} on ${{ matrix.test_suite }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 11 + distribution: 'adopt' + cache: 'maven' + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: v10.16.1 + cache: 'npm' + cache-dependency-path: pinot-controller/src/main/resources/package-lock.json + - name: Install npm + run: | + npm install -g npm@6.10.0 + npm --version - name: Pinot Compatibility Regression Testing if : ${{github.event_name == 'workflow_dispatch'}} env: @@ -50,7 +62,7 @@ jobs: TEST_SUITE: ${{ matrix.test_suite }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -62,7 +74,7 @@ jobs: if: always() run: | zip -1 -r artifacts.zip /tmp/compatibility-verifier/* - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 name: Store compatibility verifier work directory if: always() with: diff --git a/.github/workflows/pinot_tests.yml b/.github/workflows/pinot_tests.yml index b3331fc7d44..844b7bd6fe8 100644 --- a/.github/workflows/pinot_tests.yml +++ b/.github/workflows/pinot_tests.yml @@ -49,16 +49,18 @@ jobs: runs-on: ubuntu-latest name: Pinot Linter Test Set steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 11 + distribution: 'adopt' + cache: 'maven' - name: Linter Test env: MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -70,15 +72,21 @@ jobs: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: testset: [ 1, 2 ] - name: Pinot Unit Test Set ${{ matrix.testset }} + java: [ 11, 17, 20 ] + distribution: [ "temurin" ] + name: Pinot Unit Test Set ${{ matrix.testset }} (${{matrix.distribution}}-${{matrix.java}}) steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 with: - java-version: 11 + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' # Step that does that actual cache save and restore - uses: actions/cache@v3 env: @@ -94,7 +102,7 @@ jobs: RUN_TEST_SET: ${{ matrix.testset }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -104,8 +112,10 @@ jobs: run: .github/workflows/scripts/.pinot_test.sh - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + continue-on-error: true + timeout-minutes: 5 with: - flags: unittests${{ matrix.testset }} + flags: unittests${{ matrix.testset }}${{matrix.distribution}}${{matrix.java}} name: codecov-unit-tests fail_ci_if_error: false verbose: true @@ -114,15 +124,21 @@ jobs: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: testset: [ 1, 2 ] - name: Pinot Integration Test Set ${{ matrix.testset }} + java: [ 11, 17, 20 ] + distribution: [ "temurin" ] + name: Pinot Integration Test Set ${{ matrix.testset }} (${{matrix.distribution}}-${{matrix.java}}) steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 with: - java-version: 11 + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' # Step that does that actual cache save and restore - uses: actions/cache@v3 env: @@ -138,7 +154,7 @@ jobs: RUN_TEST_SET: ${{ matrix.testset }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -148,8 +164,10 @@ jobs: run: .github/workflows/scripts/.pinot_test.sh - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + continue-on-error: true + timeout-minutes: 5 with: - flags: integration${{ matrix.testset }} + flags: integration${{ matrix.testset }}${{matrix.distribution}}${{matrix.java}} name: codecov-integration-tests fail_ci_if_error: false verbose: true @@ -158,16 +176,32 @@ jobs: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: test_suite: [ "compatibility-verifier/sample-test-suite" ] - old_commit: [ "release-0.9.0", "master" ] + old_commit: [ + "release-0.9.3", "release-0.10.0", "release-0.11.0", "release-0.12.1", "master" + ] name: Pinot Compatibility Regression Testing against ${{ matrix.old_commit }} on ${{ matrix.test_suite }} steps: - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v3 with: - java-version: 1.8 + java-version: 11 + distribution: 'adopt' + cache: 'maven' + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: v10.16.1 + cache: 'npm' + cache-dependency-path: pinot-controller/src/main/resources/package-lock.json + - name: Install npm + run: | + npm install -g npm@6.10.0 + npm --version # Step that does that actual cache save and restore - uses: actions/cache@v3 env: @@ -184,7 +218,7 @@ jobs: TEST_SUITE: ${{ matrix.test_suite }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -197,17 +231,20 @@ jobs: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: - # We only test LTS Java versions - # TODO: add JDK 17 once ready. see: https://github.com/apache/pinot/issues/8529 - java: [ 1.8, 11, 15 ] + java: [ 11, 17, 20 ] + distribution: [ "temurin" ] name: Pinot Quickstart on JDK ${{ matrix.java }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' # Step that does that actual cache save and restore - uses: actions/cache@v3 env: @@ -225,30 +262,15 @@ jobs: runs-on: ubuntu-latest name: Build Presto Pinot Driver steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build presto pinot driver with JDK 11 - env: - MAVEN_OPTS: > - -Xmx2G -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false - -XX:+IgnoreUnrecognizedVMOptions - --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED - --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED - --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED - --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED - --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - run: | - mvn clean install -DskipTests -Ppresto-driver -am -B -pl ':presto-pinot-driver' -T 16 || exit 1 + - uses: actions/checkout@v3 - name: Set up JDK 8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 8 + distribution: 'adopt' + cache: 'maven' - name: Build presto pinot driver with JDK 8 env: - MAVEN_OPTS: -Xmx2G -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + MAVEN_OPTS: -Xmx2G -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false run: | - mvn clean install -DskipTests -Ppresto-driver -am -B -pl ':presto-pinot-driver' -Djdk.version=8 -T 16 || exit 1 + mvn clean install -Dmaven.test.skip=true -Ppresto-driver -am -B -pl ':pinot-spi-jdk8',':pinot-common-jdk8',':pinot-segment-spi-jdk8' -Djdk.version=8 -T 16 || exit 1 diff --git a/.github/workflows/pinot_vuln_check.yml b/.github/workflows/pinot_vuln_check.yml index e471f880490..d9aea49353b 100644 --- a/.github/workflows/pinot_vuln_check.yml +++ b/.github/workflows/pinot_vuln_check.yml @@ -37,9 +37,9 @@ jobs: name: Verify Docker Image runs-on: ubuntu-latest steps: - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v2 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 name: Set up Docker Buildx - uses: actions/checkout@v3 - name: Build the Docker image diff --git a/.github/workflows/scripts/.pinot_compatibility_verifier.sh b/.github/workflows/scripts/.pinot_compatibility_verifier.sh index 0897eacdd80..93789ce67ca 100755 --- a/.github/workflows/scripts/.pinot_compatibility_verifier.sh +++ b/.github/workflows/scripts/.pinot_compatibility_verifier.sh @@ -41,17 +41,49 @@ echo " https://packages.confluent.io/maven/">> ${SETTINGS_FILE} echo " false">> ${SETTINGS_FILE} echo " ">> ${SETTINGS_FILE} echo " ">> ${SETTINGS_FILE} + +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " central">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " 120000">> ${SETTINGS_FILE} +echo " 120000">> ${SETTINGS_FILE} +echo " 3">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} + echo "">> ${SETTINGS_FILE} # PINOT_MAVEN_OPTS is used to provide additional maven options to the checkoutAndBuild.sh command export PINOT_MAVEN_OPTS="-s $(pwd)/${SETTINGS_FILE}" -if [ -z "$newerCommit" ]; then - echo "Running compatibility regression test against \"${olderCommit}\"" +# Compare commit hash for compatibility verification +git fetch --all +NEW_COMMIT_HASH=`git log -1 --pretty=format:'%h' HEAD` +if [ ! -z "${NEW_COMMIT}" ]; then + NEW_COMMIT_HASH=`git log -1 --pretty=format:'%h' ${NEW_COMMIT}` +fi +OLD_COMMIT_HASH=`git log -1 --pretty=format:'%h' ${OLD_COMMIT}` +if [ $? -ne 0 ]; then + echo "Failed to get commit hash for commit: \"${OLD_COMMIT}\"" + OLD_COMMIT_HASH=`git log -1 --pretty=format:'%h' origin/${OLD_COMMIT}` +fi +if [ "${NEW_COMMIT_HASH}" == "${OLD_COMMIT_HASH}" ]; then + echo "No changes between old commit: \"${OLD_COMMIT}\" and new commit: \"${NEW_COMMIT}\"" + exit 0 +fi + +if [ -z "${NEW_COMMIT}" ]; then + echo "Running compatibility regression test against \"${OLD_COMMIT}\"" compatibility-verifier/checkoutAndBuild.sh -w $WORKING_DIR -o $OLD_COMMIT else - echo "Running compatibility regression test against \"${olderCommit}\" and \"${newerCommit}\"" - compatibility-verifier/checkoutAndBuild.sh -w $WORKING_DIR -o $OLD_COMMIT -n $NEW_OLD_COMMIT + echo "Running compatibility regression test against \"${OLD_COMMIT}\" and \"${NEW_COMMIT}\"" + compatibility-verifier/checkoutAndBuild.sh -w $WORKING_DIR -o $OLD_COMMIT -n $NEW_COMMIT fi compatibility-verifier/compCheck.sh -w $WORKING_DIR -t $TEST_SUITE diff --git a/.github/workflows/scripts/.pinot_quickstart.sh b/.github/workflows/scripts/.pinot_quickstart.sh index f27ecc8dcfd..d78f647c0c9 100755 --- a/.github/workflows/scripts/.pinot_quickstart.sh +++ b/.github/workflows/scripts/.pinot_quickstart.sh @@ -68,9 +68,9 @@ PASS=0 for i in $(seq 1 2) do if [ "$JAVA_VER" -gt 11 ] ; then - mvn clean install -B -DskipTests=true -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=11 + mvn clean install -B -Dmaven.test.skip=true -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=11 else - mvn clean install -B -DskipTests=true -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=${JAVA_VER} + mvn clean install -B -Dmaven.test.skip=true -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=${JAVA_VER} fi if [ $? -eq 0 ]; then PASS=1 @@ -87,6 +87,7 @@ cd "${DIST_BIN_DIR}" # Test standalone pinot. Configure JAVA_OPTS for smaller memory, and don't use System.exit export JAVA_OPTS="-Xms1G -Dlog4j2.configurationFile=conf/log4j2.xml" + bin/pinot-admin.sh StartZookeeper & ZK_PID=$! sleep 10 @@ -119,6 +120,12 @@ if [ $? -ne 0 ]; then exit 1 fi +bin/pinot-admin.sh AddTable -tableConfigFile examples/batch/dimBaseballTeams/dimBaseballTeams_offline_table_config.json -schemaFile examples/batch/dimBaseballTeams/dimBaseballTeams_schema.json -exec +if [ $? -ne 0 ]; then + echo 'Failed to create table dimBaseballTeams.' + exit 1 +fi + # Ingest Data d=`pwd` INSERT_INTO_RES=`curl -X POST --header 'Content-Type: application/json' -d "{\"sql\":\"INSERT INTO baseballStats FROM FILE '${d}/examples/batch/baseballStats/rawdata'\",\"trace\":false}" http://localhost:8099/query/sql` @@ -128,8 +135,17 @@ if [ $? -ne 0 ]; then fi PASS=0 + +INSERT_INTO_RES=`curl -X POST --header 'Content-Type: application/json' -d "{\"sql\":\"INSERT INTO dimBaseballTeams FROM FILE '${d}/examples/batch/dimBaseballTeams/rawdata'\",\"trace\":false}" http://localhost:8099/query/sql` +if [ $? -ne 0 ]; then + echo 'Failed to ingest data for table baseballStats.' + exit 1 +fi +PASS=0 + # Wait for 10 Seconds for table to be set up, then query the total count. sleep 10 +# Validate V1 query count(*) result for i in $(seq 1 150) do QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"select count(*) from baseballStats limit 1","trace":false}' http://localhost:8099/query/sql` @@ -143,6 +159,38 @@ do sleep 2 done +PASS=0 + +# Validate V2 query count(*) result +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true; select count(*) from baseballStats limit 1","trace":false}' http://localhost:8099/query/sql` + if [ $? -eq 0 ]; then + COUNT_STAR_RES=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${COUNT_STAR_RES}" =~ ^[0-9]+$ ]] && [ "${COUNT_STAR_RES}" -eq 97889 ]; then + PASS=1 + break + fi + fi + sleep 2 +done + +PASS=0 + +# Validate V2 join query results +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true;SELECT a.playerName, a.teamID, b.teamName FROM baseballStats_OFFLINE AS a JOIN dimBaseballTeams_OFFLINE AS b ON a.teamID = b.teamID LIMIT 10","trace":false}' http://localhost:8099/query/sql` + if [ $? -eq 0 ]; then + RES_0=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${RES_0}" = "\"David Allan\"" ]]; then + PASS=1 + break + fi + fi + sleep 2 +done + cleanup "${PINOT_PID}" cleanup "${ZK_PID}" if [ "${PASS}" -eq 0 ]; then @@ -150,8 +198,8 @@ if [ "${PASS}" -eq 0 ]; then exit 1 fi -# Test quick-start-batch -bin/quick-start-batch.sh & +# Test quick-start-multi-stage +bin/pinot-admin.sh QuickStart -type MULTI_STAGE & PID=$! # Print the JVM settings @@ -174,6 +222,38 @@ do sleep 2 done +PASS=0 + +# Validate V2 query count(*) result +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true; select count(*) from baseballStats limit 1","trace":false}' http://localhost:8000/query/sql` + if [ $? -eq 0 ]; then + COUNT_STAR_RES=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${COUNT_STAR_RES}" =~ ^[0-9]+$ ]] && [ "${COUNT_STAR_RES}" -eq 97889 ]; then + PASS=1 + break + fi + fi + sleep 2 +done + +PASS=0 + +# Validate V2 join query results +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true;SELECT a.playerName, a.teamID, b.teamName FROM baseballStats_OFFLINE AS a JOIN dimBaseballTeams_OFFLINE AS b ON a.teamID = b.teamID LIMIT 10","trace":false}' http://localhost:8000/query/sql` + if [ $? -eq 0 ]; then + RES_0=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ ${RES_0} = "\"David Allan\"" ]]; then + PASS=1 + break + fi + fi + sleep 2 +done + cleanup "${PID}" if [ "${PASS}" -eq 0 ]; then echo 'Batch Quickstart failed: Cannot get correct result for count star query.' diff --git a/.github/workflows/scripts/.pinot_test.sh b/.github/workflows/scripts/.pinot_test.sh index d88414583c6..1608b6f7e99 100755 --- a/.github/workflows/scripts/.pinot_test.sh +++ b/.github/workflows/scripts/.pinot_test.sh @@ -27,18 +27,21 @@ netstat -i if [ "$RUN_INTEGRATION_TESTS" != false ]; then # Integration Tests - mvn clean install -DskipTests -am -B -pl 'pinot-integration-tests' -T 16 || exit 1 + mvn clean install -DskipTests -Dcheckstyle.skip -Dspotless.apply.skip -Dlicense.skip=true -am -B \ + -pl 'pinot-integration-tests' -T 16 || exit 1 if [ "$RUN_TEST_SET" == "1" ]; then mvn test -am -B \ -pl 'pinot-integration-tests' \ -Dtest='C*Test,L*Test,M*Test,R*Test,S*Test' \ - -P github-actions,integration-tests-only && exit 0 || exit 1 + -P github-actions,integration-tests-only \ + -Dcheckstyle.skip -Dspotless.apply.skip -Dlicense.skip=true && exit 0 || exit 1 fi if [ "$RUN_TEST_SET" == "2" ]; then mvn test -am -B \ -pl 'pinot-integration-tests' \ -Dtest='!C*Test,!L*Test,!M*Test,!R*Test,!S*Test' \ - -P github-actions,integration-tests-only && exit 0 || exit 1 + -P github-actions,integration-tests-only \ + -Dcheckstyle.skip -Dspotless.apply.skip -Dlicense.skip=true && exit 0 || exit 1 fi else # Unit Tests @@ -60,10 +63,11 @@ else -pl ':pinot-csv' \ -pl ':pinot-json' \ -pl ':pinot-segment-uploader-default' \ - -P github-actions,no-integration-tests && exit 0 || exit 1 + -P github-actions,no-integration-tests \ + -Dcheckstyle.skip -Dspotless.apply.skip -Dlicense.skip=true && exit 0 || exit 1 fi if [ "$RUN_TEST_SET" == "2" ]; then - mvn clean install -DskipTests -T 16 || exit 1 + mvn clean install -DskipTests -Dcheckstyle.skip -Dspotless.apply.skip -Dlicense.skip=true -T 16 || exit 1 mvn test -am -B \ -pl '!pinot-spi' \ -pl '!pinot-segment-spi' \ @@ -79,7 +83,9 @@ else -pl '!:pinot-csv' \ -pl '!:pinot-json' \ -pl '!:pinot-segment-uploader-default' \ - -P github-actions,no-integration-tests && exit 0 || exit 1 + -P github-actions,no-integration-tests \ + -Dspotless.apply.skip -Dcheckstyle.skip -Dspotless.apply.skip -Dlicense.skip=true \ + && exit 0 || exit 1 fi fi diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-0.9/src/main/resources/META-INF/services/org.apache.pinot.spi.stream.StreamConsumerFactory b/.github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh old mode 100644 new mode 100755 similarity index 68% rename from pinot-plugins/pinot-stream-ingestion/pinot-kafka-0.9/src/main/resources/META-INF/services/org.apache.pinot.spi.stream.StreamConsumerFactory rename to .github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh index 0bbaa018851..b13b14d786b --- a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-0.9/src/main/resources/META-INF/services/org.apache.pinot.spi.stream.StreamConsumerFactory +++ b/.github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh @@ -1,3 +1,4 @@ +#!/bin/bash -x # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -16,4 +17,21 @@ # specific language governing permissions and limitations # under the License. # -org.apache.pinot.plugin.stream.kafka09.KafkaConsumerFactory \ No newline at end of file + +if [ -z "${BUILD_PLATFORM}" ]; then + exit 1 +fi + +if [ -z "${BASE_IMAGE_TYPE}" ]; then + exit 1 +fi + +cd docker/images/pinot-base/pinot-base-${BASE_IMAGE_TYPE} + +docker buildx build \ + --no-cache \ + --platform=${BUILD_PLATFORM} \ + --file ${OPEN_JDK_DIST}.dockerfile \ + --tag apachepinot/pinot-base-${BASE_IMAGE_TYPE}:${TAG} \ + --push \ + . diff --git a/.github/workflows/scripts/docker/.pinot_build_info_gen.sh b/.github/workflows/scripts/docker/.pinot_build_info_gen.sh new file mode 100755 index 00000000000..bca2a0ef8e3 --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_build_info_gen.sh @@ -0,0 +1,41 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +if [ -z "${PINOT_GIT_URL}" ]; then + PINOT_GIT_URL="https://github.com/apache/pinot.git" +fi +if [ -z "${PINOT_BRANCH}" ]; then + PINOT_BRANCH="master" +fi + +# Get pinot commit id +ROOT_DIR=$(pwd) +rm -rf /tmp/pinot +git clone -b ${PINOT_BRANCH} --single-branch ${PINOT_GIT_URL} /tmp/pinot +cd /tmp/pinot +COMMIT_ID=$(git rev-parse --short HEAD) +VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +rm -rf /tmp/pinot +DATE=$(date +%Y%m%d) + +if [ -z "${TAGS}" ]; then + TAGS="${VERSION}-${COMMIT_ID}-${DATE},latest" +fi +echo "commit-id=${COMMIT_ID}" >>"$GITHUB_OUTPUT" +echo "tags=${TAGS}" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh b/.github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh new file mode 100755 index 00000000000..3a2995facfd --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh @@ -0,0 +1,62 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "${DOCKER_IMAGE_NAME}" ]; then + DOCKER_IMAGE_NAME="apachepinot/pinot" +fi + +tags=() +declare -a tags=($(echo ${TAGS} | tr "," " ")) + +platforms=() +declare -a platforms=($(echo ${BUILD_PLATFORM} | tr "," " ")) + +baseImageTags=() +declare -a baseImageTags=($(echo ${BASE_IMAGE_TAGS} | tr "," " ")) + +for tag in "${tags[@]}"; do + for baseImageTag in "${baseImageTags[@]}"; do + DOCKER_AMEND_TAGS_CMD="" + for platform in "${platforms[@]}"; do + platformTag=${platform/\//-} + DOCKER_AMEND_TAGS_CMD+=" --amend ${DOCKER_IMAGE_NAME}:${tag}-${baseImageTag}-${platformTag} " + done + + echo "Creating manifest for tag: ${tag}-${baseImageTag}" + docker manifest create \ + ${DOCKER_IMAGE_NAME}:${tag}-${baseImageTag} \ + ${DOCKER_AMEND_TAGS_CMD} + + docker manifest push \ + ${DOCKER_IMAGE_NAME}:${tag}-${baseImageTag} + + if [ "${baseImageTag}" == "11-amazoncorretto" ]; then + if [ "${tag}" == "latest" ]; then + echo "Creating manifest for tag: latest" + docker manifest create \ + ${DOCKER_IMAGE_NAME}:latest \ + ${DOCKER_AMEND_TAGS_CMD} + + docker manifest push \ + ${DOCKER_IMAGE_NAME}:latest + fi + fi + done +done diff --git a/.github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh b/.github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh new file mode 100755 index 00000000000..c2f8b288a9e --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh @@ -0,0 +1,66 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "${DOCKER_IMAGE_NAME}" ]; then + DOCKER_IMAGE_NAME="apachepinot/pinot" +fi +if [ -z "${PINOT_GIT_URL}" ]; then + PINOT_GIT_URL="https://github.com/apache/pinot.git" +fi +if [ -z "${JDK_VERSION}" ]; then + JDK_VERSION="11" +fi + +tags=() +declare -a tags=($(echo ${TAGS} | tr "," " ")) + +cd ${DOCKER_FILE_BASE_DIR} +platformTag=${BUILD_PLATFORM/\//-} +DOCKER_BUILD_TAGS="" +for tag in "${tags[@]}"; do + DOCKER_BUILD_TAGS+=" --tag ${DOCKER_IMAGE_NAME}:${tag}-${BASE_IMAGE_TAG}-${platformTag} " + + if [ "${BASE_IMAGE_TAG}" == "11-amazoncorretto" ]; then + if [ "${tag}" == "latest" ]; then + DOCKER_BUILD_TAGS+=" --tag ${DOCKER_IMAGE_NAME}:latest-${platformTag} " + fi + fi +done + +echo "Building docker image for platform: ${BUILD_PLATFORM} with tags: ${DOCKER_BUILD_TAGS}" +docker build \ + --no-cache \ + --platform ${BUILD_PLATFORM} \ + --file Dockerfile \ + --build-arg PINOT_GIT_URL=${PINOT_GIT_URL} \ + --build-arg PINOT_BRANCH=${PINOT_BRANCH} \ + --build-arg JDK_VERSION=${JDK_VERSION} \ + ${DOCKER_BUILD_TAGS} \ + . + +for tag in "${tags[@]}"; do + docker push ${DOCKER_IMAGE_NAME}:${tag}-${BASE_IMAGE_TAG}-${platformTag} + + if [ "${BASE_IMAGE_TAG}" == "11-amazoncorretto" ]; then + if [ "${tag}" == "latest" ]; then + docker push ${DOCKER_IMAGE_NAME}:${tag}-${platformTag} + fi + fi +done diff --git a/LICENSE-binary b/LICENSE-binary index 41b098b628f..e22858ca850 100644 --- a/LICENSE-binary +++ b/LICENSE-binary @@ -270,7 +270,7 @@ com.uber:h3:4.0.0 com.yahoo.datasketches:memory:0.8.3 com.yahoo.datasketches:sketches-core:0.8.3 com.yammer.metrics:metrics-core:2.2.0 -com.yscope.clp:clp-ffi:0.2.1 +com.yscope.clp:clp-ffi:0.4.3 com.zaxxer:HikariCP-java7:2.4.13 commons-cli:commons-cli:1.2 commons-codec:commons-codec:1.15 @@ -459,7 +459,7 @@ org.scala-lang.modules:scala-collection-compat_2.12:2.3.0 org.scala-lang.modules:scala-java8-compat_2.12:0.9.1 org.scala-lang.modules:scala-xml_2.12:1.3.0 org.typelevel:macro-compat_2.12:1.1.1 -org.webjars:swagger-ui:3.23.11 +org.webjars:swagger-ui:5.1.0 org.wildfly.openssl:wildfly-openssl:1.0.7.Final org.xerial.larray:larray-buffer:0.4.1 org.xerial.larray:larray-mmap:0.4.1 diff --git a/compatibility-verifier/checkoutAndBuild.sh b/compatibility-verifier/checkoutAndBuild.sh index 702e01b1f4a..0bbb531740f 100755 --- a/compatibility-verifier/checkoutAndBuild.sh +++ b/compatibility-verifier/checkoutAndBuild.sh @@ -22,6 +22,8 @@ cmdName=`basename $0` cmdDir=`dirname $0` source ${cmdDir}/utils.inc MVN_CACHE_DIR="mvn-compat-cache" +df -h +du -hd2 /home/runner # get usage of the script function usage() { @@ -64,7 +66,7 @@ function checkOut() { # compatibility tester # It uses a different version for each build, since we build trees in parallel. # Building the same version on all trees in parallel causes unstable builds. -# Using indpendent buildIds will cause maven cache to fill up, so we use a +# Using independent buildIds will cause maven cache to fill up, so we use a # dedicated maven cache for these builds, and remove the cache after build is # completed. # If buildId is less than 0, then the mvn version is not changed in pom files. @@ -72,10 +74,13 @@ function build() { local outFile=$1 local buildTests=$2 local buildId=$3 + local buildCompatibilityVerifier=$4 local repoOption="" - local versionOption="-Djdk.version=8" + local versionOption="-Djdk.version=11" + local maxRetry=5 mkdir -p ${MVN_CACHE_DIR} + mkdir -p ${mvnCache} if [ ${buildId} -gt 0 ]; then # Build it in a different env under different version so that maven cache does @@ -85,15 +90,40 @@ function build() { mvn versions:commit -q -B 1>${outFile} 2>&1 repoOption="-Dmaven.repo.local=${mvnCache}/${buildId}" fi - - mvn install package -DskipTests -Pbin-dist ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>${outFile} 2>&1 - if [ $? -ne 0 ]; then exit 1; fi - mvn -pl pinot-tools package -DskipTests ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>>${outFile} 2>&1 - if [ $? -ne 0 ]; then exit 1; fi + buildComponents=":pinot-tools" + if [ $buildCompatibilityVerifier -gt 0 ]; then + buildComponents=":pinot-tools,:pinot-compatibility-verifier" + fi + for i in $(seq 1 $maxRetry); do + mvn clean package -am -pl ${buildComponents} -DskipTests -T1C ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>${outFile} 2>&1 + if [ $? -eq 0 ]; then break; fi + if [ $i -eq $maxRetry ]; then exit 1; fi + echo "" + echo "Build failed, see last 1000 lines of output below." + tail -1000 ${outFile} + echo "Retrying after 30 seconds..." + sleep 30 + done if [ $buildTests -eq 1 ]; then - mvn -pl pinot-integration-tests package -DskipTests ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>>${outFile} 2>&1 - if [ $? -ne 0 ]; then exit 1; fi + for i in $(seq 1 $maxRetry); do + mvn -pl :pinot-integration-tests package -DskipTests -T1C ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>>${outFile} 2>&1 + if [ $? -eq 0 ]; then break; fi + if [ $i -eq $maxRetry ]; then exit 1; fi + echo "" + echo "Build failed, see last 500 lines of output below." + tail -500 ${outFile} + echo "Retrying after 30 seconds..." + sleep 30 + done fi + # DELETE the mvn cache + du -hd1 ${mvnCache} + df -h + rm -rf ${mvnCache} + du -hd2 /home/runner + df -h + rm -rf /home/runner/.m2/repository + df -h } # @@ -190,29 +220,46 @@ fi echo "Checking out old version at commit \"${olderCommit}\"" checkOut "$olderCommit" "$oldTargetDir" -# Start builds in parallel. +exitStatus=0 + +# Start builds in sequential. # First build the current tree. We need it so that we can -# run the compatibiity tester. +# run the compatibility tester. echo Starting build for compat checker at ${cmdDir}, buildId none. -(cd ${cmdDir}/..; build ${curBuildOutFile} 1 -1) & +(cd ${cmdDir}/..; build ${curBuildOutFile} 1 -1 1) & curBuildPid=$! +echo Awaiting build complete for compat checker +while true ; do + printf "." + ps -p ${curBuildPid} 1>/dev/null 2>&1 + if [ $? -ne 0 ]; then + printf "\n" + wait ${curBuildPid} + curBuildStatus=$? + break + fi + sleep 5 +done + +if [ ${curBuildStatus} -eq 0 ]; then + echo Compat checker build completed successfully +else + echo Compat checker build failed. See ${curBuildOutFile} + echo ======== Build output ======== + cat ${curBuildOutFile} + echo ==== End Build output ======== + exitStatus=1 + /bin/rm -r ${mvnCache} + exit ${exitStatus} +fi + # The old commit has been cloned in oldTargetDir, build it. buildId=$(date +%s) echo Starting build for old version at ${oldTargetDir} buildId ${buildId} -(cd ${oldTargetDir}; build ${oldBuildOutFile} 0 ${buildId}) & +(cd ${oldTargetDir}; build ${oldBuildOutFile} 0 ${buildId} 0) & oldBuildPid=$! -# In case the user specified the current tree as newer commit, then -# We don't need to build newer commit tree (we have already built the -# current tree above and linked newTargetDir). Otherwise, build the newTargetDir -if [ ${buildNewTarget} -eq 1 ]; then - buildId=$((buildId+1)) - echo Starting build for new version at ${newTargetDir} buildId ${buildId} - (cd ${newTargetDir}; build ${newBuildOutFile} 0 ${buildId}) & - newBuildPid=$! -fi - # We may have potentially started three builds above (at least two). # Wait for each of them to complete. echo Awaiting build complete for old commit @@ -228,18 +275,27 @@ while true ; do sleep 5 done -echo Awaiting build complete for compat checker -while true ; do - printf "." - ps -p ${curBuildPid} 1>/dev/null 2>&1 - if [ $? -ne 0 ]; then - printf "\n" - wait ${curBuildPid} - curBuildStatus=$? - break - fi - sleep 5 -done +if [ ${oldBuildStatus} -eq 0 ]; then + echo Old version build completed successfully +else + echo Old version build failed. See ${oldBuildOutFile} + echo ======== Build output ======== + cat ${oldBuildOutFile} + echo ==== End Build output ======== + exitStatus=1 + /bin/rm -r ${mvnCache} + exit ${exitStatus} +fi + +# In case the user specified the current tree as newer commit, then +# We don't need to build newer commit tree (we have already built the +# current tree above and linked newTargetDir). Otherwise, build the newTargetDir +if [ ${buildNewTarget} -eq 1 ]; then + buildId=$((buildId+1)) + echo Starting build for new version at ${newTargetDir} buildId ${buildId} + (cd ${newTargetDir}; build ${newBuildOutFile} 0 ${buildId} 0) & + newBuildPid=$! +fi if [ ${buildNewTarget} -eq 1 ]; then echo Awaiting build complete for new commit @@ -256,28 +312,6 @@ if [ ${buildNewTarget} -eq 1 ]; then done fi -exitStatus=0 - -if [ ${oldBuildStatus} -eq 0 ]; then - echo Old version build completed successfully -else - echo Old version build failed. See ${oldBuildOutFile} - echo ======== Build output ======== - cat ${oldBuildOutFile} - echo ==== End Build output ======== - exitStatus=1 -fi - -if [ ${curBuildStatus} -eq 0 ]; then - echo Compat checker build completed successfully -else - echo Compat checker build failed. See ${curBuildOutFile} - echo ======== Build output ======== - cat ${curBuildOutFile} - echo ==== End Build output ======== - exitStatus=1 -fi - if [ ${buildNewTarget} -eq 1 ]; then if [ ${newBuildStatus} -eq 0 ]; then echo New version build completed successfully @@ -287,6 +321,7 @@ if [ ${buildNewTarget} -eq 1 ]; then cat ${newBuildOutFile} echo ==== End Build output ======== exitStatus=1 + exit ${exitStatus} fi fi diff --git a/config/suppressions.xml b/config/suppressions.xml index 56b06ed6b7a..10d796b2333 100644 --- a/config/suppressions.xml +++ b/config/suppressions.xml @@ -45,4 +45,7 @@ + + + diff --git a/contrib/pinot-druid-benchmark/pom.xml b/contrib/pinot-druid-benchmark/pom.xml index 050e312a1b2..0e4589761e0 100644 --- a/contrib/pinot-druid-benchmark/pom.xml +++ b/contrib/pinot-druid-benchmark/pom.xml @@ -33,7 +33,7 @@ org.apache.httpcomponents httpclient - 4.5.1 + 4.5.13 diff --git a/contrib/pinot-fmpp-maven-plugin/pom.xml b/contrib/pinot-fmpp-maven-plugin/pom.xml index b7ccad0d855..7ed1a2dab60 100644 --- a/contrib/pinot-fmpp-maven-plugin/pom.xml +++ b/contrib/pinot-fmpp-maven-plugin/pom.xml @@ -36,9 +36,9 @@ maven-plugin ${basedir}/../.. - 3.3.3 + 3.8.2 0.9.16 - 2.3.28 + 2.3.30 @@ -55,17 +55,33 @@ org.codehaus.plexus plexus-utils + + org.eclipse.sisu + org.eclipse.sisu.plexus + org.apache.maven maven-plugin-api ${maven.version} + + + org.eclipse.sisu + org.eclipse.sisu.plexus + + net.sourceforge.fmpp fmpp ${fmpp.version} + + + org.freemarker + freemarker + + org.freemarker diff --git a/docker/images/pinot-base/README.md b/docker/images/pinot-base/README.md index d7f5e929174..92430de06c6 100644 --- a/docker/images/pinot-base/README.md +++ b/docker/images/pinot-base/README.md @@ -20,40 +20,35 @@ --> # docker-pinot-base + This is the base docker image to build [Apache Pinot](https://github.com/apache/pinot). -## How to build a docker image +## Build and publish the docker image -Arguments: +Here is +the [Github Action task](https://github.com/apachepinot/pinot-fork/actions/workflows/build-pinot-docker-base-image.yml) +to build and publish pinot base docker images. -`JAVA_VERSION`: The Java Build and Runtime image version. Default is `11` +This task can be triggered manually to build the cross platform(amd64 and arm64v8) base image. -`OPENJDK_IMAGE`: Base image to use for Pinot build and runtime, e.g. `arm64v8/openjdk`. Default is `openjdk`. +The build shell is: -Usage: -```SHELL -docker build -t apachepinot/pinot-base-build:openjdk11 --no-cache --network=host --build-arg JAVA_VERSION=11 -f pinot-base-build/Dockerfile . -``` +For Amazon Corretto 11: ```SHELL -docker build -t apachepinot/pinot-base-runtime:openjdk11 --no-cache --network=host --build-arg JAVA_VERSION=11 -f pinot-base-runtime/Dockerfile . +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-build/amazoncorretto.dockerfile --tag apachepinot/pinot-base-build:11-amazoncorretto --push . ``` -Note that if you are not on arm64 machine, you can still build the image by turning on the experimental feature of docker, and add `--platform linux/arm64` into the `docker build ...` script, e.g. -```SHELL -docker build -t apachepinot/pinot-base-build:openjdk11-arm64v8 --platform linux/arm64 --no-cache --network=host --build-arg JAVA_VERSION=11 --build-arg OPENJDK_IMAGE=arm64v8/openjdk -f pinot-base-build/Dockerfile . -``` ```SHELL -docker build -t apachepinot/pinot-base-runtime:openjdk11-arm64v8 --platform linux/arm64 --no-cache --network=host --build-arg JAVA_VERSION=11 --build-arg OPENJDK_IMAGE=arm64v8/openjdk -f pinot-base-runtime/Dockerfile . +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-runtime/amazoncorretto.dockerfile --tag apachepinot/pinot-base-runtime:11-amazoncorretto --push . ``` -## Publish the docker image +For MS OpenJDK, the build shell is: -Here is the [Github Action task](https://github.com/apachepinot/pinot-fork/actions/workflows/build-pinot-docker-base-image.yml) to build and publish pinot base docker images. - -This task can be triggered manually to build the cross platform(amd64 and arm64v8) base image. +```SHELL +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-build/amazoncorretto.dockerfile --tag apachepinot/pinot-base-build:11-ms-openjdk --push . +``` -The build shell is: ```SHELL -docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file Dockerfile --tag apachepinot/pinot-base-build:openjdk11 --push . -``` \ No newline at end of file +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-runtime/amazoncorretto.dockerfile --tag apachepinot/pinot-base-runtime:11-ms-openjdk --push . +``` diff --git a/docker/images/pinot-base/pinot-base-build/amazoncorretto.dockerfile b/docker/images/pinot-base/pinot-base-build/amazoncorretto.dockerfile new file mode 100644 index 00000000000..674f87d6cbb --- /dev/null +++ b/docker/images/pinot-base/pinot-base-build/amazoncorretto.dockerfile @@ -0,0 +1,64 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +FROM debian:buster-slim + +ARG version=11.0.18.10-1 +# In addition to installing the Amazon corretto, we also install +# fontconfig. The folks who manage the docker hub's +# official image library have found that font management +# is a common usecase, and painpoint, and have +# recommended that Java images include font support. +# +# See: +# https://github.com/docker-library/official-images/blob/master/test/tests/java-uimanager-font/container.java + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN set -eux \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + curl ca-certificates gnupg software-properties-common fontconfig java-common vim wget git automake bison flex g++ libboost-all-dev libevent-dev libssl-dev libtool make pkg-config\ + && curl -fL https://apt.corretto.aws/corretto.key | apt-key add - \ + && add-apt-repository 'deb https://apt.corretto.aws stable main' \ + && mkdir -p /usr/share/man/man1 || true \ + && apt-get update \ + && apt-get install -y java-11-amazon-corretto-jdk=1:$version \ + && rm -rf /var/lib/apt/lists/* + +ENV LANG C.UTF-8 +ENV JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto + +# install maven +RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ + && wget https://dlcdn.apache.org/maven/maven-3/3.9.2/binaries/apache-maven-3.9.2-bin.tar.gz -P /tmp \ + && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ + && rm -f /tmp/apache-maven-*.tar.gz \ + && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn +ENV MAVEN_HOME /usr/share/maven +ENV MAVEN_CONFIG /opt/.m2 + +# install thrift +RUN wget http://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ + tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ + base_dir=`pwd` && \ + cd /tmp/thrift-0.12.0 && \ + ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ + make install + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-build/Dockerfile b/docker/images/pinot-base/pinot-base-build/ms-openjdk.dockerfile similarity index 65% rename from docker/images/pinot-base/pinot-base-build/Dockerfile rename to docker/images/pinot-base/pinot-base-build/ms-openjdk.dockerfile index 5b93684c563..7fb80886c7a 100644 --- a/docker/images/pinot-base/pinot-base-build/Dockerfile +++ b/docker/images/pinot-base/pinot-base-build/ms-openjdk.dockerfile @@ -17,20 +17,20 @@ # under the License. # ARG JAVA_VERSION=11 -ARG OPENJDK_IMAGE=openjdk -FROM ${OPENJDK_IMAGE}:${JAVA_VERSION} AS pinot_build_env +ARG JDK_IMAGE=mcr.microsoft.com/openjdk/jdk +FROM ${JDK_IMAGE}:${JAVA_VERSION}-ubuntu AS pinot_build_env LABEL MAINTAINER=dev@pinot.apache.org # extra dependency for running launcher RUN apt-get update && \ - apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev \ - libssl-dev libtool make pkg-config && \ - rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev \ + libssl-dev libtool make pkg-config && \ + rm -rf /var/lib/apt/lists/* # install maven RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ - && wget https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz -P /tmp \ + && wget https://dlcdn.apache.org/maven/maven-3/3.9.2/binaries/apache-maven-3.9.2-bin.tar.gz -P /tmp \ && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ && rm -f /tmp/apache-maven-*.tar.gz \ && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn @@ -39,10 +39,10 @@ ENV MAVEN_CONFIG /opt/.m2 # install thrift RUN wget http://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ - tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ - base_dir=`pwd` && \ - cd /tmp/thrift-0.12.0 && \ - ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ - make install + tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ + base_dir=`pwd` && \ + cd /tmp/thrift-0.12.0 && \ + ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ + make install -CMD ["-help"] +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-build/openjdk.dockerfile b/docker/images/pinot-base/pinot-base-build/openjdk.dockerfile new file mode 100644 index 00000000000..0abef1191ff --- /dev/null +++ b/docker/images/pinot-base/pinot-base-build/openjdk.dockerfile @@ -0,0 +1,52 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=21 +ARG JDK_IMAGE=openjdk +# At 2023-06-14, slim is the only openjdk flavour without medium, high or critical vulns +FROM ${JDK_IMAGE}:${JAVA_VERSION}-jdk-slim + +LABEL MAINTAINER=dev@pinot.apache.org + +ENV MAVEN_HOME /usr/share/maven +ENV MAVEN_CONFIG /root/.m2 + +# extra dependency for running launcher +RUN apt-get update && \ + apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev \ + libssl-dev libtool make pkg-config && \ + rm -rf /var/lib/apt/lists/* + +# install maven +RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ + && wget https://dlcdn.apache.org/maven/maven-3/3.9.2/binaries/apache-maven-3.9.2-bin.tar.gz -P /tmp \ + && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ + && rm -f /tmp/apache-maven-*.tar.gz \ + && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn \ + && mvn help:evaluate -Dexpression=settings.localRepository + + +# install thrift +RUN wget http://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ + tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ + base_dir=`pwd` && \ + cd /tmp/thrift-0.12.0 && \ + ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ + make install + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/amazoncorretto.dockerfile b/docker/images/pinot-base/pinot-base-runtime/amazoncorretto.dockerfile new file mode 100644 index 00000000000..bb43e2131c2 --- /dev/null +++ b/docker/images/pinot-base/pinot-base-runtime/amazoncorretto.dockerfile @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=11 +ARG JDK_IMAGE=amazoncorretto + +FROM ${JDK_IMAGE}:${JAVA_VERSION}-al2-jdk + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN yum update -y && \ + yum groupinstall 'Development Tools' -y && \ + yum install -y procps vim less wget curl git python sysstat perf libtasn1 && \ + yum clean all + +RUN case `uname -m` in \ + x86_64) arch=x64; ;; \ + aarch64) arch=arm64; ;; \ + *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ + esac \ + && mkdir -p /usr/local/lib/async-profiler \ + && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ + && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/Dockerfile b/docker/images/pinot-base/pinot-base-runtime/ms-openjdk.dockerfile similarity index 69% rename from docker/images/pinot-base/pinot-base-runtime/Dockerfile rename to docker/images/pinot-base/pinot-base-runtime/ms-openjdk.dockerfile index 7a8ade3016a..364da20d6b4 100644 --- a/docker/images/pinot-base/pinot-base-runtime/Dockerfile +++ b/docker/images/pinot-base/pinot-base-runtime/ms-openjdk.dockerfile @@ -16,24 +16,25 @@ # specific language governing permissions and limitations # under the License. # + ARG JAVA_VERSION=11 -ARG OPENJDK_IMAGE=openjdk +ARG JDK_IMAGE=mcr.microsoft.com/openjdk/jdk -FROM ${OPENJDK_IMAGE}:${JAVA_VERSION}-jdk-slim +FROM ${JDK_IMAGE}:${JAVA_VERSION}-ubuntu LABEL MAINTAINER=dev@pinot.apache.org RUN apt-get update && \ - apt-get install -y --no-install-recommends vim less wget curl git python sysstat procps linux-perf openjdk-11-dbg && \ - rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends vim less wget curl git python sysstat procps linux-tools-generic libtasn1-6 && \ + rm -rf /var/lib/apt/lists/* RUN case `uname -m` in \ - x86_64) arch=x64; ;; \ - aarch64) arch=arm64; ;; \ - *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ + x86_64) arch=x64; ;; \ + aarch64) arch=arm64; ;; \ + *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ esac \ && mkdir -p /usr/local/lib/async-profiler \ - && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.5.1/async-profiler-2.5.1-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ + && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/openjdk.dockerfile b/docker/images/pinot-base/pinot-base-runtime/openjdk.dockerfile new file mode 100644 index 00000000000..e59787749fd --- /dev/null +++ b/docker/images/pinot-base/pinot-base-runtime/openjdk.dockerfile @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=21 +ARG JDK_IMAGE=openjdk + +FROM ${JDK_IMAGE}:${JAVA_VERSION}-jdk-slim + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN apt-get update && \ + apt-get install -y --no-install-recommends vim less wget curl git python-is-python3 sysstat procps linux-perf libtasn1-6 && \ + rm -rf /var/lib/apt/lists/* + +RUN case `uname -m` in \ + x86_64) arch=x64; ;; \ + aarch64) arch=arm64; ;; \ + *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ + esac \ + && mkdir -p /usr/local/lib/async-profiler \ + && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ + && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler + +CMD ["bash"] diff --git a/docker/images/pinot-superset/requirements-db.txt b/docker/images/pinot-superset/requirements-db.txt index bb250db8c99..caf7d4a3ff7 100644 --- a/docker/images/pinot-superset/requirements-db.txt +++ b/docker/images/pinot-superset/requirements-db.txt @@ -17,4 +17,4 @@ # under the License. # pinotdb>=0.4.5 -redis==3.4.1 +redis==4.5.4 diff --git a/docker/images/pinot/Dockerfile b/docker/images/pinot/Dockerfile index 499060ed7c7..3378b6f6454 100644 --- a/docker/images/pinot/Dockerfile +++ b/docker/images/pinot/Dockerfile @@ -16,16 +16,20 @@ # specific language governing permissions and limitations # under the License. # -ARG PINOT_BASE_IMAGE_TAG=openjdk11 +ARG PINOT_BASE_IMAGE_TAG=11-amazoncorretto FROM apachepinot/pinot-base-build:${PINOT_BASE_IMAGE_TAG} AS pinot_build_env LABEL MAINTAINER=dev@pinot.apache.org ARG PINOT_BRANCH=master -ARG KAFKA_VERSION=2.0 ARG JDK_VERSION=11 ARG PINOT_GIT_URL="https://github.com/apache/pinot.git" -RUN echo "Trying to build Pinot from [ ${PINOT_GIT_URL} ] on branch [ ${PINOT_BRANCH} ] with Kafka version [ ${KAFKA_VERSION} ]" +ARG CI=true + +RUN echo "Build Pinot based on image: apachepinot/pinot-base-build:${PINOT_BASE_IMAGE_TAG}" +RUN echo "Current build system CPU arch is [ $(uname -m) ]" + +RUN echo "Trying to build Pinot from [ ${PINOT_GIT_URL} ] on branch [ ${PINOT_BRANCH} ] and CI [ ${CI} ]" ENV PINOT_HOME=/opt/pinot ENV PINOT_BUILD_DIR=/opt/pinot-build ENV MAVEN_HOME /usr/share/maven @@ -35,6 +39,7 @@ RUN git clone ${PINOT_GIT_URL} ${PINOT_BUILD_DIR} && \ cd ${PINOT_BUILD_DIR} && \ git checkout ${PINOT_BRANCH} && \ mvn install package -DskipTests -Pbin-dist -Pbuild-shaded-jar -Djdk.version=${JDK_VERSION} -T1C && \ + rm -rf /root/.m2 && \ mkdir -p ${PINOT_HOME}/configs && \ mkdir -p ${PINOT_HOME}/data && \ cp -r build/* ${PINOT_HOME}/. && \ @@ -54,9 +59,8 @@ COPY bin ${PINOT_HOME}/bin COPY etc ${PINOT_HOME}/etc COPY examples ${PINOT_HOME}/examples -RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.12.0.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.12.0/jmx_prometheus_javaagent-0.12.0.jar -RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.16.1.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.16.1/jmx_prometheus_javaagent-0.16.1.jar && \ - ln -s ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.16.1.jar ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent.jar +RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.18.0.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.18.0/jmx_prometheus_javaagent-0.18.0.jar && \ + ln -s ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.18.0.jar ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent.jar # expose ports for controller/broker/server/admin EXPOSE 9000 8099 8098 8097 8096 diff --git a/docker/images/pinot/etc/conf/pinot-minion-log4j2.xml b/docker/images/pinot/etc/conf/pinot-minion-log4j2.xml new file mode 100644 index 00000000000..6907f5e9f9d --- /dev/null +++ b/docker/images/pinot/etc/conf/pinot-minion-log4j2.xml @@ -0,0 +1,80 @@ + + + + + logs/pinotMinion + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] [%t] %m%n + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] [%t] %m%n + + + + + + + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml index ea9d5e3a859..91e72bc39b5 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml @@ -1,71 +1,71 @@ rules: -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_authorization_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_documentsScanned_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_entriesScannedInFilter_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_entriesScannedPostFilter_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_freshnessLagMs_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queries_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryExecution_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryRouting_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_reduce_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_requestCompilation_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_scatterGather_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_totalServerResponseSize_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_groupBySize_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_noServingHostForSegment_$3" cache: true labels: @@ -98,48 +98,48 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_routingTableUpdateTime_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_brokerResponsesWithPartialServersResponded_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_noServerFoundExceptions_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_brokerResponsesWithProcessingExceptions_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_brokerResponsesWithNumGroupsLimitReached_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryQuotaExceeded_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryTotalTimeMs_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_serverMissingForRouting_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_deserialization_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_requestConnectionWait_$2" cache: true labels: diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml index 5dafc3910c9..88ea8dff920 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml @@ -8,49 +8,49 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_helix_ZookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_idealstateZnodeSize_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_idealstateZnodeByteSize_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_replicationFromConfig_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_numberOfReplicas_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_percentOfReplicas_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_percentSegmentsAvailable_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_segmentCount_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_segmentsInErrorState_$3" cache: true labels: @@ -77,49 +77,49 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_offlineTableCount_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_validateion_$2_$3" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_cronSchedulerJobScheduled_$3" cache: true labels: table: "$1" taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerTriggered_$3" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobTriggered_$3" cache: true labels: table: "$1" taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerSkipped_$3" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobSkipped_$3" cache: true labels: table: "$1" taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_cronSchedulerJobExecutionTimeMs_$3" cache: true labels: table: "$1" taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_taskStatus_$3" cache: true labels: taskType: "$1" status: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_timeMsSinceLastMinionTaskMetadataUpdate_$4" cache: true labels: table: "$1" tableType: "$2" taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_$1_$5" cache: true labels: @@ -131,14 +131,14 @@ rules: cache: true labels: taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_timeMsSinceLastSuccessfulMinionTaskGeneration_$4" cache: true labels: table: "$1" tableType: "$2" taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_lastMinionTaskGenerationEncountersError_$4" cache: true labels: @@ -148,47 +148,47 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_pinotLeadControllerResourceEnabled_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_offlineTableEstimatedSize_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableQuota_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_periodicTaskError_$4" cache: true labels: table: "$1" tableType: "$2" periodicTask: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableStorageQuotaUtilization_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableStorageEstMissingSegmentPercent_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableTotalSizeOnServer_$3" labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableSizePerReplicaOnServer_$3" labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableCompressedSize_$3" labels: table: "$1" diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml index 8d493e90b62..95853f50ecb 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml @@ -4,13 +4,13 @@ rules: cache: true labels: version: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_minion_numberOfTasks_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_minion_$4_$5" cache: true labels: diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml index 104d461bbec..a502742e02f 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml @@ -9,43 +9,43 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_helix_ZookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_idealstateZnodeSize_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_idealstateZnodeByteSize_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_numberOfReplicas_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_percentOfReplicas_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_percentSegmentsAvailable_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_segmentCount_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_segmentsInErrorState_$3" cache: true labels: @@ -72,24 +72,30 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_offlineTableCount_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_validateion_$2_$3" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_cronSchedulerJobScheduled_$3" cache: true labels: table: "$1" taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerTriggered_$3" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobTriggered_$3" cache: true labels: table: "$1" taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobSkipped_$3" + cache: true + labels: + table: "$1" + taskType: "$2" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_cronSchedulerJobExecutionTimeMs_$3" cache: true labels: @@ -101,14 +107,14 @@ rules: labels: taskType: "$1" status: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_timeMsSinceLastMinionTaskMetadataUpdate_$4" cache: true labels: table: "$1" tableType: "$2" taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_$1_$5" cache: true labels: @@ -120,14 +126,14 @@ rules: cache: true labels: taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_timeMsSinceLastSuccessfulMinionTaskGeneration_$4" cache: true labels: table: "$1" tableType: "$2" taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_lastMinionTaskGenerationEncountersError_$4" cache: true labels: @@ -137,44 +143,44 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_pinotLeadControllerResourceEnabled_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_offlineTableEstimatedSize_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_largestSegmentSizeOnServer_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableTotalSizeOnServer_$3" labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableSizePerReplicaOnServer_$3" labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableCompressedSize_$3" labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableQuota_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableStorageQuotaUtilization_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_controller_tableStorageEstMissingSegmentPercent_$3" cache: true labels: @@ -182,78 +188,78 @@ rules: tableType: "$2" # Pinot Broker -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_authorization_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_documentsScanned_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_entriesScannedInFilter_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_entriesScannedPostFilter_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_freshnessLagMs_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queries_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryExecution_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryRouting_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryTotalTimeMs_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_reduce_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_requestCompilation_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_scatterGather_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_totalServerResponseSize_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_groupBySize_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_noServingHostForSegment_$3" cache: true labels: @@ -286,63 +292,63 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_routingTableUpdateTime_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_brokerResponsesWithPartialServersResponded_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_brokerResponsesWithProcessingExceptions_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_brokerResponsesWithNumGroupsLimitReached_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_queryQuotaExceeded_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_serverMissingForRouting_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_deserialization_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_broker_requestConnectionWait_$2" cache: true labels: table: "$1" # Pinot Server -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_documentCount_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_segmentCount_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_$3_$4" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtimeRowsConsumed_$5" cache: true labels: @@ -356,7 +362,7 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_helix_zookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_highestKafkaOffsetConsumed_$5" cache: true labels: @@ -364,7 +370,7 @@ rules: tableType: "$2" topic: "$3" partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_highestStreamOffsetConsumed_$5" cache: true labels: @@ -372,7 +378,7 @@ rules: tableType: "$2" topic: "$3" partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_lastRealtimeSegment$1Seconds_$6" cache: true labels: @@ -383,7 +389,7 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_llcControllerResponse_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_llcPartitionConsuming_$5" cache: true labels: @@ -403,7 +409,7 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtime_consumptionExceptions_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtime_offheapMemoryUsed_$2" cache: true labels: @@ -428,25 +434,25 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_nettyConnection_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtimeSegmentNumPartitions_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_resizeTimeMs_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_upsertPrimaryKeysCount_$4" cache: true labels: table: "$1" tableType: "$2" partition: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtimeIngestionDelayMs_$4" cache: true labels: @@ -455,13 +461,13 @@ rules: partition: "$3" # Pinot Minions -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_minion_numberOfTasks_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_minion_$4_$5" cache: true labels: diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml index 5ad7d15f81c..ad5b96eef84 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml @@ -1,23 +1,23 @@ rules: -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_documentCount_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_segmentCount_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_$3_$4" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtimeRowsConsumed_$5" cache: true labels: @@ -31,7 +31,7 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_helix_zookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_highestKafkaOffsetConsumed_$5" cache: true labels: @@ -39,7 +39,7 @@ rules: tableType: "$2" topic: "$3" partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_highestStreamOffsetConsumed_$5" cache: true labels: @@ -47,7 +47,7 @@ rules: tableType: "$2" topic: "$3" partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_lastRealtimeSegment$1Seconds_$6" cache: true labels: @@ -58,7 +58,7 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_llcControllerResponse_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_llcPartitionConsuming_$5" cache: true labels: @@ -66,7 +66,7 @@ rules: tableType: "$2" topic: "$3" partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtimeIngestionDelayMs_$4" cache: true labels: @@ -85,7 +85,7 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtime_consumptionExceptions_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtime_offheapMemoryUsed_$2" cache: true labels: @@ -107,18 +107,18 @@ rules: - pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_nettyConnection_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_realtimeSegmentNumPartitions_$2" cache: true labels: table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_resizeTimeMs_$3" cache: true labels: table: "$1" tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" name: "pinot_server_upsertPrimaryKeysCount_$4" cache: true labels: diff --git a/kubernetes/helm/index.yaml b/kubernetes/helm/index.yaml index 5f69405a114..01aece8b5e7 100644 --- a/kubernetes/helm/index.yaml +++ b/kubernetes/helm/index.yaml @@ -1,6 +1,34 @@ apiVersion: v1 entries: pinot: + - apiVersion: v1 + appVersion: 0.2.7 + created: "2023-05-04T20:38:46.922122-04:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 9.x.x + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: f466ab18582850c589c0a6fc264e12ee236ab9cb3b540b653822296e2f65a323 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/kubernetes/helm + urls: + - pinot-0.2.7.tgz + version: 0.2.7 - apiVersion: v1 appVersion: 0.2.6 created: "2022-11-23T13:05:57.685715-08:00" diff --git a/kubernetes/helm/pinot-0.2.7.tgz b/kubernetes/helm/pinot-0.2.7.tgz new file mode 100644 index 00000000000..0bffc948def Binary files /dev/null and b/kubernetes/helm/pinot-0.2.7.tgz differ diff --git a/kubernetes/helm/pinot/Chart.yaml b/kubernetes/helm/pinot/Chart.yaml index 8d60c123665..835cdc051d9 100644 --- a/kubernetes/helm/pinot/Chart.yaml +++ b/kubernetes/helm/pinot/Chart.yaml @@ -18,10 +18,10 @@ # apiVersion: v1 -appVersion: 0.2.7-SNAPSHOT +appVersion: 0.2.8-SNAPSHOT name: pinot description: Apache Pinot is a realtime distributed OLAP datastore, which is used to deliver scalable real time analytics with low latency. It can ingest data from offline data sources (such as Hadoop and flat files) as well as online sources (such as Kafka). Pinot is designed to scale horizontally. -version: 0.2.7-SNAPSHOT +version: 0.2.8-SNAPSHOT keywords: - olap - analytics diff --git a/kubernetes/helm/pinot/README.md b/kubernetes/helm/pinot/README.md index cf5d086f8cb..239c5be080b 100644 --- a/kubernetes/helm/pinot/README.md +++ b/kubernetes/helm/pinot/README.md @@ -62,6 +62,22 @@ eksctl create cluster \ --node-ami auto ``` +For k8s 1.23+ we need to run the following commands to allow the containers to provision their storage +``` +eksctl utils associate-iam-oidc-provider --region=us-east-2 --cluster=pinot-quickstart --approve + +eksctl create iamserviceaccount \ + --name ebs-csi-controller-sa \ + --namespace kube-system \ + --cluster pinot-quickstart \ + --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \ + --approve \ + --role-only \ + --role-name AmazonEKS_EBS_CSI_DriverRole + +eksctl create addon --name aws-ebs-csi-driver --cluster pinot-quickstart --service-account-role-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/AmazonEKS_EBS_CSI_DriverRole --force +``` + You can monitor cluster status by command: ```bash diff --git a/kubernetes/helm/pinot/templates/broker/ingress.yaml b/kubernetes/helm/pinot/templates/broker/ingress-v1.yaml similarity index 54% rename from kubernetes/helm/pinot/templates/broker/ingress.yaml rename to kubernetes/helm/pinot/templates/broker/ingress-v1.yaml index 80aa8fe4266..29ab8b43d5c 100644 --- a/kubernetes/helm/pinot/templates/broker/ingress.yaml +++ b/kubernetes/helm/pinot/templates/broker/ingress-v1.yaml @@ -1,34 +1,3 @@ -{{- if .Values.broker.ingress.v1beta1.enabled -}} -{{- $ingressPath := .Values.broker.ingress.v1beta1.path -}} -{{- $serviceName := include "pinot.broker.fullname" . -}} -{{- $servicePort := .Values.broker.service.port -}} -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ $serviceName }} -{{- if .Values.broker.ingress.v1beta1.annotations }} - annotations: -{{ toYaml .Values.broker.ingress.v1beta1.annotations | indent 4 }} -{{- end }} - labels: -{{- include "pinot.brokerLabels" . | nindent 4 }} -spec: -{{- if .Values.broker.ingress.v1beta1.tls }} - tls: -{{ toYaml .Values.broker.ingress.v1beta1.tls | indent 4 }} -{{- end }} - rules: - {{- range .Values.broker.ingress.v1beta1.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - backend: - serviceName: {{ $serviceName }} - servicePort: {{ $servicePort }} - {{- end }} -{{- end }} - {{- if .Values.broker.ingress.v1.enabled -}} {{- $ingressPath := .Values.broker.ingress.v1.path -}} {{- $serviceName := include "pinot.broker.fullname" . -}} diff --git a/kubernetes/helm/pinot/templates/broker/ingress-v1beta1.yaml b/kubernetes/helm/pinot/templates/broker/ingress-v1beta1.yaml new file mode 100644 index 00000000000..9c8d07cfef9 --- /dev/null +++ b/kubernetes/helm/pinot/templates/broker/ingress-v1beta1.yaml @@ -0,0 +1,30 @@ +{{- if .Values.broker.ingress.v1beta1.enabled -}} +{{- $ingressPath := .Values.broker.ingress.v1beta1.path -}} +{{- $serviceName := include "pinot.broker.fullname" . -}} +{{- $servicePort := .Values.broker.service.port -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $serviceName }} +{{- if .Values.broker.ingress.v1beta1.annotations }} + annotations: +{{ toYaml .Values.broker.ingress.v1beta1.annotations | indent 4 }} +{{- end }} + labels: +{{- include "pinot.brokerLabels" . | nindent 4 }} +spec: +{{- if .Values.broker.ingress.v1beta1.tls }} + tls: +{{ toYaml .Values.broker.ingress.v1beta1.tls | indent 4 }} +{{- end }} + rules: + {{- range .Values.broker.ingress.v1beta1.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/kubernetes/helm/pinot/templates/broker/service-headless.yaml b/kubernetes/helm/pinot/templates/broker/service-headless.yaml index 1c8ad71e720..ab537bf57e5 100644 --- a/kubernetes/helm/pinot/templates/broker/service-headless.yaml +++ b/kubernetes/helm/pinot/templates/broker/service-headless.yaml @@ -29,5 +29,11 @@ spec: # [pod_name].[service_name].[namespace].svc.cluster.local - name: {{ .Values.broker.service.name }} port: {{ .Values.broker.service.port }} + {{- if .Values.broker.service.extraPorts }} + {{- range .Values.broker.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.brokerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/broker/service.yaml b/kubernetes/helm/pinot/templates/broker/service.yaml index bd0f4a5db06..2f441fdaf55 100644 --- a/kubernetes/helm/pinot/templates/broker/service.yaml +++ b/kubernetes/helm/pinot/templates/broker/service.yaml @@ -31,5 +31,11 @@ spec: # [pod_name].[service_name].[namespace].svc.cluster.local - name: {{ .Values.broker.service.name }} port: {{ .Values.broker.service.port }} + {{- if .Values.broker.service.extraPorts }} + {{- range .Values.broker.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.brokerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/broker/statefulset.yaml b/kubernetes/helm/pinot/templates/broker/statefulset.yaml index f53ce57d354..de69c0408f4 100644 --- a/kubernetes/helm/pinot/templates/broker/statefulset.yaml +++ b/kubernetes/helm/pinot/templates/broker/statefulset.yaml @@ -60,7 +60,7 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: [ - "StartBroker", + "{{ .Values.broker.startCommand }}", "-clusterName", "{{ .Values.cluster.name }}", "-zkAddress", {{ include "zookeeper.url" . | quote }}, "-configFileName", "/var/pinot/broker/config/pinot-broker.conf" @@ -77,6 +77,9 @@ spec: - containerPort: {{ .Values.broker.service.port }} protocol: {{ .Values.broker.service.protocol }} name: {{ .Values.broker.service.name }} +{{- if .Values.broker.service.extraPorts }} +{{ toYaml .Values.broker.service.extraPorts | indent 10 }} +{{- end }} volumeMounts: - name: config mountPath: /var/pinot/broker/config diff --git a/kubernetes/helm/pinot/templates/controller/ingress.yaml b/kubernetes/helm/pinot/templates/controller/ingress-v1.yaml similarity index 54% rename from kubernetes/helm/pinot/templates/controller/ingress.yaml rename to kubernetes/helm/pinot/templates/controller/ingress-v1.yaml index 449a84e6786..45295a60fe5 100644 --- a/kubernetes/helm/pinot/templates/controller/ingress.yaml +++ b/kubernetes/helm/pinot/templates/controller/ingress-v1.yaml @@ -1,34 +1,3 @@ -{{- if .Values.controller.ingress.v1beta1.enabled -}} -{{- $ingressPath := .Values.controller.ingress.v1beta1.path -}} -{{- $serviceName := include "pinot.controller.fullname" . -}} -{{- $servicePort := .Values.controller.service.port -}} -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ $serviceName }} -{{- if .Values.controller.ingress.v1beta1.annotations }} - annotations: -{{ toYaml .Values.controller.ingress.v1beta1.annotations | indent 4 }} -{{- end }} - labels: -{{- include "pinot.controllerLabels" . | nindent 4 }} -spec: -{{- if .Values.controller.ingress.v1beta1.tls }} - tls: -{{ toYaml .Values.controller.ingress.v1beta1.tls | indent 4 }} -{{- end }} - rules: - {{- range .Values.controller.ingress.v1beta1.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - backend: - serviceName: {{ $serviceName }} - servicePort: {{ $servicePort }} - {{- end }} -{{- end }} - {{- if .Values.controller.ingress.v1.enabled -}} {{- $ingressPath := .Values.controller.ingress.v1.path -}} {{- $serviceName := include "pinot.controller.fullname" . -}} diff --git a/kubernetes/helm/pinot/templates/controller/ingress-v1beta1.yaml b/kubernetes/helm/pinot/templates/controller/ingress-v1beta1.yaml new file mode 100644 index 00000000000..228b7501b91 --- /dev/null +++ b/kubernetes/helm/pinot/templates/controller/ingress-v1beta1.yaml @@ -0,0 +1,30 @@ +{{- if .Values.controller.ingress.v1beta1.enabled -}} +{{- $ingressPath := .Values.controller.ingress.v1beta1.path -}} +{{- $serviceName := include "pinot.controller.fullname" . -}} +{{- $servicePort := .Values.controller.service.port -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $serviceName }} +{{- if .Values.controller.ingress.v1beta1.annotations }} + annotations: +{{ toYaml .Values.controller.ingress.v1beta1.annotations | indent 4 }} +{{- end }} + labels: +{{- include "pinot.controllerLabels" . | nindent 4 }} +spec: +{{- if .Values.controller.ingress.v1beta1.tls }} + tls: +{{ toYaml .Values.controller.ingress.v1beta1.tls | indent 4 }} +{{- end }} + rules: + {{- range .Values.controller.ingress.v1beta1.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/kubernetes/helm/pinot/templates/controller/service-headless.yaml b/kubernetes/helm/pinot/templates/controller/service-headless.yaml index bb64490c3c2..1b1f5f47cca 100644 --- a/kubernetes/helm/pinot/templates/controller/service-headless.yaml +++ b/kubernetes/helm/pinot/templates/controller/service-headless.yaml @@ -29,5 +29,11 @@ spec: # [pod_name].[service_name].[namespace].svc.cluster.local - name: {{ .Values.controller.service.name }} port: {{ .Values.controller.service.port }} + {{- if .Values.controller.service.extraPorts }} + {{- range .Values.controller.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.controllerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/controller/service.yaml b/kubernetes/helm/pinot/templates/controller/service.yaml index 8e0cba9bf23..7453142e12f 100644 --- a/kubernetes/helm/pinot/templates/controller/service.yaml +++ b/kubernetes/helm/pinot/templates/controller/service.yaml @@ -31,5 +31,11 @@ spec: # [pod_name].[service_name].[namespace].svc.cluster.local - name: {{ .Values.controller.service.name }} port: {{ .Values.controller.service.port }} + {{- if .Values.controller.service.extraPorts }} + {{- range .Values.controller.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.controllerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/controller/statefulset.yaml b/kubernetes/helm/pinot/templates/controller/statefulset.yaml index baf6229b5de..8b652b301e5 100644 --- a/kubernetes/helm/pinot/templates/controller/statefulset.yaml +++ b/kubernetes/helm/pinot/templates/controller/statefulset.yaml @@ -59,7 +59,7 @@ spec: {{- toYaml .Values.controller.securityContext | nindent 10 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ "StartController", "-configFileName", "/var/pinot/controller/config/pinot-controller.conf" ] + args: [ "{{ .Values.controller.startCommand }}", "-configFileName", "/var/pinot/controller/config/pinot-controller.conf" ] env: - name: JAVA_OPTS value: "{{ .Values.controller.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.controller.log4j2ConfFile }} -Dplugins.dir={{ .Values.controller.pluginsDir }}" @@ -72,6 +72,9 @@ spec: - containerPort: {{ .Values.controller.service.port }} protocol: {{ .Values.controller.service.protocol }} name: {{ .Values.controller.service.name }} +{{- if .Values.controller.service.extraPorts }} +{{ toYaml .Values.controller.service.extraPorts | indent 10 }} +{{- end }} {{- if .Values.controller.probes.livenessEnabled }} livenessProbe: initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} diff --git a/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml b/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml index 1f286f7a80d..d68fd40275c 100644 --- a/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml +++ b/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml @@ -37,6 +37,7 @@ spec: {{ toYaml .Values.minionStateless.podAnnotations | indent 8 }} spec: terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + serviceAccountName: {{ include "pinot.serviceAccountName" . }} securityContext: {{- toYaml .Values.minionStateless.podSecurityContext | nindent 8 }} {{- with .Values.imagePullSecrets }} @@ -56,7 +57,7 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: [ - "StartMinion", + "{{ .Values.minionStateless.startCommand }}", "-clusterName", "{{ .Values.cluster.name }}", "-zkAddress", {{ include "zookeeper.url" . | quote }}, "-configFileName", "/var/pinot/minion/config/pinot-minion-stateless.conf" @@ -73,6 +74,9 @@ spec: - containerPort: {{ .Values.minionStateless.service.port }} protocol: {{ .Values.minionStateless.service.protocol }} name: {{ .Values.minionStateless.service.name }} +{{- if .Values.minionStateless.service.extraPorts }} +{{ toYaml .Values.minionStateless.service.extraPorts | indent 10 }} +{{- end }} {{- if .Values.minionStateless.probes.livenessEnabled }} livenessProbe: initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} diff --git a/kubernetes/helm/pinot/templates/minion/service-headless.yaml b/kubernetes/helm/pinot/templates/minion/service-headless.yaml index 120042b8b16..5673b9ac2d5 100644 --- a/kubernetes/helm/pinot/templates/minion/service-headless.yaml +++ b/kubernetes/helm/pinot/templates/minion/service-headless.yaml @@ -30,6 +30,12 @@ spec: # [pod_name].[service_name].[namespace].svc.cluster.local - name: {{ .Values.minion.service.name }} port: {{ .Values.minion.service.port }} + {{- if .Values.minion.service.extraPorts }} + {{- range .Values.minion.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.minionMatchLabels" . | nindent 4 }} {{- end }} diff --git a/kubernetes/helm/pinot/templates/minion/service.yaml b/kubernetes/helm/pinot/templates/minion/service.yaml index d4f61d982d2..4fc88fd2889 100644 --- a/kubernetes/helm/pinot/templates/minion/service.yaml +++ b/kubernetes/helm/pinot/templates/minion/service.yaml @@ -32,6 +32,12 @@ spec: # [pod_name].[service_name].[namespace].svc.cluster.local - name: {{ .Values.minion.service.name }} port: {{ .Values.minion.service.port }} + {{- if .Values.minion.service.extraPorts }} + {{- range .Values.minion.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.minionMatchLabels" . | nindent 4 }} {{- end }} diff --git a/kubernetes/helm/pinot/templates/minion/statefulset.yaml b/kubernetes/helm/pinot/templates/minion/statefulset.yaml index db43b87ded8..443056dacfb 100644 --- a/kubernetes/helm/pinot/templates/minion/statefulset.yaml +++ b/kubernetes/helm/pinot/templates/minion/statefulset.yaml @@ -61,7 +61,7 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: [ - "StartMinion", + "{{ .Values.minion.startCommand }}", "-clusterName", "{{ .Values.cluster.name }}", "-zkAddress", {{ include "zookeeper.url" . | quote }}, "-configFileName", "/var/pinot/minion/config/pinot-minion.conf" @@ -78,6 +78,9 @@ spec: - containerPort: {{ .Values.minion.service.port }} protocol: {{ .Values.minion.service.protocol }} name: {{ .Values.minion.service.name }} +{{- if .Values.minion.service.extraPorts }} +{{ toYaml .Values.minion.service.extraPorts | indent 10 }} +{{- end }} {{- if .Values.minion.probes.livenessEnabled }} livenessProbe: initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} diff --git a/kubernetes/helm/pinot/templates/server/service-headless.yaml b/kubernetes/helm/pinot/templates/server/service-headless.yaml index 5ccfcd56b1b..fe558e41430 100644 --- a/kubernetes/helm/pinot/templates/server/service-headless.yaml +++ b/kubernetes/helm/pinot/templates/server/service-headless.yaml @@ -34,5 +34,12 @@ spec: port: {{ .Values.server.service.adminExposePort }} targetPort: {{ .Values.server.service.adminPort }} protocol: {{ .Values.server.service.protocol }} + {{- if .Values.server.service.extraPorts }} + {{- range .Values.server.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} selector: {{- include "pinot.serverMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/server/service.yaml b/kubernetes/helm/pinot/templates/server/service.yaml index c93dfd01fac..298470c9565 100644 --- a/kubernetes/helm/pinot/templates/server/service.yaml +++ b/kubernetes/helm/pinot/templates/server/service.yaml @@ -36,5 +36,11 @@ spec: port: {{ .Values.server.service.adminExposePort }} targetPort: {{ .Values.server.service.adminPort }} protocol: {{ .Values.server.service.protocol }} + {{- if .Values.server.service.extraPorts }} + {{- range .Values.server.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} selector: {{- include "pinot.serverMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/server/statefulset.yaml b/kubernetes/helm/pinot/templates/server/statefulset.yaml index 1d80e9bdd06..ea2de5785ec 100644 --- a/kubernetes/helm/pinot/templates/server/statefulset.yaml +++ b/kubernetes/helm/pinot/templates/server/statefulset.yaml @@ -60,7 +60,7 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: [ - "StartServer", + "{{ .Values.server.startCommand }}", "-clusterName", "{{ .Values.cluster.name }}", "-zkAddress", {{ include "zookeeper.url" . | quote }}, "-configFileName", "/var/pinot/server/config/pinot-server.conf" @@ -80,6 +80,9 @@ spec: - containerPort: {{ .Values.server.service.adminPort }} protocol: {{ .Values.server.service.protocol }} name: {{ .Values.server.service.adminPortName }} +{{- if .Values.server.service.extraPorts }} +{{ toYaml .Values.server.service.extraPorts | indent 10 }} +{{- end }} {{- if .Values.server.probes.livenessEnabled }} livenessProbe: initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} diff --git a/kubernetes/helm/pinot/values.yaml b/kubernetes/helm/pinot/values.yaml index 54aaa638d14..9e21167a6c7 100644 --- a/kubernetes/helm/pinot/values.yaml +++ b/kubernetes/helm/pinot/values.yaml @@ -21,7 +21,7 @@ image: repository: apachepinot/pinot - tag: latest # release-0.11.0 + tag: latest # release-0.12.0 pullPolicy: Always # Use IfNotPresent when you pinged a version of image tag cluster: @@ -76,6 +76,7 @@ controller: podSecurityContext: {} # fsGroup: 2000 securityContext: {} + startCommand: "StartController" probes: endpoint: "/health" @@ -115,6 +116,10 @@ controller: nodePort: "" protocol: TCP name: controller + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port external: enabled: true @@ -185,6 +190,7 @@ broker: podSecurityContext: {} # fsGroup: 2000 securityContext: {} + startCommand: "StartBroker" jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-broker.log" @@ -214,6 +220,10 @@ broker: port: 8099 name: broker nodePort: "" + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port external: enabled: true @@ -284,6 +294,7 @@ server: podSecurityContext: {} # fsGroup: 2000 securityContext: {} + startCommand: "StartServer" probes: endpoint: "/health" @@ -322,6 +333,10 @@ server: adminPortName: admin nodePort: "" protocol: TCP + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port resources: requests: @@ -372,6 +387,7 @@ minion: podSecurityContext: {} # fsGroup: 2000 securityContext: {} + startCommand: "StartMinion" probes: endpoint: "/health" @@ -405,6 +421,10 @@ minion: nodePort: "" protocol: TCP name: minion + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port resources: requests: @@ -454,6 +474,7 @@ minionStateless: podSecurityContext: {} # fsGroup: 2000 securityContext: {} + startCommand: "StartMinion" probes: endpoint: "/health" @@ -481,7 +502,11 @@ minionStateless: port: 9514 protocol: TCP name: minion - + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port + resources: requests: memory: "1.25Gi" diff --git a/pinot-broker/pom.xml b/pinot-broker/pom.xml index c90e920d0a7..771780bcc53 100644 --- a/pinot-broker/pom.xml +++ b/pinot-broker/pom.xml @@ -79,6 +79,10 @@ org.glassfish.jersey.media jersey-media-json-jackson + + org.glassfish.jersey.core + jersey-common + io.swagger swagger-jaxrs @@ -153,5 +157,10 @@ mockito-core test + + com.mercateo + test-clock + test + diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java index c8e252ee272..485131a3e56 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.api; +import java.util.Set; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.spi.annotations.InterfaceAudience; import org.apache.pinot.spi.annotations.InterfaceStability; @@ -47,4 +48,14 @@ default boolean hasAccess(RequesterIdentity requesterIdentity) { * @return {@code true} if authorized, {@code false} otherwise */ boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest); + + /** + * Fine-grained access control on pinot tables. + * + * @param requesterIdentity requester identity + * @param tables Set of pinot tables used in the query. Table name can be with or without tableType. + * + * @return {@code true} if authorized, {@code false} otherwise + */ + boolean hasAccess(RequesterIdentity requesterIdentity, Set tables); } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/InstanceResource.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/InstanceResource.java new file mode 100644 index 00000000000..3b6a80f6d67 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/InstanceResource.java @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.api.resources; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiKeyAuthDefinition; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; +import io.swagger.annotations.SecurityDefinition; +import io.swagger.annotations.SwaggerDefinition; +import java.util.Collections; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import org.apache.helix.HelixManager; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.broker.broker.BrokerAdminApiApplication; +import org.apache.pinot.common.utils.helix.HelixHelper; + +import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; + +/** + * This resource API can be used to retrieve instance level information like instance tags. + */ +@Api(description = "Metadata for this instance (like tenant tags)", tags = "instance", + authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY)}) +@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = @ApiKeyAuthDefinition(name = + HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = SWAGGER_AUTHORIZATION_KEY))) +@Path("instance") +public class InstanceResource { + @Inject + @Named(BrokerAdminApiApplication.BROKER_INSTANCE_ID) + private String _instanceId; + @Inject + private HelixManager _helixManager; + + @GET + @Path("tags") + @ApiOperation(value = "Tenant tags for current instance") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal server error") + }) + @Produces(MediaType.APPLICATION_JSON) + public List getInstanceTags() { + InstanceConfig config = HelixHelper.getInstanceConfig(_helixManager, _instanceId); + if (config != null && config.getTags() != null) { + return config.getTags(); + } + return Collections.emptyList(); + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java index 30c8cb6d7b8..5792579c3af 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java @@ -121,6 +121,8 @@ public void processSqlQueryGet(@ApiParam(value = "Query", required = true) @Quer } BrokerResponse brokerResponse = executeSqlQuery(requestJson, makeHttpIdentity(requestContext), true); asyncResponse.resume(brokerResponse.toJsonString()); + } catch (WebApplicationException wae) { + asyncResponse.resume(wae); } catch (Exception e) { LOGGER.error("Caught exception while processing GET request", e); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.UNCAUGHT_GET_EXCEPTIONS, 1L); @@ -148,6 +150,8 @@ public void processSqlQueryPost(String query, @Suspended AsyncResponse asyncResp BrokerResponse brokerResponse = executeSqlQuery((ObjectNode) requestJson, makeHttpIdentity(requestContext), false); asyncResponse.resume(brokerResponse.toJsonString()); + } catch (WebApplicationException wae) { + asyncResponse.resume(wae); } catch (Exception e) { LOGGER.error("Caught exception while processing POST request", e); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.UNCAUGHT_POST_EXCEPTIONS, 1L); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java index e5d96a424fe..1e5a888a66f 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.broker; +import java.util.Set; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.common.request.BrokerRequest; @@ -43,5 +44,10 @@ private static class AllowAllAccessControl implements AccessControl { public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { return true; } + + @Override + public boolean hasAccess(RequesterIdentity requesterIdentity, Set tables) { + return true; + } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java index 91ae183e8c2..1eb134cfa73 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.HttpRequesterIdentity; @@ -77,18 +78,12 @@ public BasicAuthAccessControl(Collection principals) { @Override public boolean hasAccess(RequesterIdentity requesterIdentity) { - return hasAccess(requesterIdentity, null); + return hasAccess(requesterIdentity, (BrokerRequest) null); } @Override public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { - Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, "HttpRequesterIdentity required"); - HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; - - Collection tokens = identity.getHttpHeaders().get(HEADER_AUTHORIZATION); - Optional principalOpt = - tokens.stream().map(BasicAuthUtils::normalizeBase64Token).map(_token2principal::get).filter(Objects::nonNull) - .findFirst(); + Optional principalOpt = getPrincipalOpt(requesterIdentity); if (!principalOpt.isPresent()) { // no matching token? reject @@ -104,5 +99,39 @@ public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brok return principal.hasTable(brokerRequest.getQuerySource().getTableName()); } + + @Override + public boolean hasAccess(RequesterIdentity requesterIdentity, Set tables) { + Optional principalOpt = getPrincipalOpt(requesterIdentity); + + if (!principalOpt.isPresent()) { + // no matching token? reject + return false; + } + + if (tables == null || tables.isEmpty()) { + return true; + } + + BasicAuthPrincipal principal = principalOpt.get(); + for (String table : tables) { + if (!principal.hasTable(table)) { + return false; + } + } + + return true; + } + + private Optional getPrincipalOpt(RequesterIdentity requesterIdentity) { + Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, "HttpRequesterIdentity required"); + HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; + + Collection tokens = identity.getHttpHeaders().get(HEADER_AUTHORIZATION); + Optional principalOpt = + tokens.stream().map(BasicAuthUtils::normalizeBase64Token).map(_token2principal::get).filter(Objects::nonNull) + .findFirst(); + return principalOpt; + } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java index a0b5e9e330d..a3e11a22060 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java @@ -29,6 +29,7 @@ import java.util.concurrent.Executors; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.helix.HelixManager; import org.apache.pinot.broker.requesthandler.BrokerRequestHandler; import org.apache.pinot.broker.routing.BrokerRoutingManager; import org.apache.pinot.common.metrics.BrokerMetrics; @@ -55,10 +56,10 @@ public class BrokerAdminApiApplication extends ResourceConfig { private static final Logger LOGGER = LoggerFactory.getLogger(BrokerAdminApiApplication.class); - private static final String RESOURCE_PACKAGE = "org.apache.pinot.broker.api.resources"; public static final String PINOT_CONFIGURATION = "pinotConfiguration"; public static final String BROKER_INSTANCE_ID = "brokerInstanceId"; + private final String _brokerResourcePackages; private final boolean _useHttps; private final boolean _swaggerBrokerEnabled; private final ExecutorService _executorService; @@ -67,8 +68,12 @@ public class BrokerAdminApiApplication extends ResourceConfig { public BrokerAdminApiApplication(BrokerRoutingManager routingManager, BrokerRequestHandler brokerRequestHandler, BrokerMetrics brokerMetrics, PinotConfiguration brokerConf, SqlQueryExecutor sqlQueryExecutor, - ServerRoutingStatsManager serverRoutingStatsManager, AccessControlFactory accessFactory) { - packages(RESOURCE_PACKAGE); + ServerRoutingStatsManager serverRoutingStatsManager, AccessControlFactory accessFactory, + HelixManager helixManager) { + _brokerResourcePackages = brokerConf.getProperty(CommonConstants.Broker.BROKER_RESOURCE_PACKAGES, + CommonConstants.Broker.DEFAULT_BROKER_RESOURCE_PACKAGES); + String[] pkgs = _brokerResourcePackages.split(","); + packages(pkgs); property(PINOT_CONFIGURATION, brokerConf); _useHttps = Boolean.parseBoolean(brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_SWAGGER_USE_HTTPS)); _swaggerBrokerEnabled = brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_SWAGGER_BROKER_ENABLED, @@ -87,6 +92,7 @@ public BrokerAdminApiApplication(BrokerRoutingManager routingManager, BrokerRequ protected void configure() { bind(connMgr).to(HttpConnectionManager.class); bind(_executorService).to(Executor.class); + bind(helixManager).to(HelixManager.class); bind(sqlQueryExecutor).to(SqlQueryExecutor.class); bind(routingManager).to(BrokerRoutingManager.class); bind(brokerRequestHandler).to(BrokerRequestHandler.class); @@ -102,6 +108,12 @@ protected void configure() { bind(accessFactory).to(AccessControlFactory.class); } }); + boolean enableBoundedJerseyThreadPoolExecutor = brokerConf + .getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_BOUNDED_JERSEY_THREADPOOL_EXECUTOR, + CommonConstants.Broker.DEFAULT_ENABLE_BOUNDED_JERSEY_THREADPOOL_EXECUTOR); + if (enableBoundedJerseyThreadPoolExecutor) { + register(buildBrokerManagedAsyncExecutorProvider(brokerConf, brokerMetrics)); + } register(JacksonFeature.class); registerClasses(io.swagger.jaxrs.listing.ApiListingResource.class); registerClasses(io.swagger.jaxrs.listing.SwaggerSerializers.class); @@ -137,7 +149,7 @@ private void setupSwagger() { beanConfig.setSchemes(new String[]{CommonConstants.HTTP_PROTOCOL, CommonConstants.HTTPS_PROTOCOL}); } beanConfig.setBasePath("/"); - beanConfig.setResourcePackage(RESOURCE_PACKAGE); + beanConfig.setResourcePackage(_brokerResourcePackages); beanConfig.setScan(true); HttpHandler httpHandler = new CLStaticHttpHandler(BrokerAdminApiApplication.class.getClassLoader(), "/api/"); @@ -145,11 +157,24 @@ private void setupSwagger() { _httpServer.getServerConfiguration().addHttpHandler(httpHandler, "/api/", "/help/"); URL swaggerDistLocation = - BrokerAdminApiApplication.class.getClassLoader().getResource("META-INF/resources/webjars/swagger-ui/3.23.11/"); + BrokerAdminApiApplication.class.getClassLoader().getResource(CommonConstants.CONFIG_OF_SWAGGER_RESOURCES_PATH); CLStaticHttpHandler swaggerDist = new CLStaticHttpHandler(new URLClassLoader(new URL[]{swaggerDistLocation})); _httpServer.getServerConfiguration().addHttpHandler(swaggerDist, "/swaggerui-dist/"); } + private BrokerManagedAsyncExecutorProvider buildBrokerManagedAsyncExecutorProvider(PinotConfiguration brokerConf, + BrokerMetrics brokerMetrics) { + int corePoolSize = brokerConf + .getProperty(CommonConstants.Broker.CONFIG_OF_JERSEY_THREADPOOL_EXECUTOR_CORE_POOL_SIZE, + CommonConstants.Broker.DEFAULT_JERSEY_THREADPOOL_EXECUTOR_CORE_POOL_SIZE); + int maximumPoolSize = brokerConf + .getProperty(CommonConstants.Broker.CONFIG_OF_JERSEY_THREADPOOL_EXECUTOR_MAX_POOL_SIZE, + CommonConstants.Broker.DEFAULT_JERSEY_THREADPOOL_EXECUTOR_MAX_POOL_SIZE); + int queueSize = brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_JERSEY_THREADPOOL_EXECUTOR_QUEUE_SIZE, + CommonConstants.Broker.DEFAULT_JERSEY_THREADPOOL_EXECUTOR_QUEUE_SIZE); + return new BrokerManagedAsyncExecutorProvider(corePoolSize, maximumPoolSize, queueSize, brokerMetrics); + } + public void stop() { if (_httpServer != null) { LOGGER.info("Shutting down http server"); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProvider.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProvider.java new file mode 100644 index 00000000000..c85f421f4c1 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProvider.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.broker; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import javax.ws.rs.ServiceUnavailableException; +import javax.ws.rs.core.Response; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.glassfish.jersey.server.ManagedAsyncExecutor; +import org.glassfish.jersey.spi.ThreadPoolExecutorProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * BrokerManagedAsyncExecutorProvider provides a bounded thread pool. + */ +@ManagedAsyncExecutor +public class BrokerManagedAsyncExecutorProvider extends ThreadPoolExecutorProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(BrokerManagedAsyncExecutorProvider.class); + + private static final String NAME = "broker-managed-async-executor"; + + private final BrokerMetrics _brokerMetrics; + + private final int _maximumPoolSize; + private final int _corePoolSize; + private final int _queueSize; + + public BrokerManagedAsyncExecutorProvider(int corePoolSize, int maximumPoolSize, int queueSize, + BrokerMetrics brokerMetrics) { + super(NAME); + _corePoolSize = corePoolSize; + _maximumPoolSize = maximumPoolSize; + _queueSize = queueSize; + _brokerMetrics = brokerMetrics; + } + + @Override + protected int getMaximumPoolSize() { + return _maximumPoolSize; + } + + @Override + protected int getCorePoolSize() { + return _corePoolSize; + } + + @Override + protected BlockingQueue getWorkQueue() { + if (_queueSize == Integer.MAX_VALUE) { + return new LinkedBlockingQueue(); + } + return new ArrayBlockingQueue(_queueSize); + } + + @Override + protected RejectedExecutionHandler getRejectedExecutionHandler() { + return new BrokerThreadPoolRejectExecutionHandler(_brokerMetrics); + } + + static class BrokerThreadPoolRejectExecutionHandler implements RejectedExecutionHandler { + private final BrokerMetrics _brokerMetrics; + + public BrokerThreadPoolRejectExecutionHandler(BrokerMetrics brokerMetrics) { + _brokerMetrics = brokerMetrics; + } + + /** + * Reject the runnable if it can’t be accommodated by the thread pool. + * + *

Response returned will have SERVICE_UNAVAILABLE(503) error code with error msg. + * + * @param r Runnable + * @param executor ThreadPoolExecutor + */ + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.QUERY_REJECTED_EXCEPTIONS, 1L); + LOGGER.error("Task " + r + " rejected from " + executor); + + throw new ServiceUnavailableException(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity( + "Pinot Broker thread pool can not accommodate more requests now. " + "Request is rejected from " + executor) + .build()); + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java index a127f1a40aa..95414928184 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java @@ -20,9 +20,11 @@ import com.google.common.base.Preconditions; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; @@ -79,11 +81,42 @@ public BasicAuthAccessControl(AccessControlUserCache userCache) { @Override public boolean hasAccess(RequesterIdentity requesterIdentity) { - return hasAccess(requesterIdentity, null); + return hasAccess(requesterIdentity, (BrokerRequest) null); } @Override public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + if (brokerRequest == null || !brokerRequest.isSetQuerySource() || !brokerRequest.getQuerySource() + .isSetTableName()) { + // no table restrictions? accept + return true; + } + + return hasAccess(requesterIdentity, Collections.singleton(brokerRequest.getQuerySource().getTableName())); + } + + @Override + public boolean hasAccess(RequesterIdentity requesterIdentity, Set tables) { + Optional principalOpt = getPrincipalAuth(requesterIdentity); + if (!principalOpt.isPresent()) { + // no matching token? reject + return false; + } + if (tables == null || tables.isEmpty()) { + return true; + } + + ZkBasicAuthPrincipal principal = principalOpt.get(); + for (String table : tables) { + if (!principal.hasTable(table)) { + return false; + } + } + + return true; + } + + private Optional getPrincipalAuth(RequesterIdentity requesterIdentity) { Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, "HttpRequesterIdentity required"); HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; @@ -95,28 +128,15 @@ public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brok Map name2password = tokens.stream().collect(Collectors - .toMap(BasicAuthUtils::extractUsername, BasicAuthUtils::extractPassword)); + .toMap(BasicAuthUtils::extractUsername, BasicAuthUtils::extractPassword)); Map password2principal = name2password.keySet().stream() - .collect(Collectors.toMap(name2password::get, _name2principal::get)); + .collect(Collectors.toMap(name2password::get, _name2principal::get)); Optional principalOpt = - password2principal.entrySet().stream() - .filter(entry -> BcryptUtils.checkpw(entry.getKey(), entry.getValue().getPassword())) - .map(u -> u.getValue()).filter(Objects::nonNull).findFirst(); - - if (!principalOpt.isPresent()) { - // no matching token? reject - return false; - } - - ZkBasicAuthPrincipal principal = principalOpt.get(); - if (brokerRequest == null || !brokerRequest.isSetQuerySource() || !brokerRequest.getQuerySource() - .isSetTableName()) { - // no table restrictions? accept - return true; - } - - return principal.hasTable(brokerRequest.getQuerySource().getTableName()); + password2principal.entrySet().stream() + .filter(entry -> BcryptUtils.checkpw(entry.getKey(), entry.getValue().getPassword())) + .map(u -> u.getValue()).filter(Objects::nonNull).findFirst(); + return principalOpt; } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java index 6407fd0b296..c54183da8b1 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java @@ -56,6 +56,7 @@ import org.apache.pinot.common.metrics.BrokerGauge; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.utils.PinotAppConfigs; import org.apache.pinot.common.utils.ServiceStartableUtils; import org.apache.pinot.common.utils.ServiceStatus; import org.apache.pinot.common.utils.TlsUtils; @@ -63,14 +64,19 @@ import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.common.version.PinotVersion; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; +import org.apache.pinot.core.query.utils.rewriter.ResultRewriterFactory; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; import org.apache.pinot.core.util.ListenerConfigUtil; +import org.apache.pinot.query.service.QueryConfig; +import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.metrics.PinotMetricsRegistry; import org.apache.pinot.spi.services.ServiceRole; import org.apache.pinot.spi.services.ServiceStartable; +import org.apache.pinot.spi.trace.Tracing; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Broker; import org.apache.pinot.spi.utils.CommonConstants.Helix; import org.apache.pinot.spi.utils.InstanceTypeUtils; @@ -128,6 +134,9 @@ public void init(PinotConfiguration brokerConf) _clusterName = brokerConf.getProperty(Helix.CONFIG_OF_CLUSTER_NAME); ServiceStartableUtils.applyClusterConfig(_brokerConf, _zkServers, _clusterName, ServiceRole.BROKER); + if (_brokerConf.getProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT, QueryConfig.DEFAULT_QUERY_RUNNER_PORT) == 0) { + _brokerConf.setProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT, NetUtils.findOpenPort()); + } setupHelixSystemProperties(); _listenerConfigs = ListenerConfigUtil.buildBrokerConfigs(brokerConf); _hostname = brokerConf.getProperty(Broker.CONFIG_OF_BROKER_HOSTNAME); @@ -220,6 +229,7 @@ public PinotConfiguration getConfig() { public void start() throws Exception { LOGGER.info("Starting Pinot broker (Version: {})", PinotVersion.VERSION); + LOGGER.info("Broker configs: {}", new PinotAppConfigs(getConfig()).toJSONString()); _isStarting = true; Utils.logVersions(); @@ -241,6 +251,7 @@ public void start() _brokerConf.getProperty(Broker.CONFIG_OF_ALLOWED_TABLES_FOR_EMITTING_METRICS, Collections.emptyList())); _brokerMetrics.initializeGlobalMeters(); _brokerMetrics.setValueOfGlobalGauge(BrokerGauge.VERSION, PinotVersion.VERSION_METRIC_NAME, 1); + BrokerMetrics.register(_brokerMetrics); // Set up request handling classes _serverRoutingStatsManager = new ServerRoutingStatsManager(_brokerConf); _serverRoutingStatsManager.init(); @@ -254,11 +265,12 @@ public void start() // Initialize QueryRewriterFactory LOGGER.info("Initializing QueryRewriterFactory"); QueryRewriterFactory.init(_brokerConf.getProperty(Broker.CONFIG_OF_BROKER_QUERY_REWRITER_CLASS_NAMES)); + LOGGER.info("Initializing ResultRewriterFactory"); + ResultRewriterFactory.init(_brokerConf.getProperty(Broker.CONFIG_OF_BROKER_RESULT_REWRITER_CLASS_NAMES)); // Initialize FunctionRegistry before starting the broker request handler FunctionRegistry.init(); boolean caseInsensitive = - _brokerConf.getProperty(Helix.ENABLE_CASE_INSENSITIVE_KEY, false) || _brokerConf.getProperty( - Helix.DEPRECATED_ENABLE_CASE_INSENSITIVE_KEY, false); + _brokerConf.getProperty(Helix.ENABLE_CASE_INSENSITIVE_KEY, Helix.DEFAULT_ENABLE_CASE_INSENSITIVE); TableCache tableCache = new TableCache(_propertyStore, caseInsensitive); // Configure TLS for netty connection to server TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(_brokerConf, Broker.BROKER_TLS_PREFIX); @@ -298,6 +310,18 @@ public void start() _brokerRequestHandler = new BrokerRequestHandlerDelegate(brokerId, singleStageBrokerRequestHandler, multiStageBrokerRequestHandler, _brokerMetrics); _brokerRequestHandler.start(); + + // Enable/disable thread CPU time measurement through instance config. + ThreadResourceUsageProvider.setThreadCpuTimeMeasurementEnabled( + _brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_THREAD_CPU_TIME_MEASUREMENT, + CommonConstants.Broker.DEFAULT_ENABLE_THREAD_CPU_TIME_MEASUREMENT)); + // Enable/disable thread memory allocation tracking through instance config + ThreadResourceUsageProvider.setThreadMemoryMeasurementEnabled( + _brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_THREAD_ALLOCATED_BYTES_MEASUREMENT, + CommonConstants.Broker.DEFAULT_THREAD_ALLOCATED_BYTES_MEASUREMENT)); + Tracing.ThreadAccountantOps + .initializeThreadAccountant(_brokerConf.subset(CommonConstants.PINOT_QUERY_SCHEDULER_PREFIX), _instanceId); + String controllerUrl = _brokerConf.getProperty(Broker.CONTROLLER_URL); if (controllerUrl != null) { _sqlQueryExecutor = new SqlQueryExecutor(controllerUrl); @@ -307,7 +331,8 @@ public void start() LOGGER.info("Starting broker admin application on: {}", ListenerConfigUtil.toString(_listenerConfigs)); _brokerAdminApplication = new BrokerAdminApiApplication(_routingManager, _brokerRequestHandler, _brokerMetrics, _brokerConf, - _sqlQueryExecutor, _serverRoutingStatsManager, _accessControlFactory); + _sqlQueryExecutor, _serverRoutingStatsManager, _accessControlFactory, _spectatorHelixManager); + registerExtraComponents(_brokerAdminApplication); _brokerAdminApplication.start(_listenerConfigs); LOGGER.info("Initializing cluster change mediator"); @@ -370,12 +395,31 @@ public void start() LOGGER.info("Finish starting Pinot broker"); } + /** + * This method is called after initialization of BrokerAdminApiApplication object + * and before calling start to allow custom broker starters to register additional + * components. + * @param brokerAdminApplication is the application + */ + protected void registerExtraComponents(BrokerAdminApiApplication brokerAdminApplication) { + } + private void updateInstanceConfigAndBrokerResourceIfNeeded() { InstanceConfig instanceConfig = HelixHelper.getInstanceConfig(_participantHelixManager, _instanceId); boolean updated = HelixHelper.updateHostnamePort(instanceConfig, _hostname, _port); + + ZNRecord znRecord = instanceConfig.getRecord(); + Map simpleFields = znRecord.getSimpleFields(); if (_tlsPort > 0) { HelixHelper.updateTlsPort(instanceConfig, _tlsPort); } + // Update multi-stage query engine ports + if (_brokerConf.getProperty(Helix.CONFIG_OF_MULTI_STAGE_ENGINE_ENABLED, Helix.DEFAULT_MULTI_STAGE_ENGINE_ENABLED)) { + updated |= updatePortIfNeeded(simpleFields, Helix.Instance.MULTI_STAGE_QUERY_ENGINE_MAILBOX_PORT_KEY, + Integer.parseInt(_brokerConf.getProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT))); + } else { + updated |= updatePortIfNeeded(simpleFields, Helix.Instance.MULTI_STAGE_QUERY_ENGINE_MAILBOX_PORT_KEY, -1); + } updated |= HelixHelper.removeDisabledPartitions(instanceConfig); boolean shouldUpdateBrokerResource = false; String brokerTag = null; @@ -444,6 +488,25 @@ private String getDefaultBrokerId() { } } + private boolean updatePortIfNeeded(Map instanceConfigSimpleFields, String key, int port) { + String existingPortStr = instanceConfigSimpleFields.get(key); + if (port > 0) { + String portStr = Integer.toString(port); + if (!portStr.equals(existingPortStr)) { + LOGGER.info("Updating '{}' for instance: {} to: {}", key, _instanceId, port); + instanceConfigSimpleFields.put(key, portStr); + return true; + } + } else { + if (existingPortStr != null) { + LOGGER.info("Removing '{}' from instance: {}", key, _instanceId); + instanceConfigSimpleFields.remove(key); + return true; + } + } + return false; + } + @Override public void stop() { LOGGER.info("Shutting down Pinot broker"); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java index c9277395a4d..5921435aab7 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java @@ -34,10 +34,11 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.URI; @@ -57,6 +58,7 @@ import org.apache.pinot.common.metrics.BrokerQueryPhase; import org.apache.pinot.common.metrics.BrokerTimer; import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.request.DataSource; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; @@ -83,6 +85,7 @@ import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.exception.BadQueryRequestException; import org.apache.pinot.spi.trace.RequestContext; +import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.BytesUtils; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Broker; @@ -113,7 +116,7 @@ public abstract class BaseBrokerRequestHandler implements BrokerRequestHandler { protected final TableCache _tableCache; protected final BrokerMetrics _brokerMetrics; - protected final AtomicLong _requestIdGenerator = new AtomicLong(); + protected final BrokerRequestIdGenerator _brokerIdGenerator; protected final QueryOptimizer _queryOptimizer = new QueryOptimizer(); protected final String _brokerId; @@ -132,6 +135,7 @@ public BaseBrokerRequestHandler(PinotConfiguration config, String brokerId, Brok AccessControlFactory accessControlFactory, QueryQuotaManager queryQuotaManager, TableCache tableCache, BrokerMetrics brokerMetrics) { _brokerId = brokerId; + _brokerIdGenerator = new BrokerRequestIdGenerator(brokerId); _config = config; _routingManager = routingManager; _accessControlFactory = accessControlFactory; @@ -229,7 +233,7 @@ public boolean cancelQuery(long requestId, int timeoutMs, Executor executor, Htt public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) throws Exception { - long requestId = _requestIdGenerator.incrementAndGet(); + long requestId = _brokerIdGenerator.get(); requestContext.setRequestId(requestId); requestContext.setRequestArrivalTimeMillis(System.currentTimeMillis()); @@ -240,7 +244,7 @@ public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOption _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); LOGGER.info("Access denied for requestId {}", requestId); requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); - return new BrokerResponseNative(QueryException.ACCESS_DENIED_ERROR); + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); } JsonNode sql = request.get(Broker.Request.SQL); @@ -249,7 +253,19 @@ public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOption } String query = sql.asText(); requestContext.setQuery(query); - return handleRequest(requestId, query, sqlNodeAndOptions, request, requesterIdentity, requestContext); + BrokerResponse brokerResponse = handleRequest(requestId, query, sqlNodeAndOptions, request, + requesterIdentity, requestContext); + + if (request.has(Broker.Request.QUERY_OPTIONS)) { + String queryOptions = request.get(Broker.Request.QUERY_OPTIONS).asText(); + Map optionsFromString = RequestUtils.getOptionsFromString(queryOptions); + if (QueryOptionsUtils.shouldDropResults(optionsFromString)) { + brokerResponse.setResultTable(null); + } + } + + brokerResponse.setRequestId(String.valueOf(requestId)); + return brokerResponse; } private BrokerResponseNative handleRequest(long requestId, String query, @@ -258,434 +274,470 @@ private BrokerResponseNative handleRequest(long requestId, String query, throws Exception { LOGGER.debug("SQL query for request {}: {}", requestId, query); - long compilationStartTimeNs; - PinotQuery pinotQuery; + //Start instrumentation context. This must not be moved further below interspersed into the code. + Tracing.ThreadAccountantOps.setupRunner(String.valueOf(requestId)); + try { - // Parse the request - sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); - // Compile the request into PinotQuery - compilationStartTimeNs = System.nanoTime(); - pinotQuery = CalciteSqlParser.compileToPinotQuery(sqlNodeAndOptions); - } catch (Exception e) { - LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); - requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); - return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); - } - - if (isLiteralOnlyQuery(pinotQuery)) { - LOGGER.debug("Request {} contains only Literal, skipping server query: {}", requestId, query); + long compilationStartTimeNs; + PinotQuery pinotQuery; try { - if (pinotQuery.isExplain()) { - // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. - return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; - } - return processLiteralOnlyQuery(pinotQuery, compilationStartTimeNs, requestContext); + // Parse the request + sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); + // Compile the request into PinotQuery + compilationStartTimeNs = System.nanoTime(); + pinotQuery = CalciteSqlParser.compileToPinotQuery(sqlNodeAndOptions); } catch (Exception e) { - // TODO: refine the exceptions here to early termination the queries won't requires to send to servers. - LOGGER.warn("Unable to execute literal request {}: {} at broker, fallback to server query. {}", requestId, - query, e.getMessage()); + LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); + requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); } - } - - PinotQuery serverPinotQuery = GapfillUtils.stripGapfill(pinotQuery); - if (serverPinotQuery.getDataSource() == null) { - LOGGER.info("Data source (FROM clause) not found in request {}: {}", request, query); - requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); - return new BrokerResponseNative( - QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "Data source (FROM clause) not found")); - } - try { - handleSubquery(serverPinotQuery, requestId, request, requesterIdentity, requestContext); - } catch (Exception e) { - LOGGER.info("Caught exception while handling the subquery in request {}: {}, {}", requestId, query, - e.getMessage()); - requestContext.setErrorCode(QueryException.QUERY_EXECUTION_ERROR_CODE); - return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); - } + if (isLiteralOnlyQuery(pinotQuery)) { + LOGGER.debug("Request {} contains only Literal, skipping server query: {}", requestId, query); + try { + if (pinotQuery.isExplain()) { + // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. + return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; + } + return processLiteralOnlyQuery(pinotQuery, compilationStartTimeNs, requestContext); + } catch (Exception e) { + // TODO: refine the exceptions here to early termination the queries won't requires to send to servers. + LOGGER.warn("Unable to execute literal request {}: {} at broker, fallback to server query. {}", requestId, + query, e.getMessage()); + } + } - String tableName = getActualTableName(serverPinotQuery.getDataSource().getTableName(), _tableCache); - serverPinotQuery.getDataSource().setTableName(tableName); - String rawTableName = TableNameBuilder.extractRawTableName(tableName); - requestContext.setTableName(rawTableName); + PinotQuery serverPinotQuery = GapfillUtils.stripGapfill(pinotQuery); + DataSource dataSource = serverPinotQuery.getDataSource(); + if (dataSource == null) { + LOGGER.info("Data source (FROM clause) not found in request {}: {}", request, query); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "Data source (FROM clause) not found")); + } + if (dataSource.getJoin() != null) { + LOGGER.info("JOIN is not supported in request {}: {}", request, query); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "JOIN is not supported")); + } + if (dataSource.getTableName() == null) { + LOGGER.info("Table name not found in request {}: {}", request, query); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "Table name not found")); + } - try { - boolean isCaseInsensitive = _tableCache.isIgnoreCase(); - Map columnNameMap = _tableCache.getColumnNameMap(rawTableName); - if (columnNameMap != null) { - updateColumnNames(rawTableName, serverPinotQuery, isCaseInsensitive, columnNameMap); - } - } catch (Exception e) { - // Throw exceptions with column in-existence error. - if (e instanceof BadQueryRequestException) { - LOGGER.info("Caught exception while checking column names in request {}: {}, {}", requestId, query, + try { + handleSubquery(serverPinotQuery, requestId, request, requesterIdentity, requestContext); + } catch (Exception e) { + LOGGER.info("Caught exception while handling the subquery in request {}: {}, {}", requestId, query, e.getMessage()); - requestContext.setErrorCode(QueryException.UNKNOWN_COLUMN_ERROR_CODE); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.UNKNOWN_COLUMN_EXCEPTIONS, 1); - return new BrokerResponseNative(QueryException.getException(QueryException.UNKNOWN_COLUMN_ERROR, e)); - } - LOGGER.warn("Caught exception while updating column names in request {}: {}, {}", requestId, query, - e.getMessage()); - } - if (_defaultHllLog2m > 0) { - handleHLLLog2mOverride(serverPinotQuery, _defaultHllLog2m); - } - if (_enableQueryLimitOverride) { - handleQueryLimitOverride(serverPinotQuery, _queryResponseLimit); - } - handleSegmentPartitionedDistinctCountOverride(serverPinotQuery, - getSegmentPartitionedColumns(_tableCache, tableName)); - if (_enableDistinctCountBitmapOverride) { - handleDistinctCountBitmapOverride(serverPinotQuery); - } - - long compilationEndTimeNs = System.nanoTime(); - // full request compile time = compilationTimeNs + parserTimeNs - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.REQUEST_COMPILATION, - (compilationEndTimeNs - compilationStartTimeNs) + sqlNodeAndOptions.getParseTimeNs()); - - // Second-stage table-level access control - // TODO: Modify AccessControl interface to directly take PinotQuery - BrokerRequest brokerRequest = CalciteSqlCompiler.convertToBrokerRequest(pinotQuery); - BrokerRequest serverBrokerRequest = - serverPinotQuery == pinotQuery ? brokerRequest : CalciteSqlCompiler.convertToBrokerRequest(serverPinotQuery); - boolean hasTableAccess = _accessControlFactory.create().hasAccess(requesterIdentity, serverBrokerRequest); - if (!hasTableAccess) { - _brokerMetrics.addMeteredTableValue(tableName, BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); - LOGGER.info("Access denied for request {}: {}, table: {}", requestId, query, tableName); - requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); - return new BrokerResponseNative(QueryException.ACCESS_DENIED_ERROR); - } - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.AUTHORIZATION, - System.nanoTime() - compilationEndTimeNs); + requestContext.setErrorCode(QueryException.QUERY_EXECUTION_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); + } + + String tableName = getActualTableName(dataSource.getTableName(), _tableCache); + dataSource.setTableName(tableName); + String rawTableName = TableNameBuilder.extractRawTableName(tableName); + requestContext.setTableName(rawTableName); - // Get the tables hit by the request - String offlineTableName = null; - String realtimeTableName = null; - TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); - if (tableType == TableType.OFFLINE) { - // Offline table - if (_routingManager.routingExists(tableName)) { - offlineTableName = tableName; + try { + boolean isCaseInsensitive = _tableCache.isIgnoreCase(); + Map columnNameMap = _tableCache.getColumnNameMap(rawTableName); + if (columnNameMap != null) { + updateColumnNames(rawTableName, serverPinotQuery, isCaseInsensitive, columnNameMap); + } + } catch (Exception e) { + // Throw exceptions with column in-existence error. + if (e instanceof BadQueryRequestException) { + LOGGER.info("Caught exception while checking column names in request {}: {}, {}", requestId, query, + e.getMessage()); + requestContext.setErrorCode(QueryException.UNKNOWN_COLUMN_ERROR_CODE); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.UNKNOWN_COLUMN_EXCEPTIONS, 1); + return new BrokerResponseNative(QueryException.getException(QueryException.UNKNOWN_COLUMN_ERROR, e)); + } + LOGGER.warn("Caught exception while updating column names in request {}: {}, {}", requestId, query, + e.getMessage()); } - } else if (tableType == TableType.REALTIME) { - // Realtime table - if (_routingManager.routingExists(tableName)) { - realtimeTableName = tableName; + if (_defaultHllLog2m > 0) { + handleHLLLog2mOverride(serverPinotQuery, _defaultHllLog2m); } - } else { - // Hybrid table (check both OFFLINE and REALTIME) - String offlineTableNameToCheck = TableNameBuilder.OFFLINE.tableNameWithType(tableName); - if (_routingManager.routingExists(offlineTableNameToCheck)) { - offlineTableName = offlineTableNameToCheck; + if (_enableQueryLimitOverride) { + handleQueryLimitOverride(serverPinotQuery, _queryResponseLimit); } - String realtimeTableNameToCheck = TableNameBuilder.REALTIME.tableNameWithType(tableName); - if (_routingManager.routingExists(realtimeTableNameToCheck)) { - realtimeTableName = realtimeTableNameToCheck; + handleSegmentPartitionedDistinctCountOverride(serverPinotQuery, + getSegmentPartitionedColumns(_tableCache, tableName)); + if (_enableDistinctCountBitmapOverride) { + handleDistinctCountBitmapOverride(serverPinotQuery); } - } - TableConfig offlineTableConfig = - _tableCache.getTableConfig(TableNameBuilder.OFFLINE.tableNameWithType(rawTableName)); - TableConfig realtimeTableConfig = - _tableCache.getTableConfig(TableNameBuilder.REALTIME.tableNameWithType(rawTableName)); + long compilationEndTimeNs = System.nanoTime(); + // full request compile time = compilationTimeNs + parserTimeNs + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.REQUEST_COMPILATION, + (compilationEndTimeNs - compilationStartTimeNs) + sqlNodeAndOptions.getParseTimeNs()); + + // Second-stage table-level access control + // TODO: Modify AccessControl interface to directly take PinotQuery + BrokerRequest brokerRequest = CalciteSqlCompiler.convertToBrokerRequest(pinotQuery); + BrokerRequest serverBrokerRequest = + serverPinotQuery == pinotQuery ? brokerRequest : CalciteSqlCompiler.convertToBrokerRequest(serverPinotQuery); + boolean hasTableAccess = _accessControlFactory.create().hasAccess(requesterIdentity, serverBrokerRequest); + if (!hasTableAccess) { + _brokerMetrics.addMeteredTableValue(tableName, BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); + LOGGER.info("Access denied for request {}: {}, table: {}", requestId, query, tableName); + requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); + } + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.AUTHORIZATION, + System.nanoTime() - compilationEndTimeNs); + + // Get the tables hit by the request + String offlineTableName = null; + String realtimeTableName = null; + TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); + if (tableType == TableType.OFFLINE) { + // Offline table + if (_routingManager.routingExists(tableName)) { + offlineTableName = tableName; + } + } else if (tableType == TableType.REALTIME) { + // Realtime table + if (_routingManager.routingExists(tableName)) { + realtimeTableName = tableName; + } + } else { + // Hybrid table (check both OFFLINE and REALTIME) + String offlineTableNameToCheck = TableNameBuilder.OFFLINE.tableNameWithType(tableName); + if (_routingManager.routingExists(offlineTableNameToCheck)) { + offlineTableName = offlineTableNameToCheck; + } + String realtimeTableNameToCheck = TableNameBuilder.REALTIME.tableNameWithType(tableName); + if (_routingManager.routingExists(realtimeTableNameToCheck)) { + realtimeTableName = realtimeTableNameToCheck; + } + } - if (offlineTableName == null && realtimeTableName == null) { - // No table matches the request - if (realtimeTableConfig == null && offlineTableConfig == null) { - LOGGER.info("Table not found for request {}: {}", requestId, query); - requestContext.setErrorCode(QueryException.TABLE_DOES_NOT_EXIST_ERROR_CODE); - return BrokerResponseNative.TABLE_DOES_NOT_EXIST; + TableConfig offlineTableConfig = + _tableCache.getTableConfig(TableNameBuilder.OFFLINE.tableNameWithType(rawTableName)); + TableConfig realtimeTableConfig = + _tableCache.getTableConfig(TableNameBuilder.REALTIME.tableNameWithType(rawTableName)); + + if (offlineTableName == null && realtimeTableName == null) { + // No table matches the request + if (realtimeTableConfig == null && offlineTableConfig == null) { + LOGGER.info("Table not found for request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.TABLE_DOES_NOT_EXIST_ERROR_CODE); + return BrokerResponseNative.TABLE_DOES_NOT_EXIST; + } + LOGGER.info("No table matches for request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.BROKER_RESOURCE_MISSING_ERROR_CODE); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.RESOURCE_MISSING_EXCEPTIONS, 1); + return BrokerResponseNative.NO_TABLE_RESULT; } - LOGGER.info("No table matches for request {}: {}", requestId, query); - requestContext.setErrorCode(QueryException.BROKER_RESOURCE_MISSING_ERROR_CODE); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.RESOURCE_MISSING_EXCEPTIONS, 1); - return BrokerResponseNative.NO_TABLE_RESULT; - } - // Handle query rewrite that can be overridden by the table configs - if (offlineTableName == null) { - offlineTableConfig = null; - } - if (realtimeTableName == null) { - realtimeTableConfig = null; - } - HandlerContext handlerContext = getHandlerContext(offlineTableConfig, realtimeTableConfig); - if (handlerContext._disableGroovy) { - rejectGroovyQuery(serverPinotQuery); - } - if (handlerContext._useApproximateFunction) { - handleApproximateFunctionOverride(serverPinotQuery); - } + // Handle query rewrite that can be overridden by the table configs + if (offlineTableName == null) { + offlineTableConfig = null; + } + if (realtimeTableName == null) { + realtimeTableConfig = null; + } + HandlerContext handlerContext = getHandlerContext(offlineTableConfig, realtimeTableConfig); + if (handlerContext._disableGroovy) { + rejectGroovyQuery(serverPinotQuery); + } + if (handlerContext._useApproximateFunction) { + handleApproximateFunctionOverride(serverPinotQuery); + } - // Validate QPS quota - if (!_queryQuotaManager.acquire(tableName)) { - String errorMessage = - String.format("Request %d: %s exceeds query quota for table: %s", requestId, query, tableName); - LOGGER.info(errorMessage); - requestContext.setErrorCode(QueryException.TOO_MANY_REQUESTS_ERROR_CODE); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_QUOTA_EXCEEDED, 1); - return new BrokerResponseNative(QueryException.getException(QueryException.QUOTA_EXCEEDED_ERROR, errorMessage)); - } + // Validate QPS quota + if (!_queryQuotaManager.acquire(tableName)) { + String errorMessage = + String.format("Request %d: %s exceeds query quota for table: %s", requestId, query, tableName); + LOGGER.info(errorMessage); + requestContext.setErrorCode(QueryException.TOO_MANY_REQUESTS_ERROR_CODE); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_QUOTA_EXCEEDED, 1); + return new BrokerResponseNative(QueryException.getException(QueryException.QUOTA_EXCEEDED_ERROR, errorMessage)); + } - // Validate the request - try { - validateRequest(serverPinotQuery, _queryResponseLimit); - } catch (Exception e) { - LOGGER.info("Caught exception while validating request {}: {}, {}", requestId, query, e.getMessage()); - requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_VALIDATION_EXCEPTIONS, 1); - return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, e)); - } - - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERIES, 1); - _brokerMetrics.addValueToTableGauge(rawTableName, BrokerGauge.REQUEST_SIZE, query.length()); - - // Prepare OFFLINE and REALTIME requests - BrokerRequest offlineBrokerRequest = null; - BrokerRequest realtimeBrokerRequest = null; - TimeBoundaryInfo timeBoundaryInfo = null; - Schema schema = _tableCache.getSchema(rawTableName); - if (offlineTableName != null && realtimeTableName != null) { - // Time boundary info might be null when there is no segment in the offline table, query real-time side only - timeBoundaryInfo = _routingManager.getTimeBoundaryInfo(offlineTableName); - if (timeBoundaryInfo == null) { - LOGGER.debug("No time boundary info found for hybrid table: {}", rawTableName); - offlineTableName = null; - } - } - if (offlineTableName != null && realtimeTableName != null) { - // Hybrid - PinotQuery offlinePinotQuery = serverPinotQuery.deepCopy(); - offlinePinotQuery.getDataSource().setTableName(offlineTableName); - attachTimeBoundary(offlinePinotQuery, timeBoundaryInfo, true); - handleExpressionOverride(offlinePinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); - handleTimestampIndexOverride(offlinePinotQuery, offlineTableConfig); - _queryOptimizer.optimize(offlinePinotQuery, offlineTableConfig, schema); - offlineBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(offlinePinotQuery); - - PinotQuery realtimePinotQuery = serverPinotQuery.deepCopy(); - realtimePinotQuery.getDataSource().setTableName(realtimeTableName); - attachTimeBoundary(realtimePinotQuery, timeBoundaryInfo, false); - handleExpressionOverride(realtimePinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); - handleTimestampIndexOverride(realtimePinotQuery, realtimeTableConfig); - _queryOptimizer.optimize(realtimePinotQuery, realtimeTableConfig, schema); - realtimeBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(realtimePinotQuery); - - requestContext.setFanoutType(RequestContext.FanoutType.HYBRID); - requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); - requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); - } else if (offlineTableName != null) { - // OFFLINE only - setTableName(serverBrokerRequest, offlineTableName); - handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); - handleTimestampIndexOverride(serverPinotQuery, offlineTableConfig); - _queryOptimizer.optimize(serverPinotQuery, offlineTableConfig, schema); - offlineBrokerRequest = serverBrokerRequest; - - requestContext.setFanoutType(RequestContext.FanoutType.OFFLINE); - requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); - } else { - // REALTIME only - setTableName(serverBrokerRequest, realtimeTableName); - handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); - handleTimestampIndexOverride(serverPinotQuery, realtimeTableConfig); - _queryOptimizer.optimize(serverPinotQuery, realtimeTableConfig, schema); - realtimeBrokerRequest = serverBrokerRequest; + // Validate the request + try { + validateRequest(serverPinotQuery, _queryResponseLimit); + } catch (Exception e) { + LOGGER.info("Caught exception while validating request {}: {}, {}", requestId, query, e.getMessage()); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_VALIDATION_EXCEPTIONS, 1); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, e)); + } - requestContext.setFanoutType(RequestContext.FanoutType.REALTIME); - requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); - } + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERIES, 1); + _brokerMetrics.addValueToTableGauge(rawTableName, BrokerGauge.REQUEST_SIZE, query.length()); + + // Prepare OFFLINE and REALTIME requests + BrokerRequest offlineBrokerRequest = null; + BrokerRequest realtimeBrokerRequest = null; + TimeBoundaryInfo timeBoundaryInfo = null; + Schema schema = _tableCache.getSchema(rawTableName); + if (offlineTableName != null && realtimeTableName != null) { + // Time boundary info might be null when there is no segment in the offline table, query real-time side only + timeBoundaryInfo = _routingManager.getTimeBoundaryInfo(offlineTableName); + if (timeBoundaryInfo == null) { + LOGGER.debug("No time boundary info found for hybrid table: {}", rawTableName); + offlineTableName = null; + } + } + if (offlineTableName != null && realtimeTableName != null) { + // Hybrid + PinotQuery offlinePinotQuery = serverPinotQuery.deepCopy(); + offlinePinotQuery.getDataSource().setTableName(offlineTableName); + attachTimeBoundary(offlinePinotQuery, timeBoundaryInfo, true); + handleExpressionOverride(offlinePinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); + handleTimestampIndexOverride(offlinePinotQuery, offlineTableConfig); + _queryOptimizer.optimize(offlinePinotQuery, offlineTableConfig, schema); + offlineBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(offlinePinotQuery); + + PinotQuery realtimePinotQuery = serverPinotQuery.deepCopy(); + realtimePinotQuery.getDataSource().setTableName(realtimeTableName); + attachTimeBoundary(realtimePinotQuery, timeBoundaryInfo, false); + handleExpressionOverride(realtimePinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); + handleTimestampIndexOverride(realtimePinotQuery, realtimeTableConfig); + _queryOptimizer.optimize(realtimePinotQuery, realtimeTableConfig, schema); + realtimeBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(realtimePinotQuery); + + requestContext.setFanoutType(RequestContext.FanoutType.HYBRID); + requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); + requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); + } else if (offlineTableName != null) { + // OFFLINE only + setTableName(serverBrokerRequest, offlineTableName); + handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); + handleTimestampIndexOverride(serverPinotQuery, offlineTableConfig); + _queryOptimizer.optimize(serverPinotQuery, offlineTableConfig, schema); + offlineBrokerRequest = serverBrokerRequest; + + requestContext.setFanoutType(RequestContext.FanoutType.OFFLINE); + requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); + } else { + // REALTIME only + setTableName(serverBrokerRequest, realtimeTableName); + handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); + handleTimestampIndexOverride(serverPinotQuery, realtimeTableConfig); + _queryOptimizer.optimize(serverPinotQuery, realtimeTableConfig, schema); + realtimeBrokerRequest = serverBrokerRequest; + + requestContext.setFanoutType(RequestContext.FanoutType.REALTIME); + requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); + } - // Check if response can be sent without server query evaluation. - if (offlineBrokerRequest != null && isFilterAlwaysFalse(offlineBrokerRequest.getPinotQuery())) { - // We don't need to evaluate offline request - offlineBrokerRequest = null; - } - if (realtimeBrokerRequest != null && isFilterAlwaysFalse(realtimeBrokerRequest.getPinotQuery())) { - // We don't need to evaluate realtime request - realtimeBrokerRequest = null; - } + // Check if response can be sent without server query evaluation. + if (offlineBrokerRequest != null && isFilterAlwaysFalse(offlineBrokerRequest.getPinotQuery())) { + // We don't need to evaluate offline request + offlineBrokerRequest = null; + } + if (realtimeBrokerRequest != null && isFilterAlwaysFalse(realtimeBrokerRequest.getPinotQuery())) { + // We don't need to evaluate realtime request + realtimeBrokerRequest = null; + } - if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { - if (pinotQuery.isExplain()) { - // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. - return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; + if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { + return getEmptyBrokerOnlyResponse(requestId, query, requesterIdentity, requestContext, pinotQuery, tableName); } - // Send empty response since we don't need to evaluate either offline or realtime request. - BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); - // Extract source info from incoming request - _queryLogger.log(new QueryLogger.QueryLogParams(requestId, query, requestContext, tableName, 0, new ServerStats(), - brokerResponse, System.nanoTime(), requesterIdentity)); - return brokerResponse; - } + if (offlineBrokerRequest != null && isFilterAlwaysTrue(offlineBrokerRequest.getPinotQuery())) { + // Drop offline request filter since it is always true + offlineBrokerRequest.getPinotQuery().setFilterExpression(null); + } + if (realtimeBrokerRequest != null && isFilterAlwaysTrue(realtimeBrokerRequest.getPinotQuery())) { + // Drop realtime request filter since it is always true + realtimeBrokerRequest.getPinotQuery().setFilterExpression(null); + } - if (offlineBrokerRequest != null && isFilterAlwaysTrue(offlineBrokerRequest.getPinotQuery())) { - // Drop offline request filter since it is always true - offlineBrokerRequest.getPinotQuery().setFilterExpression(null); - } - if (realtimeBrokerRequest != null && isFilterAlwaysTrue(realtimeBrokerRequest.getPinotQuery())) { - // Drop realtime request filter since it is always true - realtimeBrokerRequest.getPinotQuery().setFilterExpression(null); - } - - // Calculate routing table for the query - // TODO: Modify RoutingManager interface to directly take PinotQuery - long routingStartTimeNs = System.nanoTime(); - Map> offlineRoutingTable = null; - Map> realtimeRoutingTable = null; - List unavailableSegments = new ArrayList<>(); - int numPrunedSegmentsTotal = 0; - if (offlineBrokerRequest != null) { - // NOTE: Routing table might be null if table is just removed - RoutingTable routingTable = _routingManager.getRoutingTable(offlineBrokerRequest, requestId); - if (routingTable != null) { - unavailableSegments.addAll(routingTable.getUnavailableSegments()); - Map> serverInstanceToSegmentsMap = routingTable.getServerInstanceToSegmentsMap(); - if (!serverInstanceToSegmentsMap.isEmpty()) { - offlineRoutingTable = serverInstanceToSegmentsMap; + // Calculate routing table for the query + // TODO: Modify RoutingManager interface to directly take PinotQuery + long routingStartTimeNs = System.nanoTime(); + Map> offlineRoutingTable = null; + Map> realtimeRoutingTable = null; + List unavailableSegments = new ArrayList<>(); + int numPrunedSegmentsTotal = 0; + if (offlineBrokerRequest != null) { + // NOTE: Routing table might be null if table is just removed + RoutingTable routingTable = _routingManager.getRoutingTable(offlineBrokerRequest, requestId); + if (routingTable != null) { + unavailableSegments.addAll(routingTable.getUnavailableSegments()); + Map> serverInstanceToSegmentsMap = routingTable.getServerInstanceToSegmentsMap(); + if (!serverInstanceToSegmentsMap.isEmpty()) { + offlineRoutingTable = serverInstanceToSegmentsMap; + } else { + offlineBrokerRequest = null; + } + numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); } else { offlineBrokerRequest = null; } - numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); - } else { - offlineBrokerRequest = null; } - } - if (realtimeBrokerRequest != null) { - // NOTE: Routing table might be null if table is just removed - RoutingTable routingTable = _routingManager.getRoutingTable(realtimeBrokerRequest, requestId); - if (routingTable != null) { - unavailableSegments.addAll(routingTable.getUnavailableSegments()); - Map> serverInstanceToSegmentsMap = routingTable.getServerInstanceToSegmentsMap(); - if (!serverInstanceToSegmentsMap.isEmpty()) { - realtimeRoutingTable = serverInstanceToSegmentsMap; + if (realtimeBrokerRequest != null) { + // NOTE: Routing table might be null if table is just removed + RoutingTable routingTable = _routingManager.getRoutingTable(realtimeBrokerRequest, requestId); + if (routingTable != null) { + unavailableSegments.addAll(routingTable.getUnavailableSegments()); + Map> serverInstanceToSegmentsMap = routingTable.getServerInstanceToSegmentsMap(); + if (!serverInstanceToSegmentsMap.isEmpty()) { + realtimeRoutingTable = serverInstanceToSegmentsMap; + } else { + realtimeBrokerRequest = null; + } + numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); } else { realtimeBrokerRequest = null; } - numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); - } else { - realtimeBrokerRequest = null; } - } - int numUnavailableSegments = unavailableSegments.size(); - requestContext.setNumUnavailableSegments(numUnavailableSegments); + int numUnavailableSegments = unavailableSegments.size(); + requestContext.setNumUnavailableSegments(numUnavailableSegments); + + List exceptions = new ArrayList<>(); + if (numUnavailableSegments > 0) { + String errorMessage; + if (numUnavailableSegments > MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION) { + errorMessage = String.format("%d segments unavailable, sampling %d: %s", numUnavailableSegments, + MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION, + unavailableSegments.subList(0, MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION)); + } else { + errorMessage = String.format("%d segments unavailable: %s", numUnavailableSegments, unavailableSegments); + } + exceptions.add(QueryException.getException(QueryException.BROKER_SEGMENT_UNAVAILABLE_ERROR, errorMessage)); + } - List exceptions = new ArrayList<>(); - if (numUnavailableSegments > 0) { - String errorMessage; - if (numUnavailableSegments > MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION) { - errorMessage = String.format("%d segments unavailable, sampling %d: %s", numUnavailableSegments, - MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION, - unavailableSegments.subList(0, MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION)); + if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { + if (!exceptions.isEmpty()) { + LOGGER.info("No server found for request {}: {}", requestId, query); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.NO_SERVER_FOUND_EXCEPTIONS, 1); + return new BrokerResponseNative(exceptions); + } else { + // When all segments have been pruned, we can just return an empty response. + return getEmptyBrokerOnlyResponse(requestId, query, requesterIdentity, requestContext, pinotQuery, + tableName); + } + } + long routingEndTimeNs = System.nanoTime(); + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_ROUTING, + routingEndTimeNs - routingStartTimeNs); + + // Set timeout in the requests + long timeSpentMs = TimeUnit.NANOSECONDS.toMillis(routingEndTimeNs - compilationStartTimeNs); + // Remaining time in milliseconds for the server query execution + // NOTE: For hybrid use case, in most cases offline table and real-time table should have the same query timeout + // configured, but if necessary, we also allow different timeout for them. + // If the timeout is not the same for offline table and real-time table, use the max of offline table + // remaining time and realtime table remaining time. Server side will have different remaining time set for + // each table type, and broker should wait for both types to return. + long remainingTimeMs = 0; + try { + if (offlineBrokerRequest != null) { + remainingTimeMs = + setQueryTimeout(offlineTableName, offlineBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs); + } + if (realtimeBrokerRequest != null) { + remainingTimeMs = Math.max(remainingTimeMs, + setQueryTimeout(realtimeTableName, realtimeBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs)); + } + } catch (TimeoutException e) { + String errorMessage = e.getMessage(); + LOGGER.info("{} {}: {}", errorMessage, requestId, query); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.REQUEST_TIMEOUT_BEFORE_SCATTERED_EXCEPTIONS, 1); + exceptions.add(QueryException.getException(QueryException.BROKER_TIMEOUT_ERROR, errorMessage)); + return new BrokerResponseNative(exceptions); + } + + // Execute the query + // TODO: Replace ServerStats with ServerRoutingStatsEntry. + ServerStats serverStats = new ServerStats(); + // TODO: Handle broker specific operations for explain plan queries such as: + // - Alias handling + // - Compile time function invocation + // - Literal only queries + // - Any rewrites + if (pinotQuery.isExplain()) { + // Update routing tables to only send request to offline servers for OFFLINE and HYBRID tables. + // TODO: Assess if the Explain Plan Query should also be routed to REALTIME servers for HYBRID tables + if (offlineRoutingTable != null) { + // For OFFLINE and HYBRID tables, don't send EXPLAIN query to realtime servers. + realtimeBrokerRequest = null; + realtimeRoutingTable = null; + } + } + BrokerResponseNative brokerResponse; + if (_queriesById != null) { + // Start to track the running query for cancellation just before sending it out to servers to avoid any + // potential failures that could happen before sending it out, like failures to calculate the routing table etc. + // TODO: Even tracking the query as late as here, a potential race condition between calling cancel API and + // query being sent out to servers can still happen. If cancel request arrives earlier than query being + // sent out to servers, the servers miss the cancel request and continue to run the queries. The users + // can always list the running queries and cancel query again until it ends. Just that such race + // condition makes cancel API less reliable. This should be rare as it assumes sending queries out to + // servers takes time, but will address later if needed. + _queriesById.put(requestId, new QueryServers(query, offlineRoutingTable, realtimeRoutingTable)); + LOGGER.debug("Keep track of running query: {}", requestId); + try { + brokerResponse = processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, + offlineRoutingTable, realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, + requestContext); + } finally { + _queriesById.remove(requestId); + LOGGER.debug("Remove track of running query: {}", requestId); + } } else { - errorMessage = String.format("%d segments unavailable: %s", numUnavailableSegments, unavailableSegments); + brokerResponse = + processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, + offlineRoutingTable, + realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, requestContext); } - exceptions.add(QueryException.getException(QueryException.BROKER_SEGMENT_UNAVAILABLE_ERROR, errorMessage)); - } - if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { - LOGGER.info("No server found for request {}: {}", requestId, query); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.NO_SERVER_FOUND_EXCEPTIONS, 1); - return new BrokerResponseNative(exceptions); - } - long routingEndTimeNs = System.nanoTime(); - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_ROUTING, routingEndTimeNs - routingStartTimeNs); + brokerResponse.setExceptions(exceptions); + brokerResponse.setNumSegmentsPrunedByBroker(numPrunedSegmentsTotal); + long executionEndTimeNs = System.nanoTime(); + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_EXECUTION, + executionEndTimeNs - routingEndTimeNs); - // Set timeout in the requests - long timeSpentMs = TimeUnit.NANOSECONDS.toMillis(routingEndTimeNs - compilationStartTimeNs); - // Remaining time in milliseconds for the server query execution - // NOTE: For hybrid use case, in most cases offline table and real-time table should have the same query timeout - // configured, but if necessary, we also allow different timeout for them. - // If the timeout is not the same for offline table and real-time table, use the max of offline table - // remaining time and realtime table remaining time. Server side will have different remaining time set for - // each table type, and broker should wait for both types to return. - long remainingTimeMs = 0; - try { - if (offlineBrokerRequest != null) { - remainingTimeMs = - setQueryTimeout(offlineTableName, offlineBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs); + // Track number of queries with number of groups limit reached + if (brokerResponse.isNumGroupsLimitReached()) { + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_NUM_GROUPS_LIMIT_REACHED, + 1); } - if (realtimeBrokerRequest != null) { - remainingTimeMs = Math.max(remainingTimeMs, - setQueryTimeout(realtimeTableName, realtimeBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs)); - } - } catch (TimeoutException e) { - String errorMessage = e.getMessage(); - LOGGER.info("{} {}: {}", errorMessage, requestId, query); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.REQUEST_TIMEOUT_BEFORE_SCATTERED_EXCEPTIONS, 1); - exceptions.add(QueryException.getException(QueryException.BROKER_TIMEOUT_ERROR, errorMessage)); - return new BrokerResponseNative(exceptions); - } - - // Execute the query - // TODO: Replace ServerStats with ServerRoutingStatsEntry. - ServerStats serverStats = new ServerStats(); - // TODO: Handle broker specific operations for explain plan queries such as: - // - Alias handling - // - Compile time function invocation - // - Literal only queries - // - Any rewrites - if (pinotQuery.isExplain()) { - // Update routing tables to only send request to offline servers for OFFLINE and HYBRID tables. - // TODO: Assess if the Explain Plan Query should also be routed to REALTIME servers for HYBRID tables - if (offlineRoutingTable != null) { - // For OFFLINE and HYBRID tables, don't send EXPLAIN query to realtime servers. - realtimeBrokerRequest = null; - realtimeRoutingTable = null; - } - } - BrokerResponseNative brokerResponse; - if (_queriesById != null) { - // Start to track the running query for cancellation just before sending it out to servers to avoid any potential - // failures that could happen before sending it out, like failures to calculate the routing table etc. - // TODO: Even tracking the query as late as here, a potential race condition between calling cancel API and - // query being sent out to servers can still happen. If cancel request arrives earlier than query being - // sent out to servers, the servers miss the cancel request and continue to run the queries. The users - // can always list the running queries and cancel query again until it ends. Just that such race - // condition makes cancel API less reliable. This should be rare as it assumes sending queries out to - // servers takes time, but will address later if needed. - _queriesById.put(requestId, new QueryServers(query, offlineRoutingTable, realtimeRoutingTable)); - LOGGER.debug("Keep track of running query: {}", requestId); - try { - brokerResponse = processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, - offlineRoutingTable, realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, - requestContext); - } finally { - _queriesById.remove(requestId); - LOGGER.debug("Remove track of running query: {}", requestId); - } - } else { - brokerResponse = - processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, offlineRoutingTable, - realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, requestContext); - } - brokerResponse.setExceptions(exceptions); - brokerResponse.setNumSegmentsPrunedByBroker(numPrunedSegmentsTotal); - long executionEndTimeNs = System.nanoTime(); - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_EXECUTION, - executionEndTimeNs - routingEndTimeNs); + // Set total query processing time + long totalTimeMs = TimeUnit.NANOSECONDS.toMillis(executionEndTimeNs - compilationStartTimeNs); + brokerResponse.setTimeUsedMs(totalTimeMs); + requestContext.setQueryProcessingTime(totalTimeMs); + augmentStatistics(requestContext, brokerResponse); + _brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.QUERY_TOTAL_TIME_MS, totalTimeMs, + TimeUnit.MILLISECONDS); - // Track number of queries with number of groups limit reached - if (brokerResponse.isNumGroupsLimitReached()) { - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_NUM_GROUPS_LIMIT_REACHED, 1); + // Extract source info from incoming request + _queryLogger.log( + new QueryLogger.QueryLogParams(requestId, query, requestContext, tableName, numUnavailableSegments, + serverStats, + brokerResponse, totalTimeMs, requesterIdentity)); + + return brokerResponse; + } finally { + Tracing.ThreadAccountantOps.clear(); } + } - // Set total query processing time - long totalTimeMs = TimeUnit.NANOSECONDS.toMillis(executionEndTimeNs - compilationStartTimeNs); - brokerResponse.setTimeUsedMs(totalTimeMs); - requestContext.setQueryProcessingTime(totalTimeMs); - augmentStatistics(requestContext, brokerResponse); - _brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.QUERY_TOTAL_TIME_MS, totalTimeMs, - TimeUnit.MILLISECONDS); + private BrokerResponseNative getEmptyBrokerOnlyResponse(long requestId, String query, + RequesterIdentity requesterIdentity, RequestContext requestContext, PinotQuery pinotQuery, String tableName) { + if (pinotQuery.isExplain()) { + // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. + return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; + } + // Send empty response since we don't need to evaluate either offline or realtime request. + BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); // Extract source info from incoming request - _queryLogger.log( - new QueryLogger.QueryLogParams(requestId, query, requestContext, tableName, numUnavailableSegments, serverStats, - brokerResponse, totalTimeMs, requesterIdentity)); + _queryLogger.log(new QueryLogger.QueryLogParams(requestId, query, requestContext, tableName, 0, new ServerStats(), + brokerResponse, System.nanoTime(), requesterIdentity)); return brokerResponse; } @@ -1322,6 +1374,10 @@ private void computeResultsForLiteral(Literal literal, List columnNames, columnTypes.add(DataSchema.ColumnDataType.BYTES); row.add(BytesUtils.toHexString(literal.getBinaryValue())); break; + case NULL_VALUE: + columnTypes.add(DataSchema.ColumnDataType.UNKNOWN); + row.add(null); + break; default: break; } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java index 3e6a0598bea..360291ec69f 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java @@ -47,15 +47,15 @@ public class BrokerRequestHandlerDelegate implements BrokerRequestHandler { private static final Logger LOGGER = LoggerFactory.getLogger(BrokerRequestHandlerDelegate.class); private final BrokerRequestHandler _singleStageBrokerRequestHandler; - private final BrokerRequestHandler _multiStageWorkerRequestHandler; + private final BrokerRequestHandler _multiStageBrokerRequestHandler; private final BrokerMetrics _brokerMetrics; private final String _brokerId; public BrokerRequestHandlerDelegate(String brokerId, BrokerRequestHandler singleStageBrokerRequestHandler, - @Nullable BrokerRequestHandler multiStageWorkerRequestHandler, BrokerMetrics brokerMetrics) { + @Nullable BrokerRequestHandler multiStageBrokerRequestHandler, BrokerMetrics brokerMetrics) { _brokerId = brokerId; _singleStageBrokerRequestHandler = singleStageBrokerRequestHandler; - _multiStageWorkerRequestHandler = multiStageWorkerRequestHandler; + _multiStageBrokerRequestHandler = multiStageBrokerRequestHandler; _brokerMetrics = brokerMetrics; } @@ -64,8 +64,8 @@ public void start() { if (_singleStageBrokerRequestHandler != null) { _singleStageBrokerRequestHandler.start(); } - if (_multiStageWorkerRequestHandler != null) { - _multiStageWorkerRequestHandler.start(); + if (_multiStageBrokerRequestHandler != null) { + _multiStageBrokerRequestHandler.start(); } } @@ -74,8 +74,8 @@ public void shutDown() { if (_singleStageBrokerRequestHandler != null) { _singleStageBrokerRequestHandler.shutDown(); } - if (_multiStageWorkerRequestHandler != null) { - _multiStageWorkerRequestHandler.shutDown(); + if (_multiStageBrokerRequestHandler != null) { + _multiStageBrokerRequestHandler.shutDown(); } } @@ -99,9 +99,9 @@ public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOption CommonConstants.Broker.Request.QUERY_OPTIONS)); } - if (_multiStageWorkerRequestHandler != null && Boolean.parseBoolean(sqlNodeAndOptions.getOptions().get( + if (_multiStageBrokerRequestHandler != null && Boolean.parseBoolean(sqlNodeAndOptions.getOptions().get( CommonConstants.Broker.Request.QueryOptionKey.USE_MULTISTAGE_ENGINE))) { - return _multiStageWorkerRequestHandler.handleRequest(request, requesterIdentity, requestContext); + return _multiStageBrokerRequestHandler.handleRequest(request, requesterIdentity, requestContext); } else { return _singleStageBrokerRequestHandler.handleRequest(request, sqlNodeAndOptions, requesterIdentity, requestContext); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestIdGenerator.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestIdGenerator.java new file mode 100644 index 00000000000..c97ee44a0af --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestIdGenerator.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.requesthandler; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * An ID generator to produce a global unique identifier for each query, used in v1/v2 engine for tracking and + * inter-stage communication(v2 only). It's guaranteed by: + *

    + *
  1. + * Using a mask computed using the hash-code of the broker-id to ensure two brokers don't arrive at the same + * requestId. This mask becomes the most significant 9 digits (in base-10). + *
  2. + *
  3. + * Using a auto-incrementing counter for the least significant 9 digits (in base-10). + *
  4. + *
+ */ +public class BrokerRequestIdGenerator { + private static final long OFFSET = 1_000_000_000L; + private final long _mask; + private final AtomicLong _incrementingId = new AtomicLong(0); + + public BrokerRequestIdGenerator(String brokerId) { + _mask = ((long) (brokerId.hashCode() & Integer.MAX_VALUE)) * OFFSET; + } + + public long get() { + long normalized = (_incrementingId.getAndIncrement() & Long.MAX_VALUE) % OFFSET; + return _mask + normalized; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java index f5295bad52f..27e48e2ed70 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java @@ -20,10 +20,15 @@ import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; import org.apache.calcite.jdbc.CalciteSchemaBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.pinot.broker.api.RequesterIdentity; @@ -34,29 +39,35 @@ import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.metrics.BrokerQueryPhase; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.BrokerResponseNativeV2; +import org.apache.pinot.common.response.broker.BrokerResponseStats; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.NamedThreadFactory; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.core.query.reduce.ExecutionStatsAggregator; import org.apache.pinot.core.transport.ServerInstance; import org.apache.pinot.query.QueryEnvironment; import org.apache.pinot.query.catalog.PinotCatalog; import org.apache.pinot.query.mailbox.MailboxService; -import org.apache.pinot.query.mailbox.MultiplexingMailboxService; -import org.apache.pinot.query.planner.QueryPlan; +import org.apache.pinot.query.planner.DispatchableSubPlan; import org.apache.pinot.query.routing.WorkerManager; -import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.executor.OpChainSchedulerService; +import org.apache.pinot.query.runtime.executor.RoundRobinScheduler; import org.apache.pinot.query.service.QueryConfig; -import org.apache.pinot.query.service.QueryDispatcher; +import org.apache.pinot.query.service.dispatch.QueryDispatcher; import org.apache.pinot.query.type.TypeFactory; import org.apache.pinot.query.type.TypeSystem; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.exception.BadQueryRequestException; import org.apache.pinot.spi.trace.RequestContext; import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,7 +79,9 @@ public class MultiStageBrokerRequestHandler extends BaseBrokerRequestHandler { private final int _reducerPort; private final long _defaultBrokerTimeoutMs; - private final MailboxService _mailboxService; + private final MailboxService _mailboxService; + private final OpChainSchedulerService _reducerScheduler; + private final QueryEnvironment _queryEnvironment; private final QueryDispatcher _queryDispatcher; @@ -83,12 +96,13 @@ public MultiStageBrokerRequestHandler(PinotConfiguration config, String brokerId // use broker ID as host name, but remove the String brokerId = brokerIdFromConfig; brokerId = brokerId.startsWith(CommonConstants.Helix.PREFIX_OF_BROKER_INSTANCE) ? brokerId.substring( - CommonConstants.Helix.SERVER_INSTANCE_PREFIX_LENGTH) : brokerId; + CommonConstants.Helix.BROKER_INSTANCE_PREFIX_LENGTH) : brokerId; brokerId = StringUtils.split(brokerId, "_").length > 1 ? StringUtils.split(brokerId, "_")[0] : brokerId; reducerHostname = brokerId; } _reducerHostname = reducerHostname; - _reducerPort = config.getProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT, QueryConfig.DEFAULT_QUERY_RUNNER_PORT); + // This config has to be set to a valid port number. + _reducerPort = Integer.parseInt(config.getProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT)); _defaultBrokerTimeoutMs = config.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_TIMEOUT_MS, CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS); _queryEnvironment = new QueryEnvironment(new TypeFactory(new TypeSystem()), @@ -96,12 +110,14 @@ public MultiStageBrokerRequestHandler(PinotConfiguration config, String brokerId new WorkerManager(_reducerHostname, _reducerPort, routingManager), _tableCache); _queryDispatcher = new QueryDispatcher(); - // it is OK to ignore the onDataAvailable callback because the broker top-level operators - // always run in-line (they don't have any scheduler) - _mailboxService = MultiplexingMailboxService.newInstance(_reducerHostname, _reducerPort, config, ignored -> { - }); + long releaseMs = config.getProperty(QueryConfig.KEY_OF_SCHEDULER_RELEASE_TIMEOUT_MS, + QueryConfig.DEFAULT_SCHEDULER_RELEASE_TIMEOUT_MS); + _reducerScheduler = new OpChainSchedulerService(new RoundRobinScheduler(releaseMs), + Executors.newCachedThreadPool(new NamedThreadFactory("query_broker_reducer_" + _reducerPort + "_port"))); + _mailboxService = new MailboxService(_reducerHostname, _reducerPort, config, _reducerScheduler::onDataAvailable); // TODO: move this to a startUp() function. + _reducerScheduler.startAsync(); _mailboxService.start(); } @@ -109,7 +125,7 @@ public MultiStageBrokerRequestHandler(PinotConfiguration config, String brokerId public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) throws Exception { - long requestId = _requestIdGenerator.incrementAndGet(); + long requestId = _brokerIdGenerator.get(); requestContext.setRequestId(requestId); requestContext.setRequestArrivalTimeMillis(System.currentTimeMillis()); @@ -120,7 +136,7 @@ public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOption _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); LOGGER.info("Access denied for requestId {}", requestId); requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); - return new BrokerResponseNative(QueryException.ACCESS_DENIED_ERROR); + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); } JsonNode sql = request.get(CommonConstants.Broker.Request.SQL); @@ -132,15 +148,14 @@ public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOption return handleRequest(requestId, query, sqlNodeAndOptions, request, requesterIdentity, requestContext); } - private BrokerResponseNative handleRequest(long requestId, String query, - @Nullable SqlNodeAndOptions sqlNodeAndOptions, JsonNode request, @Nullable RequesterIdentity requesterIdentity, - RequestContext requestContext) + private BrokerResponse handleRequest(long requestId, String query, @Nullable SqlNodeAndOptions sqlNodeAndOptions, + JsonNode request, @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) throws Exception { LOGGER.debug("SQL query for request {}: {}", requestId, query); long compilationStartTimeNs; long queryTimeoutMs; - QueryPlan queryPlan; + QueryEnvironment.QueryPlannerResult queryPlanResult; try { // Parse the request sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); @@ -150,42 +165,140 @@ private BrokerResponseNative handleRequest(long requestId, String query, compilationStartTimeNs = System.nanoTime(); switch (sqlNodeAndOptions.getSqlNode().getKind()) { case EXPLAIN: - String plan = _queryEnvironment.explainQuery(query, sqlNodeAndOptions); + queryPlanResult = _queryEnvironment.explainQuery(query, sqlNodeAndOptions, requestId); + String plan = queryPlanResult.getExplainPlan(); + Set tableNames = queryPlanResult.getTableNames(); + if (!hasTableAccess(requesterIdentity, tableNames, requestId, requestContext)) { + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); + } + return constructMultistageExplainPlan(query, plan); case SELECT: default: - queryPlan = _queryEnvironment.planQuery(query, sqlNodeAndOptions, requestId); + queryPlanResult = _queryEnvironment.planQuery(query, sqlNodeAndOptions, requestId); break; } } catch (Exception e) { LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); - return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e.getMessage())); + } + + DispatchableSubPlan dispatchableSubPlan = queryPlanResult.getQueryPlan(); + Set tableNames = queryPlanResult.getTableNames(); + + // Compilation Time. This includes the time taken for parsing, compiling, create stage plans and assigning workers. + long compilationEndTimeNs = System.nanoTime(); + long compilationTimeNs = (compilationEndTimeNs - compilationStartTimeNs) + sqlNodeAndOptions.getParseTimeNs(); + updatePhaseTimingForTables(tableNames, BrokerQueryPhase.REQUEST_COMPILATION, compilationTimeNs); + + // Validate table access. + if (!hasTableAccess(requesterIdentity, tableNames, requestId, requestContext)) { + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); + } + updatePhaseTimingForTables(tableNames, BrokerQueryPhase.AUTHORIZATION, System.nanoTime() - compilationEndTimeNs); + + // Validate QPS quota + if (hasExceededQPSQuota(tableNames, requestId, requestContext)) { + String errorMessage = String.format("Request %d: %s exceeds query quota.", requestId, query); + return new BrokerResponseNative(QueryException.getException(QueryException.QUOTA_EXCEEDED_ERROR, errorMessage)); } + boolean traceEnabled = Boolean.parseBoolean( + request.has(CommonConstants.Broker.Request.TRACE) ? request.get(CommonConstants.Broker.Request.TRACE).asText() + : "false"); + ResultTable queryResults; + Map stageIdStatsMap = new HashMap<>(); + for (int stageId = 0; stageId < dispatchableSubPlan.getQueryStageList().size(); stageId++) { + stageIdStatsMap.put(stageId, new ExecutionStatsAggregator(traceEnabled)); + } + + long executionStartTimeNs = System.nanoTime(); try { - queryResults = _queryDispatcher.submitAndReduce(requestId, queryPlan, _mailboxService, queryTimeoutMs, - sqlNodeAndOptions.getOptions()); - } catch (Exception e) { - LOGGER.info("query execution failed", e); - return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); + queryResults = _queryDispatcher.submitAndReduce(requestId, dispatchableSubPlan, _mailboxService, + _reducerScheduler, + queryTimeoutMs, sqlNodeAndOptions.getOptions(), stageIdStatsMap, traceEnabled); + } catch (Throwable t) { + LOGGER.error("query execution failed", t); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, t)); } - BrokerResponseNative brokerResponse = new BrokerResponseNative(); + BrokerResponseNativeV2 brokerResponse = new BrokerResponseNativeV2(); long executionEndTimeNs = System.nanoTime(); + updatePhaseTimingForTables(tableNames, BrokerQueryPhase.QUERY_EXECUTION, executionEndTimeNs - executionStartTimeNs); // Set total query processing time long totalTimeMs = TimeUnit.NANOSECONDS.toMillis( sqlNodeAndOptions.getParseTimeNs() + (executionEndTimeNs - compilationStartTimeNs)); brokerResponse.setTimeUsedMs(totalTimeMs); brokerResponse.setResultTable(queryResults); + brokerResponse.setRequestId(String.valueOf(requestId)); + + for (Map.Entry entry : stageIdStatsMap.entrySet()) { + if (entry.getKey() == 0) { + // Root stats are aggregated and added separately to broker response for backward compatibility + entry.getValue().setStats(brokerResponse); + continue; + } + + BrokerResponseStats brokerResponseStats = new BrokerResponseStats(); + if (!tableNames.isEmpty()) { + //TODO: Only using first table to assign broker metrics + // find a way to split metrics in case of multiple table + String rawTableName = TableNameBuilder.extractRawTableName(tableNames.iterator().next()); + entry.getValue().setStageLevelStats(rawTableName, brokerResponseStats, _brokerMetrics); + } else { + entry.getValue().setStageLevelStats(null, brokerResponseStats, null); + } + brokerResponse.addStageStat(entry.getKey(), brokerResponseStats); + } + requestContext.setQueryProcessingTime(totalTimeMs); augmentStatistics(requestContext, brokerResponse); return brokerResponse; } + /** + * Validates whether the requester has access to all the tables. + */ + private boolean hasTableAccess(RequesterIdentity requesterIdentity, Set tableNames, long requestId, + RequestContext requestContext) { + boolean hasAccess = _accessControlFactory.create().hasAccess(requesterIdentity, tableNames); + if (!hasAccess) { + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); + LOGGER.warn("Access denied for requestId {}", requestId); + requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); + return false; + } + + return true; + } + + /** + * Returns true if the QPS quota of the tables has exceeded. + */ + private boolean hasExceededQPSQuota(Set tableNames, long requestId, RequestContext requestContext) { + for (String tableName : tableNames) { + if (!_queryQuotaManager.acquire(tableName)) { + LOGGER.warn("Request {}: query exceeds quota for table: {}", requestId, tableName); + requestContext.setErrorCode(QueryException.TOO_MANY_REQUESTS_ERROR_CODE); + String rawTableName = TableNameBuilder.extractRawTableName(tableName); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_QUOTA_EXCEEDED, 1); + return true; + } + } + return false; + } + + private void updatePhaseTimingForTables(Set tableNames, BrokerQueryPhase phase, long time) { + for (String tableName : tableNames) { + String rawTableName = TableNameBuilder.extractRawTableName(tableName); + _brokerMetrics.addPhaseTiming(rawTableName, phase, time); + } + } + private BrokerResponseNative constructMultistageExplainPlan(String sql, String plan) { BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); List rows = new ArrayList<>(); @@ -215,5 +328,6 @@ public void start() { public void shutDown() { _queryDispatcher.shutdown(); _mailboxService.shutdown(); + _reducerScheduler.stopAsync(); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java index 883e9cfb026..5ea2ea5373a 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java @@ -43,6 +43,9 @@ import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelectorFactory; import org.apache.pinot.broker.routing.instanceselector.InstanceSelector; import org.apache.pinot.broker.routing.instanceselector.InstanceSelectorFactory; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetchListener; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetcher; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionMetadataManager; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelectorFactory; import org.apache.pinot.broker.routing.segmentpruner.SegmentPruner; @@ -55,14 +58,20 @@ import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.core.routing.RoutingManager; import org.apache.pinot.core.routing.RoutingTable; +import org.apache.pinot.core.routing.TablePartitionInfo; import org.apache.pinot.core.routing.TimeBoundaryInfo; import org.apache.pinot.core.transport.ServerInstance; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; +import org.apache.pinot.spi.config.table.ColumnPartitionConfig; import org.apache.pinot.spi.config.table.QueryConfig; +import org.apache.pinot.spi.config.table.SegmentPartitionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Helix; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; import org.apache.pinot.spi.utils.InstanceTypeUtils; @@ -100,11 +109,16 @@ public class BrokerRoutingManager implements RoutingManager, ClusterChangeHandle private final ServerRoutingStatsManager _serverRoutingStatsManager; private final PinotConfiguration _pinotConfig; + // Map that contains the tableNameWithType as key and the enabled serverInstances that are tagged with the table's + // tenant. + private final Map> _tableTenantServersMap = new ConcurrentHashMap<>(); + private BaseDataAccessor _zkDataAccessor; private String _externalViewPathPrefix; private String _idealStatePathPrefix; private String _instanceConfigsPath; private ZkHelixPropertyStore _propertyStore; + private HelixManager _helixManager; private Set _routableServers; @@ -123,6 +137,7 @@ public void init(HelixManager helixManager) { _idealStatePathPrefix = helixDataAccessor.keyBuilder().idealStates().getPath() + "/"; _instanceConfigsPath = helixDataAccessor.keyBuilder().instanceConfigs().getPath(); _propertyStore = helixManager.getHelixPropertyStore(); + _helixManager = helixManager; } @Override @@ -242,7 +257,8 @@ private void processInstanceConfigChange() { enabledServers.add(instanceId); // Always refresh the server instance with the latest instance config in case it changes - ServerInstance serverInstance = new ServerInstance(new InstanceConfig(instanceConfigZNRecord)); + InstanceConfig instanceConfig = new InstanceConfig(instanceConfigZNRecord); + ServerInstance serverInstance = new ServerInstance(instanceConfig); if (_enabledServerInstanceMap.put(instanceId, serverInstance) == null) { newEnabledServers.add(instanceId); @@ -250,6 +266,8 @@ private void processInstanceConfigChange() { if (_excludedServers.remove(instanceId)) { LOGGER.info("Got excluded server: {} re-enabled, including it into the routing", instanceId); } + + addNewServerToTableTenantServerMap(instanceId, serverInstance, instanceConfig); } } } @@ -257,6 +275,7 @@ private void processInstanceConfigChange() { for (String instance : _enabledServerInstanceMap.keySet()) { if (!enabledServers.contains(instance)) { newDisabledServers.add(instance); + deleteServerFromTableTenantServerMap(instance); } } @@ -406,6 +425,9 @@ public synchronized void buildRouting(String tableNameWithType) { TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, tableNameWithType); Preconditions.checkState(tableConfig != null, "Failed to find table config for table: %s", tableNameWithType); + // Build a mapping from the table to the list of servers assigned to the table's tenant. + buildTableTenantServerMap(tableNameWithType, tableConfig); + String idealStatePath = getIdealStatePath(tableNameWithType); IdealState idealState = getIdealState(idealStatePath); Preconditions.checkState(idealState != null, "Failed to find ideal state for table: %s", tableNameWithType); @@ -430,10 +452,10 @@ public synchronized void buildRouting(String tableNameWithType) { Set preSelectedOnlineSegments = segmentPreSelector.preSelect(onlineSegments); SegmentSelector segmentSelector = SegmentSelectorFactory.getSegmentSelector(tableConfig); segmentSelector.init(idealState, externalView, preSelectedOnlineSegments); + + // Register segment pruners and initialize segment zk metadata fetcher. List segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); - for (SegmentPruner segmentPruner : segmentPruners) { - segmentPruner.init(idealState, externalView, preSelectedOnlineSegments); - } + AdaptiveServerSelector adaptiveServerSelector = AdaptiveServerSelectorFactory.getAdaptiveServerSelector(_serverRoutingStatsManager, _pinotConfig); InstanceSelector instanceSelector = @@ -485,13 +507,41 @@ public synchronized void buildRouting(String tableNameWithType) { } } + SegmentPartitionMetadataManager partitionMetadataManager = null; + // TODO: Support multiple partition columns + // TODO: Make partition pruner on top of the partition metadata manager to avoid keeping 2 copies of the metadata + if (_pinotConfig.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_PARTITION_METADATA_MANAGER, + CommonConstants.Broker.DEFAULT_ENABLE_PARTITION_METADATA_MANAGER)) { + SegmentPartitionConfig segmentPartitionConfig = tableConfig.getIndexingConfig().getSegmentPartitionConfig(); + if (segmentPartitionConfig == null || segmentPartitionConfig.getColumnPartitionMap().size() != 1) { + LOGGER.warn("Cannot enable SegmentPartitionMetadataManager. " + + "Expecting SegmentPartitionConfig with exact 1 partition column"); + } else { + Map.Entry partitionConfig = + segmentPartitionConfig.getColumnPartitionMap().entrySet().iterator().next(); + LOGGER.info("Enabling SegmentPartitionMetadataManager for table: {} on partition column: {}", tableNameWithType, + partitionConfig.getKey()); + partitionMetadataManager = new SegmentPartitionMetadataManager(tableNameWithType, partitionConfig.getKey(), + partitionConfig.getValue().getFunctionName(), partitionConfig.getValue().getNumPartitions()); + } + } + QueryConfig queryConfig = tableConfig.getQueryConfig(); Long queryTimeoutMs = queryConfig != null ? queryConfig.getTimeoutMs() : null; + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(tableNameWithType, _propertyStore); + for (SegmentZkMetadataFetchListener listener : segmentPruners) { + segmentZkMetadataFetcher.register(listener); + } + if (partitionMetadataManager != null) { + segmentZkMetadataFetcher.register(partitionMetadataManager); + } + segmentZkMetadataFetcher.init(idealState, externalView, preSelectedOnlineSegments); + RoutingEntry routingEntry = new RoutingEntry(tableNameWithType, idealStatePath, externalViewPath, segmentPreSelector, segmentSelector, - segmentPruners, instanceSelector, idealStateVersion, externalViewVersion, timeBoundaryManager, - queryTimeoutMs); + segmentPruners, instanceSelector, idealStateVersion, externalViewVersion, segmentZkMetadataFetcher, + timeBoundaryManager, partitionMetadataManager, queryTimeoutMs); if (_routingEntryMap.put(tableNameWithType, routingEntry) == null) { LOGGER.info("Built routing for table: {}", tableNameWithType); } else { @@ -537,6 +587,10 @@ public synchronized void removeRouting(String tableNameWithType) { } else { LOGGER.warn("Routing does not exist for table: {}, skipping removing routing", tableNameWithType); } + + if (_tableTenantServersMap.remove(tableNameWithType) != null) { + LOGGER.info("Removed tenant servers for table: {}", tableNameWithType); + } } /** @@ -595,6 +649,11 @@ public Map getEnabledServerInstanceMap() { return _enabledServerInstanceMap; } + @Override + public Map getEnabledServersForTableTenant(String tableNameWithType) { + return _tableTenantServersMap.getOrDefault(tableNameWithType, Collections.emptyMap()); + } + private String getIdealStatePath(String tableNameWithType) { return _idealStatePathPrefix + tableNameWithType; } @@ -619,6 +678,17 @@ public TimeBoundaryInfo getTimeBoundaryInfo(String offlineTableName) { return timeBoundaryManager != null ? timeBoundaryManager.getTimeBoundaryInfo() : null; } + @Nullable + @Override + public TablePartitionInfo getTablePartitionInfo(String tableNameWithType) { + RoutingEntry routingEntry = _routingEntryMap.get(tableNameWithType); + if (routingEntry == null) { + return null; + } + SegmentPartitionMetadataManager partitionMetadataManager = routingEntry.getPartitionMetadataManager(); + return partitionMetadataManager != null ? partitionMetadataManager.getTablePartitionInfo() : null; + } + /** * Returns the table-level query timeout in milliseconds for the given table, or {@code null} if the timeout is not * configured in the table config. @@ -636,8 +706,10 @@ private static class RoutingEntry { final SegmentPreSelector _segmentPreSelector; final SegmentSelector _segmentSelector; final List _segmentPruners; + final SegmentPartitionMetadataManager _partitionMetadataManager; final InstanceSelector _instanceSelector; final Long _queryTimeoutMs; + final SegmentZkMetadataFetcher _segmentZkMetadataFetcher; // Cache IdealState and ExternalView version for the last update transient int _lastUpdateIdealStateVersion; @@ -648,7 +720,8 @@ private static class RoutingEntry { RoutingEntry(String tableNameWithType, String idealStatePath, String externalViewPath, SegmentPreSelector segmentPreSelector, SegmentSelector segmentSelector, List segmentPruners, InstanceSelector instanceSelector, int lastUpdateIdealStateVersion, int lastUpdateExternalViewVersion, - @Nullable TimeBoundaryManager timeBoundaryManager, @Nullable Long queryTimeoutMs) { + SegmentZkMetadataFetcher segmentZkMetadataFetcher, @Nullable TimeBoundaryManager timeBoundaryManager, + @Nullable SegmentPartitionMetadataManager partitionMetadataManager, @Nullable Long queryTimeoutMs) { _tableNameWithType = tableNameWithType; _idealStatePath = idealStatePath; _externalViewPath = externalViewPath; @@ -659,7 +732,9 @@ private static class RoutingEntry { _lastUpdateIdealStateVersion = lastUpdateIdealStateVersion; _lastUpdateExternalViewVersion = lastUpdateExternalViewVersion; _timeBoundaryManager = timeBoundaryManager; + _partitionMetadataManager = partitionMetadataManager; _queryTimeoutMs = queryTimeoutMs; + _segmentZkMetadataFetcher = segmentZkMetadataFetcher; } String getTableNameWithType() { @@ -683,6 +758,11 @@ TimeBoundaryManager getTimeBoundaryManager() { return _timeBoundaryManager; } + @Nullable + SegmentPartitionMetadataManager getPartitionMetadataManager() { + return _partitionMetadataManager; + } + Long getQueryTimeoutMs() { return _queryTimeoutMs; } @@ -693,10 +773,8 @@ Long getQueryTimeoutMs() { void onAssignmentChange(IdealState idealState, ExternalView externalView) { Set onlineSegments = getOnlineSegments(idealState); Set preSelectedOnlineSegments = _segmentPreSelector.preSelect(onlineSegments); + _segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); _segmentSelector.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); - for (SegmentPruner segmentPruner : _segmentPruners) { - segmentPruner.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); - } _instanceSelector.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); if (_timeBoundaryManager != null) { _timeBoundaryManager.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); @@ -710,9 +788,7 @@ void onInstancesChange(Set enabledInstances, List changedInstanc } void refreshSegment(String segment) { - for (SegmentPruner segmentPruner : _segmentPruners) { - segmentPruner.refreshSegment(segment); - } + _segmentZkMetadataFetcher.refreshSegment(segment); if (_timeBoundaryManager != null) { _timeBoundaryManager.refreshSegment(segment); } @@ -728,8 +804,8 @@ InstanceSelector.SelectionResult calculateRouting(BrokerRequest brokerRequest, l } int numPrunedSegments = numTotalSelectedSegments - selectedSegments.size(); if (!selectedSegments.isEmpty()) { - InstanceSelector.SelectionResult selectionResult = _instanceSelector.select(brokerRequest, - new ArrayList<>(selectedSegments), requestId); + InstanceSelector.SelectionResult selectionResult = + _instanceSelector.select(brokerRequest, new ArrayList<>(selectedSegments), requestId); selectionResult.setNumPrunedSegments(numPrunedSegments); return selectionResult; } else { @@ -737,4 +813,54 @@ InstanceSelector.SelectionResult calculateRouting(BrokerRequest brokerRequest, l } } } + + private void buildTableTenantServerMap(String tableNameWithType, TableConfig tableConfig) { + String serverTag = getServerTagForTable(tableNameWithType, tableConfig); + List allInstanceConfigs = HelixHelper.getInstanceConfigs(_helixManager); + List instanceConfigsWithTag = HelixHelper.getInstancesConfigsWithTag(allInstanceConfigs, serverTag); + Map serverInstances = new HashMap<>(); + for (InstanceConfig serverInstanceConfig : instanceConfigsWithTag) { + serverInstances.put(serverInstanceConfig.getInstanceName(), new ServerInstance(serverInstanceConfig)); + } + _tableTenantServersMap.put(tableNameWithType, serverInstances); + LOGGER.info("Built map for table={} with {} server instances.", tableNameWithType, serverInstances.size()); + } + + private void addNewServerToTableTenantServerMap(String instanceId, ServerInstance serverInstance, + InstanceConfig instanceConfig) { + List tags = instanceConfig.getTags(); + + for (Map.Entry> entry : _tableTenantServersMap.entrySet()) { + String tableNameWithType = entry.getKey(); + TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, tableNameWithType); + String tableServerTag = getServerTagForTable(tableNameWithType, tableConfig); + + Map tenantServerMap = entry.getValue(); + + if (!tenantServerMap.containsKey(instanceId) && tags.contains(tableServerTag)) { + tenantServerMap.put(instanceId, serverInstance); + } + } + } + + private String getServerTagForTable(String tableNameWithType, TableConfig tableConfig) { + String serverTenantName = tableConfig.getTenantConfig().getServer(); + String serverTag; + if (TableNameBuilder.isOfflineTableResource(tableNameWithType)) { + serverTag = TagNameUtils.getOfflineTagForTenant(serverTenantName); + } else { + // Realtime table + serverTag = TagNameUtils.getRealtimeTagForTenant(serverTenantName); + } + + return serverTag; + } + + private void deleteServerFromTableTenantServerMap(String server) { + for (Map.Entry> entry : _tableTenantServersMap.entrySet()) { + if (entry.getValue().remove(server) != null) { + LOGGER.info("Removing entry for server={}, table={}", server, entry.getKey()); + } + } + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java index de5eb9f5300..17c9d5e5b8a 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java @@ -18,10 +18,14 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.HashUtil; @@ -37,40 +41,54 @@ * Step1: Process seg1. Fetch server rankings. Pick the best server. * Step2: Process seg2. Fetch server rankings (could have changed or not since Step 1). Pick the best server. * Step3: Process seg3. Fetch server rankings (could have changed or not since Step 2). Pick the best server. - + * *

If AdaptiveServerSelection is disabled, the selection algorithm will always evenly distribute the traffic to all * replicas of each segment, and will try to select different replica id for each segment. The algorithm is very * light-weight and will do best effort to balance the number of segments served by each selected server instance. */ public class BalancedInstanceSelector extends BaseInstanceSelector { - public BalancedInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); + public BalancedInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock); } @Override - Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions) { + Map select(List segments, int requestId, SegmentStates segmentStates, + Map queryOptions) { Map segmentToSelectedInstanceMap = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); - for (String segment : segments) { - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - // NOTE: enabledInstances can be null when there is no enabled instances for the segment, or the instance selector - // has not been updated (we update all components for routing in sequence) - if (enabledInstances == null) { - continue; + if (_adaptiveServerSelector != null) { + for (String segment : segments) { + List candidates = segmentStates.getCandidates(segment); + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + if (candidates == null) { + continue; + } + List candidateInstances = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + candidateInstances.add(candidate.getInstance()); + } + String selectedInstance = _adaptiveServerSelector.select(candidateInstances); + if (candidates.get(candidateInstances.indexOf(selectedInstance)).isOnline()) { + segmentToSelectedInstanceMap.put(segment, selectedInstance); + } } - - String selectedServer; - if (_adaptiveServerSelector != null) { - selectedServer = _adaptiveServerSelector.select(enabledInstances); - } else { - selectedServer = enabledInstances.get(requestId++ % enabledInstances.size()); + } else { + for (String segment : segments) { + List candidates = segmentStates.getCandidates(segment); + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + if (candidates == null) { + continue; + } + int selectedIdx = requestId++ % candidates.size(); + SegmentInstanceCandidate selectedCandidate = candidates.get(selectedIdx); + if (selectedCandidate.isOnline()) { + segmentToSelectedInstanceMap.put(segment, selectedCandidate.getInstance()); + } } - - segmentToSelectedInstanceMap.put(segment, selectedServer); } - return segmentToSelectedInstanceMap; } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java index 9325035aca1..667cc766d81 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -26,11 +27,18 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import java.util.TreeMap; +import java.util.TreeSet; import javax.annotation.Nullable; +import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; @@ -41,239 +49,348 @@ /** - * Base implementation of instance selector which maintains a map from segment to enabled ONLINE/CONSUMING server + * Base implementation of instance selector. Selector maintains a map from segment to enabled ONLINE/CONSUMING server * instances that serves the segment and a set of unavailable segments (no enabled instance or all enabled instances are - * in ERROR state). + * in OFFLINE/ERROR state). + *

+ * Special handling of new segment: It is common for new segment to be partially available or not available at all in + * all instances. + * 1) We don't report new segment as unavailable segments. + * 2) To avoid creating hotspot instances, unavailable instances for new segment won't be excluded for instance + * selection. When it is selected, we don't serve the new segment. + *

+ * Definition of new segment: + * 1) Segment pushed more than 5 minutes ago. + * - If we first see a segment via initialization, we look up segment push time from zookeeper. + * - If we first see a segment via onAssignmentChange initialization, we use the calling time of onAssignmentChange + * as approximation. + * 2) We retire new segment as old when: + * - The push time is more than 5 minutes ago + * - Any instance for new segment is in ERROR state + * - External view for segment converges with ideal state + * + * Note that this implementation means: + * 1) Inconsistent selection of new segments across queries (some queries will serve new segments and others won't). + * 2) When there is no state update from helix, new segments won't be retired because of the time passing (those with + * push time more than 5 minutes ago). + * TODO: refresh new/old segment state where there is no update from helix for long time. */ abstract class BaseInstanceSelector implements InstanceSelector { private static final Logger LOGGER = LoggerFactory.getLogger(BaseInstanceSelector.class); - // To prevent int overflow, reset the request id once it reaches this value private static final long MAX_REQUEST_ID = 1_000_000_000; - private final String _tableNameWithType; - private final BrokerMetrics _brokerMetrics; - protected final AdaptiveServerSelector _adaptiveServerSelector; + final String _tableNameWithType; + final ZkHelixPropertyStore _propertyStore; + final BrokerMetrics _brokerMetrics; + final AdaptiveServerSelector _adaptiveServerSelector; + final Clock _clock; - // These 4 variables are the cached states to help accelerate the change processing - private Set _enabledInstances; - private Map> _segmentToOnlineInstancesMap; - private Map> _segmentToOfflineInstancesMap; - private Map> _instanceToSegmentsMap; + // These 3 variables are the cached states to help accelerate the change processing + Set _enabledInstances; + // For old segments, all candidates are online + // Reduce this map to reduce garbage + final Map> _oldSegmentCandidatesMap = new HashMap<>(); + Map _newSegmentStateMap; - // These 2 variables are needed for instance selection (multi-threaded), so make them volatile - private volatile Map> _segmentToEnabledInstancesMap; - private volatile Set _unavailableSegments; + // _segmentStates is needed for instance selection (multi-threaded), so it is made volatile. + private volatile SegmentStates _segmentStates; - BaseInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { + BaseInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock) { _tableNameWithType = tableNameWithType; + _propertyStore = propertyStore; _brokerMetrics = brokerMetrics; _adaptiveServerSelector = adaptiveServerSelector; + _clock = clock; } @Override public void init(Set enabledInstances, IdealState idealState, ExternalView externalView, Set onlineSegments) { _enabledInstances = enabledInstances; - int segmentMapCapacity = HashUtil.getHashMapCapacity(onlineSegments.size()); - _segmentToOnlineInstancesMap = new HashMap<>(segmentMapCapacity); - _segmentToOfflineInstancesMap = new HashMap<>(segmentMapCapacity); - _instanceToSegmentsMap = new HashMap<>(); - onAssignmentChange(idealState, externalView, onlineSegments); + Map newSegmentPushTimeMap = getNewSegmentPushTimeMapFromZK(idealState, externalView, onlineSegments); + updateSegmentMaps(idealState, externalView, onlineSegments, newSegmentPushTimeMap); + refreshSegmentStates(); } /** - * {@inheritDoc} - * - *

Updates the cached enabled instances and re-calculates {@code segmentToEnabledInstancesMap} and - * {@code unavailableSegments} based on the cached states. + * Returns whether the instance state is online for routing purpose (ONLINE/CONSUMING). */ - @Override - public void onInstancesChange(Set enabledInstances, List changedInstances) { - _enabledInstances = enabledInstances; + static boolean isOnlineForRouting(@Nullable String state) { + return SegmentStateModel.ONLINE.equals(state) || SegmentStateModel.CONSUMING.equals(state); + } - // Update all segments served by the changed instances - Set segmentsToUpdate = new HashSet<>(); - for (String instance : changedInstances) { - List segments = _instanceToSegmentsMap.get(instance); - if (segments != null) { - segmentsToUpdate.addAll(segments); + /** + * Returns a map from new segment to their push time based on the ZK metadata. + */ + Map getNewSegmentPushTimeMapFromZK(IdealState idealState, ExternalView externalView, + Set onlineSegments) { + List potentialNewSegments = new ArrayList<>(); + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + for (String segment : onlineSegments) { + assert idealStateAssignment.containsKey(segment); + if (isPotentialNewSegment(idealStateAssignment.get(segment), externalViewAssignment.get(segment))) { + potentialNewSegments.add(segment); } } - // Directly return if no segment needs to be updated - if (segmentsToUpdate.isEmpty()) { - return; + // Use push time in ZK metadata to determine whether the potential new segment is newly pushed + Map newSegmentPushTimeMap = new HashMap<>(); + long nowMillis = _clock.millis(); + String segmentZKMetadataPathPrefix = + ZKMetadataProvider.constructPropertyStorePathForResource(_tableNameWithType) + "/"; + List segmentZKMetadataPaths = new ArrayList<>(potentialNewSegments.size()); + for (String segment : potentialNewSegments) { + segmentZKMetadataPaths.add(segmentZKMetadataPathPrefix + segment); + } + List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); + for (ZNRecord record : znRecords) { + if (record == null) { + continue; + } + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(record); + long pushTimeMillis = segmentZKMetadata.getPushTime(); + if (InstanceSelector.isNewSegment(pushTimeMillis, nowMillis)) { + newSegmentPushTimeMap.put(segmentZKMetadata.getSegmentName(), pushTimeMillis); + } } + return newSegmentPushTimeMap; + } - // Update the map from segment to enabled ONLINE/CONSUMING instances and set of unavailable segments (no enabled - // instance or all enabled instances are in ERROR state) - // NOTE: We can directly modify the map because we will only update the values without changing the map entries. - // Because the map is marked as volatile, the running queries (already accessed the map) might use the enabled - // instances either before or after the change, which is okay; the following queries (not yet accessed the map) will - // get the updated value. - Map> segmentToEnabledInstancesMap = _segmentToEnabledInstancesMap; - Set currentUnavailableSegments = _unavailableSegments; - Set newUnavailableSegments = new HashSet<>(); - for (Map.Entry> entry : segmentToEnabledInstancesMap.entrySet()) { - String segment = entry.getKey(); - if (segmentsToUpdate.contains(segment)) { - List enabledInstancesForSegment = - calculateEnabledInstancesForSegment(segment, _segmentToOnlineInstancesMap.get(segment), - newUnavailableSegments); - entry.setValue(enabledInstancesForSegment); - } else { - if (currentUnavailableSegments.contains(segment)) { - newUnavailableSegments.add(segment); + /** + * Returns whether a segment is qualified as a new segment. + * A segment is count as old when: + * - Any instance for the segment is in ERROR state + * - External view for the segment converges with ideal state + */ + static boolean isPotentialNewSegment(Map idealStateInstanceStateMap, + @Nullable Map externalViewInstanceStateMap) { + if (externalViewInstanceStateMap == null) { + return true; + } + boolean hasConverged = true; + // Only track ONLINE/CONSUMING instances within the ideal state + for (Map.Entry entry : idealStateInstanceStateMap.entrySet()) { + if (isOnlineForRouting(entry.getValue())) { + String externalViewState = externalViewInstanceStateMap.get(entry.getKey()); + if (externalViewState == null || externalViewState.equals(SegmentStateModel.OFFLINE)) { + hasConverged = false; + } else if (externalViewState.equals(SegmentStateModel.ERROR)) { + return false; } } } - _unavailableSegments = newUnavailableSegments; + return !hasConverged; } /** - * {@inheritDoc} - * - *

Updates the cached maps ({@code segmentToOnlineInstancesMap}, {@code segmentToOfflineInstancesMap} and - * {@code instanceToSegmentsMap}) and re-calculates {@code segmentToEnabledInstancesMap} and - * {@code unavailableSegments} based on the cached states. + * Returns the online instances for routing purpose. */ - @Override - public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { - _segmentToOnlineInstancesMap.clear(); - _segmentToOfflineInstancesMap.clear(); - _instanceToSegmentsMap.clear(); - - // Update the cached maps - updateSegmentMaps(idealState, externalView, onlineSegments, _segmentToOnlineInstancesMap, - _segmentToOfflineInstancesMap, _instanceToSegmentsMap); - - // Generate a new map from segment to enabled ONLINE/CONSUMING instances and a new set of unavailable segments (no - // enabled instance or all enabled instances are in ERROR state) - Map> segmentToEnabledInstancesMap = - new HashMap<>(HashUtil.getHashMapCapacity(_segmentToOnlineInstancesMap.size())); - Set unavailableSegments = new HashSet<>(); - // NOTE: Put null as the value when there is no enabled instances for a segment so that segmentToEnabledInstancesMap - // always contains all segments. With this, in onInstancesChange() we can directly iterate over - // segmentToEnabledInstancesMap.entrySet() and modify the value without changing the map entries. - for (Map.Entry> entry : _segmentToOnlineInstancesMap.entrySet()) { - String segment = entry.getKey(); - List enabledInstancesForSegment = - calculateEnabledInstancesForSegment(segment, entry.getValue(), unavailableSegments); - segmentToEnabledInstancesMap.put(segment, enabledInstancesForSegment); + static TreeSet getOnlineInstances(Map idealStateInstanceStateMap, + Map externalViewInstanceStateMap) { + TreeSet onlineInstances = new TreeSet<>(); + // Only track ONLINE/CONSUMING instances within the ideal state + for (Map.Entry entry : idealStateInstanceStateMap.entrySet()) { + String instance = entry.getKey(); + // NOTE: DO NOT check if EV matches IS because it is a valid state when EV is CONSUMING while IS is ONLINE + if (isOnlineForRouting(entry.getValue()) && isOnlineForRouting(externalViewInstanceStateMap.get(instance))) { + onlineInstances.add(instance); + } } + return onlineInstances; + } - _segmentToEnabledInstancesMap = segmentToEnabledInstancesMap; - _unavailableSegments = unavailableSegments; + /** + * Converts the given map into a sorted map if needed. + */ + static SortedMap convertToSortedMap(Map map) { + if (map instanceof SortedMap) { + return (SortedMap) map; + } else { + return new TreeMap<>(map); + } } /** - * Updates the segment maps based on the given ideal state, external view and online segments (segments with - * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). + * Updates the segment maps based on the given ideal state, external view, online segments (segments with + * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}) and new segments. + * After this update: + * - Old segments' online instances should be tracked in _oldSegmentCandidatesMap + * - New segments' state (push time and candidate instances) should be tracked in _newSegmentStateMap */ void updateSegmentMaps(IdealState idealState, ExternalView externalView, Set onlineSegments, - Map> segmentToOnlineInstancesMap, Map> segmentToOfflineInstancesMap, - Map> instanceToSegmentsMap) { - // Iterate over the external view instead of the online segments so that the map lookups are performed on the - // HashSet instead of the TreeSet for performance - // NOTE: Do not track segments not in the external view because it is a valid state when the segment is new added - Map> idealStateAssignment = idealState.getRecord().getMapFields(); - for (Map.Entry> entry : externalView.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); + Map newSegmentPushTimeMap) { + _oldSegmentCandidatesMap.clear(); + _newSegmentStateMap = new HashMap<>(HashUtil.getHashMapCapacity(newSegmentPushTimeMap.size())); - // Only track online segments - if (!onlineSegments.contains(segment)) { - continue; + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + for (String segment : onlineSegments) { + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Long newSegmentPushTimeMillis = newSegmentPushTimeMap.get(segment); + Map externalViewInstanceStateMap = externalViewAssignment.get(segment); + if (externalViewInstanceStateMap == null) { + if (newSegmentPushTimeMillis != null) { + // New segment + List candidates = new ArrayList<>(idealStateInstanceStateMap.size()); + for (Map.Entry entry : convertToSortedMap(idealStateInstanceStateMap).entrySet()) { + if (isOnlineForRouting(entry.getValue())) { + candidates.add(new SegmentInstanceCandidate(entry.getKey(), false)); + } + } + _newSegmentStateMap.put(segment, new NewSegmentState(newSegmentPushTimeMillis, candidates)); + } else { + // Old segment + _oldSegmentCandidatesMap.put(segment, Collections.emptyList()); + } + } else { + TreeSet onlineInstances = getOnlineInstances(idealStateInstanceStateMap, externalViewInstanceStateMap); + if (newSegmentPushTimeMillis != null) { + // New segment + List candidates = new ArrayList<>(idealStateInstanceStateMap.size()); + for (Map.Entry entry : convertToSortedMap(idealStateInstanceStateMap).entrySet()) { + if (isOnlineForRouting(entry.getValue())) { + String instance = entry.getKey(); + candidates.add(new SegmentInstanceCandidate(instance, onlineInstances.contains(instance))); + } + } + _newSegmentStateMap.put(segment, new NewSegmentState(newSegmentPushTimeMillis, candidates)); + } else { + // Old segment + List candidates = new ArrayList<>(onlineInstances.size()); + for (String instance : onlineInstances) { + candidates.add(new SegmentInstanceCandidate(instance, true)); + } + _oldSegmentCandidatesMap.put(segment, candidates); + } } + } + } - Map externalViewInstanceStateMap = entry.getValue(); - Map idealStateInstanceStateMap = idealStateAssignment.get(segment); - List onlineInstances = new ArrayList<>(externalViewInstanceStateMap.size()); - List offlineInstances = new ArrayList<>(); - segmentToOnlineInstancesMap.put(segment, onlineInstances); - segmentToOfflineInstancesMap.put(segment, offlineInstances); - for (Map.Entry instanceStateEntry : externalViewInstanceStateMap.entrySet()) { - String instance = instanceStateEntry.getKey(); + /** + * Refreshes the _segmentStates based on the in-memory states. + * Note that the whole _segmentStates has to be updated together to avoid partial state update. + **/ + void refreshSegmentStates() { + Map> instanceCandidatesMap = + new HashMap<>(HashUtil.getHashMapCapacity(_oldSegmentCandidatesMap.size() + _newSegmentStateMap.size())); + Set unavailableSegments = new HashSet<>(); - // Only track instances within the ideal state - // NOTE: When an instance is not in the ideal state, the instance will drop the segment soon, and it is not safe - // to query this instance for the segment. This could happen when a segment is moved from one instance to - // another instance. - if (!idealStateInstanceStateMap.containsKey(instance)) { - continue; + for (Map.Entry> entry : _oldSegmentCandidatesMap.entrySet()) { + String segment = entry.getKey(); + List candidates = entry.getValue(); + List enabledCandidates = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + if (_enabledInstances.contains(candidate.getInstance())) { + enabledCandidates.add(candidate); } - - String externalViewState = instanceStateEntry.getValue(); - // Do not track instances in ERROR state - if (!externalViewState.equals(SegmentStateModel.ERROR)) { - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); - if (externalViewState.equals(SegmentStateModel.OFFLINE)) { - offlineInstances.add(instance); - } else { - onlineInstances.add(instance); - } + } + if (!enabledCandidates.isEmpty()) { + instanceCandidatesMap.put(segment, enabledCandidates); + } else { + List candidateInstances = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + candidateInstances.add(candidate.getInstance()); } + LOGGER.warn( + "Failed to find servers hosting segment: {} for table: {} (all candidate instances: {} are disabled, " + + "counting segment as unavailable)", segment, _tableNameWithType, candidateInstances); + unavailableSegments.add(segment); + _brokerMetrics.addMeteredTableValue(_tableNameWithType, BrokerMeter.NO_SERVING_HOST_FOR_SEGMENT, 1); } + } - // Sort the online instances for replica-group routing to work. For multiple segments with the same online - // instances, if the list is sorted, the same index in the list will always point to the same instance. - if (!(externalViewInstanceStateMap instanceof SortedMap)) { - onlineInstances.sort(null); - offlineInstances.sort(null); + for (Map.Entry entry : _newSegmentStateMap.entrySet()) { + String segment = entry.getKey(); + NewSegmentState newSegmentState = entry.getValue(); + List candidates = newSegmentState.getCandidates(); + List enabledCandidates = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + if (_enabledInstances.contains(candidate.getInstance())) { + enabledCandidates.add(candidate); + } } + if (!enabledCandidates.isEmpty()) { + instanceCandidatesMap.put(segment, enabledCandidates); + } + // Do not count new segment as unavailable } + + _segmentStates = new SegmentStates(instanceCandidatesMap, unavailableSegments); } /** - * Calculates the enabled ONLINE/CONSUMING instances for the given segment, and updates the unavailable segments (no - * enabled instance or all enabled instances are in ERROR state). + * {@inheritDoc} + * + *

Updates the cached enabled instances and re-calculates {@code segmentToEnabledInstancesMap} and + * {@code unavailableSegments} based on the cached states. */ - @Nullable - private List calculateEnabledInstancesForSegment(String segment, List onlineInstancesForSegment, - Set unavailableSegments) { - List enabledInstancesForSegment = new ArrayList<>(onlineInstancesForSegment.size()); - for (String onlineInstance : onlineInstancesForSegment) { - if (_enabledInstances.contains(onlineInstance)) { - enabledInstancesForSegment.add(onlineInstance); + @Override + public void onInstancesChange(Set enabledInstances, List changedInstances) { + _enabledInstances = enabledInstances; + refreshSegmentStates(); + } + + /** + * {@inheritDoc} + * + *

Updates the cached maps ({@code segmentToOnlineInstancesMap}, {@code segmentToOfflineInstancesMap} and + * {@code instanceToSegmentsMap}) and re-calculates {@code segmentToEnabledInstancesMap} and + * {@code unavailableSegments} based on the cached states. + */ + @Override + public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { + Map newSegmentPushTimeMap = + getNewSegmentPushTimeMapFromExistingStates(idealState, externalView, onlineSegments); + updateSegmentMaps(idealState, externalView, onlineSegments, newSegmentPushTimeMap); + refreshSegmentStates(); + } + + /** + * Returns a map from new segment to their push time based on the existing in-memory states. + */ + Map getNewSegmentPushTimeMapFromExistingStates(IdealState idealState, ExternalView externalView, + Set onlineSegments) { + Map newSegmentPushTimeMap = new HashMap<>(); + long nowMillis = _clock.millis(); + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + for (String segment : onlineSegments) { + NewSegmentState newSegmentState = _newSegmentStateMap.get(segment); + long pushTimeMillis = 0; + if (newSegmentState != null) { + // It was a new segment before, check the push time and segment state to see if it is still a new segment + if (InstanceSelector.isNewSegment(newSegmentState.getPushTimeMillis(), nowMillis)) { + pushTimeMillis = newSegmentState.getPushTimeMillis(); + } + } else if (!_oldSegmentCandidatesMap.containsKey(segment)) { + // This is the first time we see this segment, use the current time as the push time + pushTimeMillis = nowMillis; } - } - if (!enabledInstancesForSegment.isEmpty()) { - return enabledInstancesForSegment; - } else { - // NOTE: When there are enabled instances in OFFLINE state, we don't count the segment as unavailable because it - // is a valid state when the segment is new added. - List offlineInstancesForSegment = _segmentToOfflineInstancesMap.get(segment); - for (String offlineInstance : offlineInstancesForSegment) { - if (_enabledInstances.contains(offlineInstance)) { - LOGGER.info( - "Failed to find servers hosting segment: {} for table: {} (all ONLINE/CONSUMING instances: {} are " - + "disabled, but find enabled OFFLINE instance: {} from OFFLINE instances: {}, not counting the " - + "segment as unavailable)", segment, _tableNameWithType, onlineInstancesForSegment, offlineInstance, - offlineInstancesForSegment); - return null; + // For recently pushed segment, check if it is qualified as new segment + if (pushTimeMillis > 0) { + assert idealStateAssignment.containsKey(segment); + if (isPotentialNewSegment(idealStateAssignment.get(segment), externalViewAssignment.get(segment))) { + newSegmentPushTimeMap.put(segment, pushTimeMillis); } } - LOGGER.warn( - "Failed to find servers hosting segment: {} for table: {} (all ONLINE/CONSUMING instances: {} and OFFLINE " - + "instances: {} are disabled, counting segment as unavailable)", segment, _tableNameWithType, - onlineInstancesForSegment, offlineInstancesForSegment); - unavailableSegments.add(segment); - _brokerMetrics.addMeteredTableValue(_tableNameWithType, BrokerMeter.NO_SERVING_HOST_FOR_SEGMENT, 1); - return null; } + return newSegmentPushTimeMap; } @Override public SelectionResult select(BrokerRequest brokerRequest, List segments, long requestId) { - Map queryOptions = (brokerRequest.getPinotQuery() != null - && brokerRequest.getPinotQuery().getQueryOptions() != null) - ? brokerRequest.getPinotQuery().getQueryOptions() - : Collections.emptyMap(); + Map queryOptions = + (brokerRequest.getPinotQuery() != null && brokerRequest.getPinotQuery().getQueryOptions() != null) + ? brokerRequest.getPinotQuery().getQueryOptions() : Collections.emptyMap(); int requestIdInt = (int) (requestId % MAX_REQUEST_ID); - Map segmentToInstanceMap = select(segments, requestIdInt, _segmentToEnabledInstancesMap, - queryOptions); - Set unavailableSegments = _unavailableSegments; + // Copy the volatile reference so that segmentToInstanceMap and unavailableSegments can have a consistent view of + // the state. + SegmentStates segmentStates = _segmentStates; + Map segmentToInstanceMap = select(segments, requestIdInt, segmentStates, queryOptions); + Set unavailableSegments = segmentStates.getUnavailableSegments(); if (unavailableSegments.isEmpty()) { return new SelectionResult(segmentToInstanceMap, Collections.emptyList()); } else { @@ -288,11 +405,9 @@ public SelectionResult select(BrokerRequest brokerRequest, List segments } /** - * Selects the server instances for the given segments based on the request id and segment to enabled ONLINE/CONSUMING - * instances map, returns a map from segment to selected server instance hosting the segment. - *

NOTE: {@code segmentToEnabledInstancesMap} might contain {@code null} values (segment with no enabled - * ONLINE/CONSUMING instances). If enabled instances are not {@code null}, they are sorted in alphabetical order. + * Selects the server instances for the given segments based on the request id and segment states. Returns a map + * from segment to selected server instance hosting the segment. */ - abstract Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions); + abstract Map select(List segments, int requestId, SegmentStates segmentStates, + Map queryOptions); } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java index 4c96007fd62..12017c56c28 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; @@ -31,6 +32,11 @@ * The instance selector selects server instances to serve the query based on the selected segments. */ public interface InstanceSelector { + long NEW_SEGMENT_EXPIRATION_MILLIS = TimeUnit.MINUTES.toMillis(5); + + static boolean isNewSegment(long pushMillis, long nowMillis) { + return nowMillis - pushMillis <= NEW_SEGMENT_EXPIRATION_MILLIS; + } /** * Initializes the instance selector with the enabled instances, ideal state, external view and online segments diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java index 8cc9f260f88..2428df02e54 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import javax.annotation.Nullable; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; @@ -39,32 +40,44 @@ private InstanceSelectorFactory() { public static final String LEGACY_REPLICA_GROUP_OFFLINE_ROUTING = "PartitionAwareOffline"; public static final String LEGACY_REPLICA_GROUP_REALTIME_ROUTING = "PartitionAwareRealtime"; + public static InstanceSelector getInstanceSelector(TableConfig tableConfig, + ZkHelixPropertyStore propertyStore, BrokerMetrics brokerMetrics) { + return getInstanceSelector(tableConfig, propertyStore, brokerMetrics, null, Clock.systemUTC()); + } + public static InstanceSelector getInstanceSelector(TableConfig tableConfig, ZkHelixPropertyStore propertyStore, BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector) { + return getInstanceSelector(tableConfig, propertyStore, brokerMetrics, adaptiveServerSelector, Clock.systemUTC()); + } + + public static InstanceSelector getInstanceSelector(TableConfig tableConfig, + ZkHelixPropertyStore propertyStore, BrokerMetrics brokerMetrics, + @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock) { String tableNameWithType = tableConfig.getTableName(); RoutingConfig routingConfig = tableConfig.getRoutingConfig(); if (routingConfig != null) { if (RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE.equalsIgnoreCase(routingConfig.getInstanceSelectorType()) - || (tableConfig.getTableType() == TableType.OFFLINE && LEGACY_REPLICA_GROUP_OFFLINE_ROUTING - .equalsIgnoreCase(routingConfig.getRoutingTableBuilderName())) || ( - tableConfig.getTableType() == TableType.REALTIME && LEGACY_REPLICA_GROUP_REALTIME_ROUTING - .equalsIgnoreCase(routingConfig.getRoutingTableBuilderName()))) { + || (tableConfig.getTableType() == TableType.OFFLINE && LEGACY_REPLICA_GROUP_OFFLINE_ROUTING.equalsIgnoreCase( + routingConfig.getRoutingTableBuilderName())) || (tableConfig.getTableType() == TableType.REALTIME + && LEGACY_REPLICA_GROUP_REALTIME_ROUTING.equalsIgnoreCase(routingConfig.getRoutingTableBuilderName()))) { LOGGER.info("Using ReplicaGroupInstanceSelector for table: {}", tableNameWithType); - return new ReplicaGroupInstanceSelector(tableNameWithType, brokerMetrics, adaptiveServerSelector); + return new ReplicaGroupInstanceSelector(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, + clock); } - if (RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE - .equalsIgnoreCase(routingConfig.getInstanceSelectorType())) { + if (RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE.equalsIgnoreCase( + routingConfig.getInstanceSelectorType())) { LOGGER.info("Using StrictReplicaGroupInstanceSelector for table: {}", tableNameWithType); - return new StrictReplicaGroupInstanceSelector(tableNameWithType, brokerMetrics, adaptiveServerSelector); + return new StrictReplicaGroupInstanceSelector(tableNameWithType, propertyStore, brokerMetrics, + adaptiveServerSelector, clock); } if (RoutingConfig.MULTI_STAGE_REPLICA_GROUP_SELECTOR_TYPE.equalsIgnoreCase( routingConfig.getInstanceSelectorType())) { LOGGER.info("Using {} for table: {}", routingConfig.getInstanceSelectorType(), tableNameWithType); return new MultiStageReplicaGroupSelector(tableNameWithType, propertyStore, brokerMetrics, - adaptiveServerSelector); + adaptiveServerSelector, clock); } } - return new BalancedInstanceSelector(tableNameWithType, brokerMetrics, adaptiveServerSelector); + return new BalancedInstanceSelector(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java index 0a6d66510cc..9be701ebe51 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java @@ -20,6 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import java.time.Clock; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -53,15 +54,11 @@ public class MultiStageReplicaGroupSelector extends BaseInstanceSelector { private static final Logger LOGGER = LoggerFactory.getLogger(MultiStageReplicaGroupSelector.class); - private final String _tableNameWithType; - private final ZkHelixPropertyStore _propertyStore; - private InstancePartitions _instancePartitions; + private volatile InstancePartitions _instancePartitions; public MultiStageReplicaGroupSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, - BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); - _tableNameWithType = tableNameWithType; - _propertyStore = propertyStore; + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock); } @Override @@ -84,28 +81,28 @@ public void onAssignmentChange(IdealState idealState, ExternalView externalView, } @Override - Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions) { + Map select(List segments, int requestId, SegmentStates segmentStates, + Map queryOptions) { // Create a copy of InstancePartitions to avoid race-condition with event-listeners above. InstancePartitions instancePartitions = _instancePartitions; int replicaGroupSelected = requestId % instancePartitions.getNumReplicaGroups(); for (int iteration = 0; iteration < instancePartitions.getNumReplicaGroups(); iteration++) { int replicaGroup = (replicaGroupSelected + iteration) % instancePartitions.getNumReplicaGroups(); try { - return tryAssigning(segmentToEnabledInstancesMap, instancePartitions, replicaGroup); + return tryAssigning(segments, segmentStates, instancePartitions, replicaGroup); } catch (Exception e) { LOGGER.warn("Unable to select replica-group {} for table: {}", replicaGroup, _tableNameWithType, e); } } - throw new RuntimeException(String.format("Unable to find any replica-group to serve table: %s", - _tableNameWithType)); + throw new RuntimeException( + String.format("Unable to find any replica-group to serve table: %s", _tableNameWithType)); } /** * Returns a map from the segmentName to the corresponding server in the given replica-group. If the is not enabled, * we throw an exception. */ - private Map tryAssigning(Map> segmentToEnabledInstancesMap, + private Map tryAssigning(List segments, SegmentStates segmentStates, InstancePartitions instancePartitions, int replicaId) { Set instanceLookUpSet = new HashSet<>(); for (int partition = 0; partition < instancePartitions.getNumPartitions(); partition++) { @@ -113,18 +110,23 @@ private Map tryAssigning(Map> segmentToEnab instanceLookUpSet.addAll(instances); } Map result = new HashMap<>(); - for (Map.Entry> entry : segmentToEnabledInstancesMap.entrySet()) { - String segmentName = entry.getKey(); + for (String segment : segments) { + List candidates = segmentStates.getCandidates(segment); + // If candidates are null, we will throw an exception and log a warning. + Preconditions.checkState(candidates != null, "Failed to find servers for segment: %s", segment); boolean found = false; - for (String enabledInstanceForSegment : entry.getValue()) { - if (instanceLookUpSet.contains(enabledInstanceForSegment)) { + for (SegmentInstanceCandidate candidate : candidates) { + String instance = candidate.getInstance(); + if (instanceLookUpSet.contains(instance)) { found = true; - result.put(segmentName, enabledInstanceForSegment); + if (candidate.isOnline()) { + result.put(segment, instance); + } break; } } if (!found) { - throw new RuntimeException(String.format("Unable to find an enabled instance for segment: %s", segmentName)); + throw new RuntimeException(String.format("Unable to find an enabled instance for segment: %s", segment)); } } return result; @@ -135,7 +137,7 @@ protected InstancePartitions getInstancePartitions() { // TODO: Evaluate whether we need to provide support for COMPLETE partitions. TableType tableType = TableNameBuilder.getTableTypeFromTableName(_tableNameWithType); Preconditions.checkNotNull(tableType); - InstancePartitions instancePartitions = null; + InstancePartitions instancePartitions; if (tableType.equals(TableType.OFFLINE)) { instancePartitions = InstancePartitionsUtils.fetchInstancePartitions(_propertyStore, InstancePartitionsUtils.getInstancePartitionsName(_tableNameWithType, tableType.name())); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/NewSegmentState.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/NewSegmentState.java new file mode 100644 index 00000000000..c6c706e5a10 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/NewSegmentState.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.instanceselector; + +import java.util.List; +import javax.annotation.concurrent.Immutable; + + +/** + * Contains the push time and candidate instances for a new segment. + */ +@Immutable +public class NewSegmentState { + // Segment push time. This could be + // 1) From ZK if we first see this segment via init call. + // 2) Use wall time, if first see this segment from onAssignmentChange call. + private final long _pushTimeMillis; + + // List of SegmentInstanceCandidate: which contains instance name and online flags. + // The candidates have to be in instance sorted order. + private final List _candidates; + + public NewSegmentState(long pushTimeMillis, List candidates) { + _pushTimeMillis = pushTimeMillis; + _candidates = candidates; + } + + public long getPushTimeMillis() { + return _pushTimeMillis; + } + + public List getCandidates() { + return _candidates; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java index 957e140c8e6..9aedaa8e664 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -26,6 +27,8 @@ import java.util.Set; import javax.annotation.Nullable; import org.apache.commons.lang3.tuple.Pair; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.HashUtil; @@ -60,20 +63,18 @@ */ public class ReplicaGroupInstanceSelector extends BaseInstanceSelector { - public ReplicaGroupInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); + public ReplicaGroupInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock); } @Override - Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions) { - Map segmentToSelectedInstanceMap = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); - + Map select(List segments, int requestId, SegmentStates segmentStates, + Map queryOptions) { if (_adaptiveServerSelector != null) { // Adaptive Server Selection is enabled. List serverRankList = new ArrayList<>(); - List candidateServers = fetchCandidateServersForQuery(segments, segmentToEnabledInstancesMap); + List candidateServers = fetchCandidateServersForQuery(segments, segmentStates); // Fetch serverRankList before looping through all the segments. This is important to make sure that we pick // the least amount of instances for a query by referring to a single snapshot of the rankings. @@ -82,97 +83,94 @@ Map select(List segments, int requestId, for (Pair entry : serverRankListWithScores) { serverRankList.add(entry.getLeft()); } - - selectServersUsingAdaptiverServerSelector(segments, requestId, segmentToSelectedInstanceMap, - segmentToEnabledInstancesMap, queryOptions, serverRankList); + return selectServersUsingAdaptiveServerSelector(segments, requestId, segmentStates, serverRankList); } else { // Adaptive Server Selection is NOT enabled. - selectServersUsingRoundRobin(segments, requestId, segmentToSelectedInstanceMap, segmentToEnabledInstancesMap, - queryOptions); + return selectServersUsingRoundRobin(segments, requestId, segmentStates, queryOptions); } - - return segmentToSelectedInstanceMap; } - private void selectServersUsingRoundRobin(List segments, int requestId, - Map segmentToSelectedInstanceMap, Map> segmentToEnabledInstancesMap, - Map queryOptions) { + private Map selectServersUsingRoundRobin(List segments, int requestId, + SegmentStates segmentStates, Map queryOptions) { + Map selectedServers = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); + Integer numReplicaGroupsToQuery = QueryOptionsUtils.getNumReplicaGroupsToQuery(queryOptions); + int numReplicaGroups = numReplicaGroupsToQuery == null ? 1 : numReplicaGroupsToQuery; int replicaOffset = 0; - Integer replicaGroup = QueryOptionsUtils.getNumReplicaGroupsToQuery(queryOptions); - int numReplicaGroupsToQuery = replicaGroup == null ? 1 : replicaGroup; - for (String segment : segments) { - // NOTE: enabledInstances can be null when there is no enabled instances for the segment, or the instance selector - // has not been updated (we update all components for routing in sequence) - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - if (enabledInstances == null) { + List candidates = segmentStates.getCandidates(segment); + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + if (candidates == null) { continue; } - // Round robin selection. - int numEnabledInstances = enabledInstances.size(); - int instanceIdx = (requestId + replicaOffset) % numEnabledInstances; - String selectedInstance = enabledInstances.get(instanceIdx); - - if (numReplicaGroupsToQuery > numEnabledInstances) { - numReplicaGroupsToQuery = numEnabledInstances; + int numCandidates = candidates.size(); + int instanceIdx = (requestId + replicaOffset) % numCandidates; + SegmentInstanceCandidate selectedInstance = candidates.get(instanceIdx); + // Only put online instance. + // This can only be offline when it is a new segment. + if (selectedInstance.isOnline()) { + selectedServers.put(segment, selectedInstance.getInstance()); + } + if (numReplicaGroups > numCandidates) { + numReplicaGroups = numCandidates; } - segmentToSelectedInstanceMap.put(segment, selectedInstance); - replicaOffset = (replicaOffset + 1) % numReplicaGroupsToQuery; + replicaOffset = (replicaOffset + 1) % numReplicaGroups; } + return selectedServers; } - private void selectServersUsingAdaptiverServerSelector(List segments, int requestId, - Map segmentToSelectedInstanceMap, Map> segmentToEnabledInstancesMap, - Map queryOptions, List serverRankList) { + private Map selectServersUsingAdaptiveServerSelector(List segments, int requestId, + SegmentStates segmentStates, List serverRankList) { + Map selectedServers = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); for (String segment : segments) { - // NOTE: enabledInstances can be null when there is no enabled instances for the segment, or the instance selector - // has not been updated (we update all components for routing in sequence) - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - if (enabledInstances == null) { + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + List candidates = segmentStates.getCandidates(segment); + if (candidates == null) { continue; } - // Round Robin. - int numEnabledInstances = enabledInstances.size(); - int instanceIdx = requestId % numEnabledInstances; - String selectedInstance = enabledInstances.get(instanceIdx); - + int numCandidates = candidates.size(); + int instanceIdx = requestId % numCandidates; + SegmentInstanceCandidate selectedInstance = candidates.get(instanceIdx); // Adaptive Server Selection // TODO: Support numReplicaGroupsToQuery with Adaptive Server Selection. - if (serverRankList.size() > 0) { + if (!serverRankList.isEmpty()) { int minIdx = Integer.MAX_VALUE; - for (int i = 0; i < numEnabledInstances; i++) { - int idx = serverRankList.indexOf(enabledInstances.get(i)); + for (SegmentInstanceCandidate candidate : candidates) { + int idx = serverRankList.indexOf(candidate.getInstance()); if (idx == -1) { // Let's use the round-robin approach until stats for all servers are populated. - selectedInstance = enabledInstances.get(instanceIdx); + selectedInstance = candidates.get(instanceIdx); break; } if (idx < minIdx) { minIdx = idx; - selectedInstance = enabledInstances.get(i); + selectedInstance = candidate; } } } - - segmentToSelectedInstanceMap.put(segment, selectedInstance); + // Only put online instance. + // This can only be offline when it is a new segment. + if (selectedInstance.isOnline()) { + selectedServers.put(segment, selectedInstance.getInstance()); + } } + return selectedServers; } - private List fetchCandidateServersForQuery(List segments, - Map> segmentToEnabledInstancesMap) { - List serversList = new ArrayList<>(); - - Set tempServerSet = new HashSet<>(); + private List fetchCandidateServersForQuery(List segments, SegmentStates segmentStates) { + Set candidateServers = new HashSet<>(); for (String segment : segments) { - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - if (enabledInstances != null) { - tempServerSet.addAll(enabledInstances); + List candidates = segmentStates.getCandidates(segment); + if (candidates == null) { + continue; + } + for (SegmentInstanceCandidate candidate : candidates) { + candidateServers.add(candidate.getInstance()); } } - - serversList.addAll(tempServerSet); - return serversList; + return new ArrayList<>(candidateServers); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentInstanceCandidate.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentInstanceCandidate.java new file mode 100644 index 00000000000..97c8f08f6b1 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentInstanceCandidate.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.instanceselector; + +import javax.annotation.concurrent.Immutable; + + +/** + * Represents an instance candidate for a segment. + */ +@Immutable +public class SegmentInstanceCandidate { + private final String _instance; + private final boolean _online; + + public SegmentInstanceCandidate(String instance, boolean online) { + _instance = instance; + _online = online; + } + + public String getInstance() { + return _instance; + } + + public boolean isOnline() { + return _online; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentStates.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentStates.java new file mode 100644 index 00000000000..2f80d843e70 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentStates.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.instanceselector; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + + +/** + * The {@code SegmentStates} contains the candidate instances for each segment, and the unavailable segments for routing + * purpose. + * + * For old segments, the instance candidates should always have online flag set to true. + * For old segments without any enabled instance candidates, we report them as unavailable segments. + * + * For new segments, the online flag within the instance candidates indicates whether the instance is online or not. + * We don't report new segments as unavailable segments because it is valid for new segments to be offline. + */ +@Immutable +public class SegmentStates { + private final Map> _instanceCandidatesMap; + private final Set _unavailableSegments; + + public SegmentStates(Map> instanceCandidatesMap, + Set unavailableSegments) { + _instanceCandidatesMap = instanceCandidatesMap; + _unavailableSegments = unavailableSegments; + } + + @Nullable + public List getCandidates(String segment) { + return _instanceCandidatesMap.get(segment); + } + + public Set getUnavailableSegments() { + return _unavailableSegments; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java index 248cff0d1c6..b00cf851c70 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java @@ -18,19 +18,22 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeSet; import javax.annotation.Nullable; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.HashUtil; -import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; /** @@ -58,128 +61,108 @@ * transitioning/error scenario (external view does not match ideal state), if a segment is down on S1, we mark all * segments with the same assignment ([S1, S2, S3]) down on S1 to ensure that we always route the segments to the same * replica-group. + * + * Note that new segments won't be used to exclude instances from serving when the segment is unavailable. * */ public class StrictReplicaGroupInstanceSelector extends ReplicaGroupInstanceSelector { - public StrictReplicaGroupInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, @Nullable - AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); + public StrictReplicaGroupInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock); } /** * {@inheritDoc} * *

+   * Instances unavailable for any old segment should not exist in _oldSegmentCandidatesMap or _newSegmentStateMap for
+   * segments with the same instances in ideal state.
+   *
    * The maps are calculated in the following steps to meet the strict replica-group guarantee:
-   *   1. Create a map from online segment to set of instances hosting the segment based on the ideal state
-   *   2. Gather the online and offline instances for each online segment from the external view
-   *   3. Compare the instances from the ideal state and the external view and gather the unavailable instances for each
-   *      set of instances
-   *   4. Exclude the unavailable instances from the online instances map
+   *   1. Compute the online instances for both old and new segments
+   *   2. Compare online instances for old segments with instances in ideal state and gather the unavailable instances
+   *   for each set of instances
+   *   3. Exclude the unavailable instances from the online instances map for both old and new segment map
    * 
*/ @Override void updateSegmentMaps(IdealState idealState, ExternalView externalView, Set onlineSegments, - Map> segmentToOnlineInstancesMap, Map> segmentToOfflineInstancesMap, - Map> instanceToSegmentsMap) { - // TODO: Add support for AdaptiveServerSelection. - // Iterate over the ideal state to fill up 'idealStateSegmentToInstancesMap' which is a map from segment to set of - // instances hosting the segment in the ideal state - int segmentMapCapacity = HashUtil.getHashMapCapacity(onlineSegments.size()); - Map> idealStateSegmentToInstancesMap = new HashMap<>(segmentMapCapacity); - for (Map.Entry> entry : idealState.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); - // Only track online segments - if (!onlineSegments.contains(segment)) { - continue; - } - idealStateSegmentToInstancesMap.put(segment, entry.getValue().keySet()); - } + Map newSegmentPushTimeMap) { + _oldSegmentCandidatesMap.clear(); + int newSegmentMapCapacity = HashUtil.getHashMapCapacity(newSegmentPushTimeMap.size()); + _newSegmentStateMap = new HashMap<>(newSegmentMapCapacity); - // Iterate over the external view to fill up 'tempSegmentToOnlineInstancesMap' and 'segmentToOfflineInstancesMap'. - // 'tempSegmentToOnlineInstancesMap' is a temporary map from segment to set of instances that are in the ideal state - // and also ONLINE/CONSUMING in the external view. This map does not have the strict replica-group guarantee, and - // will be used to calculate the final 'segmentToOnlineInstancesMap'. - Map> tempSegmentToOnlineInstancesMap = new HashMap<>(segmentMapCapacity); - for (Map.Entry> entry : externalView.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); - Set instancesInIdealState = idealStateSegmentToInstancesMap.get(segment); - // Only track online segments - if (instancesInIdealState == null) { - continue; + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + + // Get the online instances for the segments + Map> oldSegmentToOnlineInstancesMap = + new HashMap<>(HashUtil.getHashMapCapacity(onlineSegments.size())); + Map> newSegmentToOnlineInstancesMap = new HashMap<>(newSegmentMapCapacity); + for (String segment : onlineSegments) { + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + assert idealStateInstanceStateMap != null; + Map externalViewInstanceStateMap = externalViewAssignment.get(segment); + Set onlineInstances; + if (externalViewInstanceStateMap == null) { + onlineInstances = Collections.emptySet(); + } else { + onlineInstances = getOnlineInstances(idealStateInstanceStateMap, externalViewInstanceStateMap); } - Map instanceStateMap = entry.getValue(); - Set tempOnlineInstances = new TreeSet<>(); - List offlineInstances = new ArrayList<>(); - tempSegmentToOnlineInstancesMap.put(segment, tempOnlineInstances); - segmentToOfflineInstancesMap.put(segment, offlineInstances); - for (Map.Entry instanceStateEntry : instanceStateMap.entrySet()) { - String instance = instanceStateEntry.getKey(); - // Only track instances within the ideal state - if (!instancesInIdealState.contains(instance)) { - continue; - } - String state = instanceStateEntry.getValue(); - if (state.equals(SegmentStateModel.ONLINE) || state.equals(SegmentStateModel.CONSUMING)) { - tempOnlineInstances.add(instance); - } else if (state.equals(SegmentStateModel.OFFLINE)) { - offlineInstances.add(instance); - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); - } + if (newSegmentPushTimeMap.containsKey(segment)) { + newSegmentToOnlineInstancesMap.put(segment, onlineInstances); + } else { + oldSegmentToOnlineInstancesMap.put(segment, onlineInstances); } } - // Iterate over the 'tempSegmentToOnlineInstancesMap' to gather the unavailable instances for each set of instances + // Calculate the unavailable instances based on the old segments' online instances for each combination of instances + // in the ideal state Map, Set> unavailableInstancesMap = new HashMap<>(); - for (Map.Entry> entry : tempSegmentToOnlineInstancesMap.entrySet()) { + for (Map.Entry> entry : oldSegmentToOnlineInstancesMap.entrySet()) { String segment = entry.getKey(); - Set tempOnlineInstances = entry.getValue(); - Set instancesInIdealState = idealStateSegmentToInstancesMap.get(segment); - // NOTE: When a segment is unavailable on all the instances, do not count all the instances as unavailable because - // this segment is unavailable and won't be included in the routing table, thus not breaking the requirement - // of routing to the same replica-group. This is normal for new added segments, and we don't want to mark - // all instances down on all segments with the same assignment. - if (tempOnlineInstances.size() == instancesInIdealState.size() || tempOnlineInstances.isEmpty()) { - continue; - } + Set instancesInIdealState = idealStateAssignment.get(segment).keySet(); Set unavailableInstances = - unavailableInstancesMap.computeIfAbsent(instancesInIdealState, k -> new TreeSet<>()); + unavailableInstancesMap.computeIfAbsent(instancesInIdealState, k -> new HashSet<>()); for (String instance : instancesInIdealState) { - if (!tempOnlineInstances.contains(instance)) { + if (!entry.getValue().contains(instance)) { unavailableInstances.add(instance); } } } - // Iterate over the 'tempSegmentToOnlineInstancesMap' again to fill up the 'segmentToOnlineInstancesMap' which has - // the strict replica-group guarantee - for (Map.Entry> entry : tempSegmentToOnlineInstancesMap.entrySet()) { + // Iterate over the maps and exclude the unavailable instances + for (Map.Entry> entry : oldSegmentToOnlineInstancesMap.entrySet()) { String segment = entry.getKey(); - Set tempOnlineInstances = entry.getValue(); - // NOTE: Instances will be sorted here because 'tempOnlineInstances' is a TreeSet. We need the online instances to - // be sorted for replica-group routing to work. For multiple segments with the same online instances, if the - // list is sorted, the same index in the list will always point to the same instance. - List onlineInstances = new ArrayList<>(tempOnlineInstances.size()); - segmentToOnlineInstancesMap.put(segment, onlineInstances); - - Set instancesInIdealState = idealStateSegmentToInstancesMap.get(segment); - Set unavailableInstances = unavailableInstancesMap.get(instancesInIdealState); - if (unavailableInstances == null) { - // No unavailable instance, add all instances as online instance - for (String instance : tempOnlineInstances) { - onlineInstances.add(instance); - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); + // NOTE: onlineInstances is either a TreeSet or an EmptySet (sorted) + Set onlineInstances = entry.getValue(); + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Set unavailableInstances = + unavailableInstancesMap.getOrDefault(idealStateInstanceStateMap.keySet(), Collections.emptySet()); + List candidates = new ArrayList<>(onlineInstances.size()); + for (String instance : onlineInstances) { + if (!unavailableInstances.contains(instance)) { + candidates.add(new SegmentInstanceCandidate(instance, true)); } - } else { - // Some instances are unavailable, add the remaining instances as online instance - for (String instance : tempOnlineInstances) { - if (!unavailableInstances.contains(instance)) { - onlineInstances.add(instance); - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); - } + } + _oldSegmentCandidatesMap.put(segment, candidates); + } + + for (Map.Entry> entry : newSegmentToOnlineInstancesMap.entrySet()) { + String segment = entry.getKey(); + Set onlineInstances = entry.getValue(); + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Set unavailableInstances = + unavailableInstancesMap.getOrDefault(idealStateInstanceStateMap.keySet(), Collections.emptySet()); + List candidates = new ArrayList<>(idealStateInstanceStateMap.size()); + for (Map.Entry instanceStateEntry : convertToSortedMap(idealStateInstanceStateMap).entrySet()) { + String instance = instanceStateEntry.getKey(); + if (!unavailableInstances.contains(instance) && isOnlineForRouting(instanceStateEntry.getValue())) { + candidates.add(new SegmentInstanceCandidate(instance, onlineInstances.contains(instance))); } } + _newSegmentStateMap.put(segment, new NewSegmentState(newSegmentPushTimeMap.get(segment), candidates)); } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetchListener.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetchListener.java new file mode 100644 index 00000000000..138291089da --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetchListener.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentmetadata; + +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; + + +/** + * Interface to register with {@link SegmentZkMetadataFetcher}. + * + *

When registered, SegmentZKMetadataFetcher will fetch {@link ZNRecord} for associated {@code onlineSegments} list + * or refreshed {@code segment}. Thus batch up all ZK access for segment metadata. + */ +public interface SegmentZkMetadataFetchListener { + + /** + * Initializes the segment pruner with the ideal state, external view and online segments (segments with + * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). Should be called + * only once before calling other methods. + */ + void init(IdealState idealState, ExternalView externalView, List onlineSegments, List znRecords); + + /** + * Processes the segment assignment (ideal state or external view) change based on the given online segments (segments + * with ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). + */ + void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments, + List pulledSegments, List znRecords); + + /** + * Refreshes the metadata for the given segment (called when segment is getting refreshed). + */ + void refreshSegment(String segment, @Nullable ZNRecord znRecord); +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcher.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcher.java new file mode 100644 index 00000000000..b6d996e8f45 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcher.java @@ -0,0 +1,133 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentmetadata; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.helix.AccessOption; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.ZKMetadataProvider; + + +/** + * {@code SegmentZkMetadataFetcher} is used to cache {@link ZNRecord} stored in {@link ZkHelixPropertyStore} for + * segments. + */ +public class SegmentZkMetadataFetcher { + private final String _tableNameWithType; + private final ZkHelixPropertyStore _propertyStore; + private final String _segmentZKMetadataPathPrefix; + private final List _listeners; + private final Set _onlineSegmentsCached; + + private boolean _initialized; + + public SegmentZkMetadataFetcher(String tableNameWithType, ZkHelixPropertyStore propertyStore) { + _tableNameWithType = tableNameWithType; + _propertyStore = propertyStore; + _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(tableNameWithType) + "/"; + _listeners = new ArrayList<>(); + _onlineSegmentsCached = new HashSet<>(); + _initialized = false; + } + + public void register(SegmentZkMetadataFetchListener listener) { + if (!_initialized) { + _listeners.add(listener); + } else { + throw new RuntimeException( + "Segment ZK metadata fetcher has already been initialized! Unable to register more listeners."); + } + } + + public List getListeners() { + return _listeners; + } + + public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + if (!_initialized) { + _initialized = true; + if (!_listeners.isEmpty()) { + // Bulk load partition info for all online segments + int numSegments = onlineSegments.size(); + List segments = new ArrayList<>(numSegments); + List segmentZKMetadataPaths = new ArrayList<>(numSegments); + for (String segment : onlineSegments) { + segments.add(segment); + segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); + } + List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); + for (SegmentZkMetadataFetchListener listener : _listeners) { + listener.init(idealState, externalView, segments, znRecords); + } + for (int i = 0; i < numSegments; i++) { + if (znRecords.get(i) != null) { + _onlineSegmentsCached.add(segments.get(i)); + } + } + } + } else { + throw new RuntimeException("Segment ZK metadata fetcher has already been initialized!"); + } + } + + public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, + Set onlineSegments) { + if (!_listeners.isEmpty()) { + List segments = new ArrayList<>(); + List segmentZKMetadataPaths = new ArrayList<>(); + for (String segment : onlineSegments) { + if (!_onlineSegmentsCached.contains(segment)) { + segments.add(segment); + segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); + } + } + List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); + for (SegmentZkMetadataFetchListener listener : _listeners) { + listener.onAssignmentChange(idealState, externalView, onlineSegments, segments, znRecords); + } + int numSegments = segments.size(); + for (int i = 0; i < numSegments; i++) { + if (znRecords.get(i) != null) { + _onlineSegmentsCached.add(segments.get(i)); + } + } + _onlineSegmentsCached.retainAll(onlineSegments); + } + } + + public synchronized void refreshSegment(String segment) { + if (!_listeners.isEmpty()) { + ZNRecord znRecord = _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT); + for (SegmentZkMetadataFetchListener listener : _listeners) { + listener.refreshSegment(segment, znRecord); + } + if (znRecord != null) { + _onlineSegmentsCached.add(segment); + } else { + _onlineSegmentsCached.remove(segment); + } + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionInfo.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionInfo.java new file mode 100644 index 00000000000..667c1f1c533 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionInfo.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import java.util.Set; +import org.apache.pinot.segment.spi.partition.PartitionFunction; + + +public class SegmentPartitionInfo { + private final String _partitionColumn; + private final PartitionFunction _partitionFunction; + private final Set _partitions; + + public SegmentPartitionInfo(String partitionColumn, PartitionFunction partitionFunction, + Set partitions) { + _partitionColumn = partitionColumn; + _partitionFunction = partitionFunction; + _partitions = partitions; + } + + public String getPartitionColumn() { + return _partitionColumn; + } + + public PartitionFunction getPartitionFunction() { + return _partitionFunction; + } + + public Set getPartitions() { + return _partitions; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManager.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManager.java new file mode 100644 index 00000000000..0ed89225f38 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManager.java @@ -0,0 +1,217 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetchListener; +import org.apache.pinot.core.routing.TablePartitionInfo; +import org.apache.pinot.core.routing.TablePartitionInfo.PartitionInfo; +import org.apache.pinot.segment.spi.partition.PartitionFunction; +import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The {@code PartitionDataManager} manages partitions of a table. It manages + * 1. all the online segments associated with the partition and their allocated servers + * 2. all the replica of a specific segment. + * It provides API to query + * 1. For each partition ID, what are the servers that contains ALL segments belong to this partition ID. + * 2. For each server, what are all the partition IDs and list of segments of those partition IDs on this server. + */ +public class SegmentPartitionMetadataManager implements SegmentZkMetadataFetchListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SegmentPartitionMetadataManager.class); + private static final int INVALID_PARTITION_ID = -1; + + private final String _tableNameWithType; + + // static content, if anything changes for the following. a rebuild of routing table is needed. + private final String _partitionColumn; + private final String _partitionFunctionName; + private final int _numPartitions; + + // cache-able content, only follow changes if onlineSegments list (of ideal-state) is changed. + private final Map _segmentInfoMap = new HashMap<>(); + + // computed value based on status change. + private transient TablePartitionInfo _tablePartitionInfo; + + public SegmentPartitionMetadataManager(String tableNameWithType, String partitionColumn, String partitionFunctionName, + int numPartitions) { + _tableNameWithType = tableNameWithType; + _partitionColumn = partitionColumn; + _partitionFunctionName = partitionFunctionName; + _numPartitions = numPartitions; + } + + @Override + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { + int numSegments = onlineSegments.size(); + for (int i = 0; i < numSegments; i++) { + String segment = onlineSegments.get(i); + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecords.get(i)); + SegmentInfo segmentInfo = new SegmentInfo(getPartitionId(partitionInfo), getOnlineServers(externalView, segment)); + _segmentInfoMap.put(segment, segmentInfo); + } + computeTablePartitionInfo(); + } + + private int getPartitionId(@Nullable SegmentPartitionInfo segmentPartitionInfo) { + if (segmentPartitionInfo == null || segmentPartitionInfo == SegmentPartitionUtils.INVALID_PARTITION_INFO) { + return INVALID_PARTITION_ID; + } + if (!_partitionColumn.equals(segmentPartitionInfo.getPartitionColumn())) { + return INVALID_PARTITION_ID; + } + PartitionFunction partitionFunction = segmentPartitionInfo.getPartitionFunction(); + if (!_partitionFunctionName.equalsIgnoreCase(partitionFunction.getName())) { + return INVALID_PARTITION_ID; + } + if (_numPartitions != partitionFunction.getNumPartitions()) { + return INVALID_PARTITION_ID; + } + Set partitions = segmentPartitionInfo.getPartitions(); + if (partitions.size() != 1) { + return INVALID_PARTITION_ID; + } + return partitions.iterator().next(); + } + + private List getOnlineServers(ExternalView externalView, String segment) { + Map instanceStateMap = externalView.getStateMap(segment); + if (instanceStateMap == null) { + return Collections.emptyList(); + } + List onlineServers = new ArrayList<>(instanceStateMap.size()); + for (Map.Entry entry : instanceStateMap.entrySet()) { + String instanceState = entry.getValue(); + if (instanceState.equals(SegmentStateModel.ONLINE) || instanceState.equals(SegmentStateModel.CONSUMING)) { + onlineServers.add(entry.getKey()); + } + } + return onlineServers; + } + + private void computeTablePartitionInfo() { + PartitionInfo[] partitionInfoMap = new PartitionInfo[_numPartitions]; + Set segmentsWithInvalidPartition = new HashSet<>(); + for (Map.Entry entry : _segmentInfoMap.entrySet()) { + String segment = entry.getKey(); + SegmentInfo segmentInfo = entry.getValue(); + int partitionId = segmentInfo._partitionId; + List onlineServers = segmentInfo._onlineServers; + if (partitionId == INVALID_PARTITION_ID) { + segmentsWithInvalidPartition.add(segment); + continue; + } + PartitionInfo partitionInfo = partitionInfoMap[partitionId]; + if (partitionInfo == null) { + Set fullyReplicatedServers = new HashSet<>(onlineServers); + List segments = new ArrayList<>(); + segments.add(segment); + partitionInfo = new PartitionInfo(fullyReplicatedServers, segments); + partitionInfoMap[partitionId] = partitionInfo; + } else { + partitionInfo._fullyReplicatedServers.retainAll(onlineServers); + partitionInfo._segments.add(segment); + } + } + if (!segmentsWithInvalidPartition.isEmpty()) { + int numSegmentsWithInvalidPartition = segmentsWithInvalidPartition.size(); + if (numSegmentsWithInvalidPartition <= 10) { + LOGGER.warn("Found {} segments: {} with invalid partition from table: {}", numSegmentsWithInvalidPartition, + segmentsWithInvalidPartition, _tableNameWithType); + } else { + LOGGER.warn("Found {} segments: {} with invalid partition from table: {}", numSegmentsWithInvalidPartition, + segmentsWithInvalidPartition, _tableNameWithType); + } + } + _tablePartitionInfo = + new TablePartitionInfo(_tableNameWithType, _partitionColumn, _partitionFunctionName, _numPartitions, + partitionInfoMap, segmentsWithInvalidPartition); + } + + @Override + public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, + Set onlineSegments, List pulledSegments, List znRecords) { + // Update segment partition id for the pulled segments + int numSegments = pulledSegments.size(); + for (int i = 0; i < numSegments; i++) { + String segment = pulledSegments.get(i); + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecords.get(i)); + SegmentInfo segmentInfo = new SegmentInfo(getPartitionId(partitionInfo), getOnlineServers(externalView, segment)); + _segmentInfoMap.put(segment, segmentInfo); + } + // Update online servers for all online segments + for (String segment : onlineSegments) { + SegmentInfo segmentInfo = _segmentInfoMap.get(segment); + if (segmentInfo == null) { + segmentInfo = new SegmentInfo(INVALID_PARTITION_ID, getOnlineServers(externalView, segment)); + _segmentInfoMap.put(segment, segmentInfo); + } else { + segmentInfo._onlineServers = getOnlineServers(externalView, segment); + } + } + _segmentInfoMap.keySet().retainAll(onlineSegments); + computeTablePartitionInfo(); + } + + @Override + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecord); + int partitionId = getPartitionId(partitionInfo); + SegmentInfo segmentInfo = _segmentInfoMap.get(segment); + if (segmentInfo == null) { + segmentInfo = new SegmentInfo(partitionId, Collections.emptyList()); + _segmentInfoMap.put(segment, segmentInfo); + } else { + segmentInfo._partitionId = partitionId; + } + computeTablePartitionInfo(); + } + + public TablePartitionInfo getTablePartitionInfo() { + return _tablePartitionInfo; + } + + private static class SegmentInfo { + int _partitionId; + List _onlineServers; + + SegmentInfo(int partitionId, List onlineServers) { + _partitionId = partitionId; + _onlineServers = onlineServers; + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionUtils.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionUtils.java new file mode 100644 index 00000000000..ee3dac042b6 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionUtils.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; +import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; +import org.apache.pinot.spi.utils.CommonConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SegmentPartitionUtils { + private SegmentPartitionUtils() { + } + + public static final SegmentPartitionInfo INVALID_PARTITION_INFO = new SegmentPartitionInfo(null, null, null); + public static final Map INVALID_COLUMN_PARTITION_INFO_MAP = Collections.emptyMap(); + + private static final Logger LOGGER = LoggerFactory.getLogger(SegmentPartitionUtils.class); + + /** + * Returns the partition info for a given segment with single partition column. + * + * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns + * {@link #INVALID_PARTITION_INFO} when the segment does not have valid partition metadata in its ZK metadata, + * in which case we won't retry later. + */ + @Nullable + public static SegmentPartitionInfo extractPartitionInfo(String tableNameWithType, String partitionColumn, + String segment, @Nullable ZNRecord znRecord) { + if (znRecord == null) { + LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, tableNameWithType); + return null; + } + + String partitionMetadataJson = znRecord.getSimpleField(CommonConstants.Segment.PARTITION_METADATA); + if (partitionMetadataJson == null) { + LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, tableNameWithType); + return INVALID_PARTITION_INFO; + } + + SegmentPartitionMetadata segmentPartitionMetadata; + try { + segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); + } catch (Exception e) { + LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, + tableNameWithType, e); + return INVALID_PARTITION_INFO; + } + + ColumnPartitionMetadata columnPartitionMetadata = + segmentPartitionMetadata.getColumnPartitionMap().get(partitionColumn); + if (columnPartitionMetadata == null) { + LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", partitionColumn, + segment, tableNameWithType); + return INVALID_PARTITION_INFO; + } + + return new SegmentPartitionInfo(partitionColumn, + PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), + columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), + columnPartitionMetadata.getPartitions()); + } + + /** + * Returns a map from partition column name to partition info for a given segment with multiple partition columns. + * + * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns + * {@link #INVALID_COLUMN_PARTITION_INFO_MAP} when the segment does not have valid partition metadata in its ZK + * metadata, in which case we won't retry later. + */ + @Nullable + public static Map extractPartitionInfoMap(String tableNameWithType, + Set partitionColumns, String segment, @Nullable ZNRecord znRecord) { + if (znRecord == null) { + LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, tableNameWithType); + return null; + } + + String partitionMetadataJson = znRecord.getSimpleField(CommonConstants.Segment.PARTITION_METADATA); + if (partitionMetadataJson == null) { + LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, tableNameWithType); + return INVALID_COLUMN_PARTITION_INFO_MAP; + } + + SegmentPartitionMetadata segmentPartitionMetadata; + try { + segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); + } catch (Exception e) { + LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, + tableNameWithType, e); + return INVALID_COLUMN_PARTITION_INFO_MAP; + } + + Map columnSegmentPartitionInfoMap = new HashMap<>(); + for (String partitionColumn : partitionColumns) { + ColumnPartitionMetadata columnPartitionMetadata = + segmentPartitionMetadata.getColumnPartitionMap().get(partitionColumn); + if (columnPartitionMetadata == null) { + LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", partitionColumn, + segment, tableNameWithType); + continue; + } + SegmentPartitionInfo segmentPartitionInfo = new SegmentPartitionInfo(partitionColumn, + PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), + columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), + columnPartitionMetadata.getPartitions()); + columnSegmentPartitionInfoMap.put(partitionColumn, segmentPartitionInfo); + } + if (columnSegmentPartitionInfoMap.size() == 1) { + String partitionColumn = columnSegmentPartitionInfoMap.keySet().iterator().next(); + return Collections.singletonMap(partitionColumn, columnSegmentPartitionInfoMap.get(partitionColumn)); + } + return columnSegmentPartitionInfoMap.isEmpty() ? INVALID_COLUMN_PARTITION_INFO_MAP : columnSegmentPartitionInfoMap; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java index 7a7b66b0869..aeb6a01f2e9 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java @@ -18,18 +18,14 @@ */ package org.apache.pinot.broker.routing.segmentpruner; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.utils.CommonConstants; @@ -45,35 +41,23 @@ public class EmptySegmentPruner implements SegmentPruner { private static final Logger LOGGER = LoggerFactory.getLogger(EmptySegmentPruner.class); private final String _tableNameWithType; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; private final Set _segmentsLoaded = new HashSet<>(); private final Set _emptySegments = ConcurrentHashMap.newKeySet(); private volatile ResultCache _resultCache; - public EmptySegmentPruner(TableConfig tableConfig, ZkHelixPropertyStore propertyStore) { + public EmptySegmentPruner(TableConfig tableConfig) { _tableNameWithType = tableConfig.getTableName(); - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(_tableNameWithType) + "/"; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - _segmentsLoaded.addAll(segments); - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - if (isEmpty(segment, znRecords.get(i))) { + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + if (isEmpty(segment, znRecords.get(idx))) { _emptySegments.add(segment); } } @@ -81,14 +65,14 @@ public void init(IdealState idealState, ExternalView externalView, Set o @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. boolean emptySegmentsChanged = false; - for (String segment : onlineSegments) { + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); if (_segmentsLoaded.add(segment)) { - if (isEmpty(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT))) { + if (isEmpty(segment, znRecords.get(idx))) { emptySegmentsChanged |= _emptySegments.add(segment); } } @@ -103,9 +87,9 @@ public synchronized void onAssignmentChange(IdealState idealState, ExternalView } @Override - public synchronized void refreshSegment(String segment) { + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { _segmentsLoaded.add(segment); - if (isEmpty(segment, _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT))) { + if (isEmpty(segment, znRecord)) { if (_emptySegments.add(segment)) { // Reset the result cache when empty segments changed _resultCache = null; diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java index dd4edb686b8..b05ab4e55b9 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java @@ -19,33 +19,22 @@ package org.apache.pinot.broker.routing.segmentpruner; import com.google.common.annotations.VisibleForTesting; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; -import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionInfo; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionUtils; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; -import org.apache.pinot.segment.spi.partition.PartitionFunction; -import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; -import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; -import org.apache.pinot.spi.utils.CommonConstants.Segment; import org.apache.pinot.sql.FilterKind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -53,111 +42,49 @@ * pruner supports queries with filter (or nested filter) of EQUALITY and IN predicates. */ public class MultiPartitionColumnsSegmentPruner implements SegmentPruner { - private static final Logger LOGGER = LoggerFactory.getLogger(MultiPartitionColumnsSegmentPruner.class); - private static final Map INVALID_COLUMN_PARTITION_INFO_MAP = Collections.emptyMap(); - private final String _tableNameWithType; private final Set _partitionColumns; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; - private final Map> _segmentColumnPartitionInfoMap = new ConcurrentHashMap<>(); + private final Map> _segmentColumnPartitionInfoMap = + new ConcurrentHashMap<>(); - public MultiPartitionColumnsSegmentPruner(String tableNameWithType, Set partitionColumns, - ZkHelixPropertyStore propertyStore) { + public MultiPartitionColumnsSegmentPruner(String tableNameWithType, Set partitionColumns) { _tableNameWithType = tableNameWithType; _partitionColumns = partitionColumns; - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(tableNameWithType) + "/"; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load partition info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - Map columnPartitionInfoMap = - extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(segment, znRecords.get(i)); + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + Map columnPartitionInfoMap = + SegmentPartitionUtils.extractPartitionInfoMap(_tableNameWithType, _partitionColumns, segment, + znRecords.get(idx)); if (columnPartitionInfoMap != null) { _segmentColumnPartitionInfoMap.put(segment, columnPartitionInfoMap); } } } - /** - * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns - * {@link #INVALID_COLUMN_PARTITION_INFO_MAP} when the segment does not have valid partition metadata in its ZK - * metadata, in which case we won't retry later. - */ - @Nullable - private Map extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(String segment, - @Nullable ZNRecord znRecord) { - if (znRecord == null) { - LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, _tableNameWithType); - return null; - } - - String partitionMetadataJson = znRecord.getSimpleField(Segment.PARTITION_METADATA); - if (partitionMetadataJson == null) { - LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, _tableNameWithType); - return INVALID_COLUMN_PARTITION_INFO_MAP; - } - - SegmentPartitionMetadata segmentPartitionMetadata; - try { - segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); - } catch (Exception e) { - LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, - _tableNameWithType, e); - return INVALID_COLUMN_PARTITION_INFO_MAP; - } - - Map columnPartitionInfoMap = new HashMap<>(); - for (String partitionColumn : _partitionColumns) { - ColumnPartitionMetadata columnPartitionMetadata = - segmentPartitionMetadata.getColumnPartitionMap().get(partitionColumn); - if (columnPartitionMetadata == null) { - LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", partitionColumn, - segment, _tableNameWithType); - continue; - } - PartitionInfo partitionInfo = new PartitionInfo( - PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), - columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), - columnPartitionMetadata.getPartitions()); - columnPartitionInfoMap.put(partitionColumn, partitionInfo); - } - if (columnPartitionInfoMap.size() == 1) { - String partitionColumn = columnPartitionInfoMap.keySet().iterator().next(); - return Collections.singletonMap(partitionColumn, columnPartitionInfoMap.get(partitionColumn)); - } - return columnPartitionInfoMap.isEmpty() ? INVALID_COLUMN_PARTITION_INFO_MAP : columnPartitionInfoMap; - } - @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. - for (String segment : onlineSegments) { + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); + ZNRecord znRecord = znRecords.get(idx); _segmentColumnPartitionInfoMap.computeIfAbsent(segment, - k -> extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(k, - _propertyStore.get(_segmentZKMetadataPathPrefix + k, null, AccessOption.PERSISTENT))); + k -> SegmentPartitionUtils.extractPartitionInfoMap(_tableNameWithType, _partitionColumns, k, znRecord)); } _segmentColumnPartitionInfoMap.keySet().retainAll(onlineSegments); } @Override - public synchronized void refreshSegment(String segment) { - Map columnPartitionInfo = extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT)); + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + Map columnPartitionInfo = + SegmentPartitionUtils.extractPartitionInfoMap(_tableNameWithType, _partitionColumns, segment, znRecord); if (columnPartitionInfo != null) { _segmentColumnPartitionInfoMap.put(segment, columnPartitionInfo); } else { @@ -173,9 +100,10 @@ public Set prune(BrokerRequest brokerRequest, Set segments) { } Set selectedSegments = new HashSet<>(); for (String segment : segments) { - Map columnPartitionInfoMap = _segmentColumnPartitionInfoMap.get(segment); - if (columnPartitionInfoMap == null || columnPartitionInfoMap == INVALID_COLUMN_PARTITION_INFO_MAP - || isPartitionMatch(filterExpression, columnPartitionInfoMap)) { + Map columnPartitionInfoMap = _segmentColumnPartitionInfoMap.get(segment); + if (columnPartitionInfoMap == null + || columnPartitionInfoMap == SegmentPartitionUtils.INVALID_COLUMN_PARTITION_INFO_MAP || isPartitionMatch( + filterExpression, columnPartitionInfoMap)) { selectedSegments.add(segment); } } @@ -187,7 +115,8 @@ public Set getPartitionColumns() { return _partitionColumns; } - private boolean isPartitionMatch(Expression filterExpression, Map columnPartitionInfoMap) { + private boolean isPartitionMatch(Expression filterExpression, + Map columnPartitionInfoMap) { Function function = filterExpression.getFunctionCall(); FilterKind filterKind = FilterKind.valueOf(function.getOperator()); List operands = function.getOperands(); @@ -209,9 +138,9 @@ private boolean isPartitionMatch(Expression filterExpression, Map _partitions; - - PartitionInfo(PartitionFunction partitionFunction, Set partitions) { - _partitionFunction = partitionFunction; - _partitions = partitions; - } - } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java index 5893e6bd923..17e92e5c158 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java @@ -19,34 +19,14 @@ package org.apache.pinot.broker.routing.segmentpruner; import java.util.Set; -import org.apache.helix.model.ExternalView; -import org.apache.helix.model.IdealState; -import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetchListener; import org.apache.pinot.common.request.BrokerRequest; /** * The segment pruner prunes the selected segments based on the query. */ -public interface SegmentPruner { - - /** - * Initializes the segment pruner with the ideal state, external view and online segments (segments with - * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). Should be called - * only once before calling other methods. - */ - void init(IdealState idealState, ExternalView externalView, Set onlineSegments); - - /** - * Processes the segment assignment (ideal state or external view) change based on the given online segments (segments - * with ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). - */ - void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments); - - /** - * Refreshes the metadata for the given segment (called when segment is getting refreshed). - */ - void refreshSegment(String segment); +public interface SegmentPruner extends SegmentZkMetadataFetchListener { /** * Prunes the segments queried by the given broker request, returns the selected segments to be queried. diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java index f34f55f3c3d..6135982e185 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java @@ -18,6 +18,8 @@ */ package org.apache.pinot.broker.routing.segmentpruner; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,6 +28,7 @@ import org.apache.commons.collections.MapUtils; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.segment.local.utils.TableConfigUtils; import org.apache.pinot.spi.config.table.ColumnPartitionConfig; import org.apache.pinot.spi.config.table.RoutingConfig; @@ -33,6 +36,9 @@ import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DateTimeFieldSpec; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.Schema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +58,7 @@ public static List getSegmentPruners(TableConfig tableConfig, boolean needsEmptySegment = TableConfigUtils.needsEmptySegmentPruner(tableConfig); if (needsEmptySegment) { // Add EmptySegmentPruner if needed - segmentPruners.add(new EmptySegmentPruner(tableConfig, propertyStore)); + segmentPruners.add(new EmptySegmentPruner(tableConfig)); } RoutingConfig routingConfig = tableConfig.getRoutingConfig(); @@ -113,8 +119,8 @@ private static SegmentPruner getPartitionSegmentPruner(TableConfig tableConfig, LOGGER.info("Using PartitionSegmentPruner on partition columns: {} for table: {}", partitionColumns, tableNameWithType); return partitionColumns.size() == 1 ? new SinglePartitionColumnSegmentPruner(tableNameWithType, - partitionColumns.iterator().next(), propertyStore) - : new MultiPartitionColumnsSegmentPruner(tableNameWithType, partitionColumns, propertyStore); + partitionColumns.iterator().next()) + : new MultiPartitionColumnsSegmentPruner(tableNameWithType, partitionColumns); } @Nullable @@ -131,9 +137,26 @@ private static TimeSegmentPruner getTimeSegmentPruner(TableConfig tableConfig, LOGGER.warn("Cannot enable time range pruning without time column for table: {}", tableNameWithType); return null; } + return createTimeSegmentPruner(tableConfig, propertyStore); + } + + @VisibleForTesting + static TimeSegmentPruner createTimeSegmentPruner(TableConfig tableConfig, + ZkHelixPropertyStore propertyStore) { + String tableNameWithType = tableConfig.getTableName(); + String timeColumn = tableConfig.getValidationConfig().getTimeColumnName(); + Preconditions.checkNotNull(timeColumn, "Time column must be configured in table config for table: %s", + tableNameWithType); + Schema schema = ZKMetadataProvider.getTableSchema(propertyStore, tableNameWithType); + Preconditions.checkNotNull(schema, "Failed to find schema for table: %s", tableNameWithType); + DateTimeFieldSpec dateTimeSpec = schema.getSpecForTimeColumn(timeColumn); + Preconditions.checkNotNull(dateTimeSpec, "Field spec must be specified in schema for time column: %s of table: %s", + timeColumn, tableNameWithType); + DateTimeFormatSpec timeFormatSpec = dateTimeSpec.getFormatSpec(); - LOGGER.info("Using TimeRangePruner on time column: {} for table: {}", timeColumn, tableNameWithType); - return new TimeSegmentPruner(tableConfig, propertyStore); + LOGGER.info("Using TimeRangePruner on time column: {} for table: {} with DateTimeFormatSpec: {}", + timeColumn, tableNameWithType, timeFormatSpec); + return new TimeSegmentPruner(tableConfig, timeColumn, timeFormatSpec); } private static List sortSegmentPruners(List pruners) { diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java index 2c75782ef3a..80ad4b7e3ed 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java @@ -18,31 +18,22 @@ */ package org.apache.pinot.broker.routing.segmentpruner; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; -import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionInfo; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionUtils; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; -import org.apache.pinot.segment.spi.partition.PartitionFunction; -import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; -import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; -import org.apache.pinot.spi.utils.CommonConstants.Segment; import org.apache.pinot.sql.FilterKind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -50,99 +41,47 @@ * pruner supports queries with filter (or nested filter) of EQUALITY and IN predicates. */ public class SinglePartitionColumnSegmentPruner implements SegmentPruner { - private static final Logger LOGGER = LoggerFactory.getLogger(SinglePartitionColumnSegmentPruner.class); - private static final PartitionInfo INVALID_PARTITION_INFO = new PartitionInfo(null, null); - private final String _tableNameWithType; private final String _partitionColumn; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; - private final Map _partitionInfoMap = new ConcurrentHashMap<>(); + private final Map _partitionInfoMap = new ConcurrentHashMap<>(); - public SinglePartitionColumnSegmentPruner(String tableNameWithType, String partitionColumn, - ZkHelixPropertyStore propertyStore) { + public SinglePartitionColumnSegmentPruner(String tableNameWithType, String partitionColumn) { _tableNameWithType = tableNameWithType; _partitionColumn = partitionColumn; - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(tableNameWithType) + "/"; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load partition info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - PartitionInfo partitionInfo = extractPartitionInfoFromSegmentZKMetadataZNRecord(segment, znRecords.get(i)); + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecords.get(idx)); if (partitionInfo != null) { _partitionInfoMap.put(segment, partitionInfo); } } } - /** - * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns - * {@link #INVALID_PARTITION_INFO} when the segment does not have valid partition metadata in its ZK metadata, - * in which case we won't retry later. - */ - @Nullable - private PartitionInfo extractPartitionInfoFromSegmentZKMetadataZNRecord(String segment, @Nullable ZNRecord znRecord) { - if (znRecord == null) { - LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, _tableNameWithType); - return null; - } - - String partitionMetadataJson = znRecord.getSimpleField(Segment.PARTITION_METADATA); - if (partitionMetadataJson == null) { - LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, _tableNameWithType); - return INVALID_PARTITION_INFO; - } - - SegmentPartitionMetadata segmentPartitionMetadata; - try { - segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); - } catch (Exception e) { - LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, - _tableNameWithType, e); - return INVALID_PARTITION_INFO; - } - - ColumnPartitionMetadata columnPartitionMetadata = - segmentPartitionMetadata.getColumnPartitionMap().get(_partitionColumn); - if (columnPartitionMetadata == null) { - LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", _partitionColumn, - segment, _tableNameWithType); - return INVALID_PARTITION_INFO; - } - - return new PartitionInfo(PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), - columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), - columnPartitionMetadata.getPartitions()); - } - @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. - for (String segment : onlineSegments) { - _partitionInfoMap.computeIfAbsent(segment, k -> extractPartitionInfoFromSegmentZKMetadataZNRecord(k, - _propertyStore.get(_segmentZKMetadataPathPrefix + k, null, AccessOption.PERSISTENT))); + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); + ZNRecord znRecord = znRecords.get(idx); + _partitionInfoMap.computeIfAbsent(segment, + k -> SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, k, znRecord)); } _partitionInfoMap.keySet().retainAll(onlineSegments); } @Override - public synchronized void refreshSegment(String segment) { - PartitionInfo partitionInfo = extractPartitionInfoFromSegmentZKMetadataZNRecord(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT)); + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecord); if (partitionInfo != null) { _partitionInfoMap.put(segment, partitionInfo); } else { @@ -158,16 +97,16 @@ public Set prune(BrokerRequest brokerRequest, Set segments) { } Set selectedSegments = new HashSet<>(); for (String segment : segments) { - PartitionInfo partitionInfo = _partitionInfoMap.get(segment); - if (partitionInfo == null || partitionInfo == INVALID_PARTITION_INFO || isPartitionMatch(filterExpression, - partitionInfo)) { + SegmentPartitionInfo partitionInfo = _partitionInfoMap.get(segment); + if (partitionInfo == null || partitionInfo == SegmentPartitionUtils.INVALID_PARTITION_INFO || isPartitionMatch( + filterExpression, partitionInfo)) { selectedSegments.add(segment); } } return selectedSegments; } - private boolean isPartitionMatch(Expression filterExpression, PartitionInfo partitionInfo) { + private boolean isPartitionMatch(Expression filterExpression, SegmentPartitionInfo partitionInfo) { Function function = filterExpression.getFunctionCall(); FilterKind filterKind = FilterKind.valueOf(function.getOperator()); List operands = function.getOperands(); @@ -189,8 +128,8 @@ private boolean isPartitionMatch(Expression filterExpression, PartitionInfo part case EQUALS: { Identifier identifier = operands.get(0).getIdentifier(); if (identifier != null && identifier.getName().equals(_partitionColumn)) { - return partitionInfo._partitions.contains( - partitionInfo._partitionFunction.getPartition(operands.get(1).getLiteral().getFieldValue().toString())); + return partitionInfo.getPartitions().contains(partitionInfo.getPartitionFunction() + .getPartition(operands.get(1).getLiteral().getFieldValue().toString())); } else { return true; } @@ -200,8 +139,8 @@ private boolean isPartitionMatch(Expression filterExpression, PartitionInfo part if (identifier != null && identifier.getName().equals(_partitionColumn)) { int numOperands = operands.size(); for (int i = 1; i < numOperands; i++) { - if (partitionInfo._partitions.contains(partitionInfo._partitionFunction.getPartition( - operands.get(i).getLiteral().getFieldValue().toString()))) { + if (partitionInfo.getPartitions().contains(partitionInfo.getPartitionFunction() + .getPartition(operands.get(i).getLiteral().getFieldValue().toString()))) { return true; } } @@ -214,14 +153,4 @@ private boolean isPartitionMatch(Expression filterExpression, PartitionInfo part return true; } } - - private static class PartitionInfo { - final PartitionFunction _partitionFunction; - final Set _partitions; - - PartitionInfo(PartitionFunction partitionFunction, Set partitions) { - _partitionFunction = partitionFunction; - _partitions = partitions; - } - } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java index c123c65a675..a7ac4fce4bd 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.broker.routing.segmentpruner; -import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -29,22 +28,17 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.segmentpruner.interval.Interval; import org.apache.pinot.broker.routing.segmentpruner.interval.IntervalTree; -import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.data.DateTimeFieldSpec; import org.apache.pinot.spi.data.DateTimeFormatSpec; -import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Query.Range; import org.apache.pinot.sql.FilterKind; @@ -64,44 +58,25 @@ public class TimeSegmentPruner implements SegmentPruner { private static final Interval DEFAULT_INTERVAL = new Interval(MIN_START_TIME, MAX_END_TIME); private final String _tableNameWithType; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; private final String _timeColumn; private final DateTimeFormatSpec _timeFormatSpec; private volatile IntervalTree _intervalTree; private final Map _intervalMap = new HashMap<>(); - public TimeSegmentPruner(TableConfig tableConfig, ZkHelixPropertyStore propertyStore) { + public TimeSegmentPruner(TableConfig tableConfig, String timeColumn, DateTimeFormatSpec timeFormatSpec) { _tableNameWithType = tableConfig.getTableName(); - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(_tableNameWithType) + "/"; - _timeColumn = tableConfig.getValidationConfig().getTimeColumnName(); - Preconditions.checkNotNull(_timeColumn, "Time column must be configured in table config for table: %s", - _tableNameWithType); - - Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); - Preconditions.checkNotNull(schema, "Failed to find schema for table: %s", _tableNameWithType); - DateTimeFieldSpec dateTimeSpec = schema.getSpecForTimeColumn(_timeColumn); - Preconditions.checkNotNull(dateTimeSpec, "Field spec must be specified in schema for time column: %s of table: %s", - _timeColumn, _tableNameWithType); - _timeFormatSpec = dateTimeSpec.getFormatSpec(); + _timeColumn = timeColumn; + _timeFormatSpec = timeFormatSpec; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load time info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, znRecords.get(i)); + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, znRecords.get(idx)); _intervalMap.put(segment, interval); } _intervalTree = new IntervalTree<>(_intervalMap); @@ -128,21 +103,21 @@ private Interval extractIntervalFromSegmentZKMetaZNRecord(String segment, @Nulla @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. - for (String segment : onlineSegments) { - _intervalMap.computeIfAbsent(segment, k -> extractIntervalFromSegmentZKMetaZNRecord(k, - _propertyStore.get(_segmentZKMetadataPathPrefix + k, null, AccessOption.PERSISTENT))); + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); + ZNRecord zNrecord = znRecords.get(idx); + _intervalMap.computeIfAbsent(segment, k -> extractIntervalFromSegmentZKMetaZNRecord(k, zNrecord)); } _intervalMap.keySet().retainAll(onlineSegments); _intervalTree = new IntervalTree<>(_intervalMap); } @Override - public synchronized void refreshSegment(String segment) { - Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT)); + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, znRecord); _intervalMap.put(segment, interval); _intervalTree = new IntervalTree<>(_intervalMap); } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java index f9e60f47919..a491e4f402f 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java @@ -21,7 +21,9 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.HttpRequesterIdentity; import org.apache.pinot.common.request.BrokerRequest; @@ -40,13 +42,20 @@ public class BasicAuthAccessControlTest { private AccessControl _accessControl; + Set _tableNames; + @BeforeClass public void setup() { Map config = new HashMap<>(); config.put("principals", "admin,user"); config.put("principals.admin.password", "verysecret"); config.put("principals.user.password", "secret"); - config.put("principals.user.tables", "lessImportantStuff"); + config.put("principals.user.tables", "lessImportantStuff,lesserImportantStuff,leastImportantStuff"); + + _tableNames = new HashSet<>(); + _tableNames.add("lessImportantStuff"); + _tableNames.add("lesserImportantStuff"); + _tableNames.add("leastImportantStuff"); AccessControlFactory factory = new BasicAuthAccessControlFactory(); factory.init(new PinotConfiguration(config)); @@ -56,7 +65,7 @@ public void setup() { @Test(expectedExceptions = IllegalArgumentException.class) public void testNullEntity() { - _accessControl.hasAccess(null, null); + _accessControl.hasAccess(null, (BrokerRequest) null); } @Test @@ -66,7 +75,7 @@ public void testNullToken() { HttpRequesterIdentity identity = new HttpRequesterIdentity(); identity.setHttpHeaders(headers); - Assert.assertFalse(_accessControl.hasAccess(identity, null)); + Assert.assertFalse(_accessControl.hasAccess(identity, (BrokerRequest) null)); } @Test @@ -84,6 +93,7 @@ public void testAllow() { request.setQuerySource(source); Assert.assertTrue(_accessControl.hasAccess(identity, request)); + Assert.assertTrue(_accessControl.hasAccess(identity, _tableNames)); } @Test @@ -101,6 +111,14 @@ public void testDeny() { request.setQuerySource(source); Assert.assertFalse(_accessControl.hasAccess(identity, request)); + + Set tableNames = new HashSet<>(); + tableNames.add("veryImportantStuff"); + Assert.assertFalse(_accessControl.hasAccess(identity, tableNames)); + tableNames.add("lessImportantStuff"); + Assert.assertFalse(_accessControl.hasAccess(identity, tableNames)); + tableNames.add("lesserImportantStuff"); + Assert.assertFalse(_accessControl.hasAccess(identity, tableNames)); } @Test @@ -118,6 +136,13 @@ public void testAllowAll() { request.setQuerySource(source); Assert.assertTrue(_accessControl.hasAccess(identity, request)); + + Set tableNames = new HashSet<>(); + tableNames.add("lessImportantStuff"); + tableNames.add("veryImportantStuff"); + tableNames.add("lesserImportantStuff"); + + Assert.assertTrue(_accessControl.hasAccess(identity, tableNames)); } @Test @@ -131,6 +156,9 @@ public void testAllowNonTable() { BrokerRequest request = new BrokerRequest(); Assert.assertTrue(_accessControl.hasAccess(identity, request)); + + Set tableNames = new HashSet<>(); + Assert.assertTrue(_accessControl.hasAccess(identity, tableNames)); } @Test @@ -148,5 +176,6 @@ public void testNormalizeToken() { request.setQuerySource(source); Assert.assertTrue(_accessControl.hasAccess(identity, request)); + Assert.assertTrue(_accessControl.hasAccess(identity, _tableNames)); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProviderTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProviderTest.java new file mode 100644 index 00000000000..25c1d099ca7 --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProviderTest.java @@ -0,0 +1,134 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.broker; + +import java.util.Collections; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.ServiceUnavailableException; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.utils.CommonConstants; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + + +public class BrokerManagedAsyncExecutorProviderTest { + + public static BrokerMetrics _brokerMetrics; + + @BeforeClass + public void setUp() { + _brokerMetrics = new BrokerMetrics(CommonConstants.Broker.DEFAULT_METRICS_NAME_PREFIX, + PinotMetricUtils.getPinotMetricsRegistry(new PinotConfiguration()), + CommonConstants.Broker.DEFAULT_ENABLE_TABLE_LEVEL_METRICS, Collections.emptyList()); + } + + @Test + public void testExecutorService() + throws InterruptedException, ExecutionException { + // create a new instance of the executor provider + BrokerManagedAsyncExecutorProvider provider = new BrokerManagedAsyncExecutorProvider(2, 2, 2, _brokerMetrics); + + // get the executor service + ThreadPoolExecutor executor = (ThreadPoolExecutor) provider.getExecutorService(); + + // submit a task to the executor service and wait for it to complete + Future futureResult = executor.submit(() -> 1 + 1); + Integer result = futureResult.get(); + + // verify that the task was executed and returned the expected result + assertNotNull(result); + assertEquals((int) result, 2); + + // wait for the executor service to shutdown + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } + + @Test + public void testGet() + throws InterruptedException { + BrokerManagedAsyncExecutorProvider provider = new BrokerManagedAsyncExecutorProvider(1, 1, 1, _brokerMetrics); + ExecutorService executorService = provider.getExecutorService(); + + // verify that the executor has the expected properties + assertNotNull(executorService); + assertTrue(executorService instanceof ThreadPoolExecutor); + + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService; + + assertEquals(1, threadPoolExecutor.getCorePoolSize()); + assertEquals(1, threadPoolExecutor.getMaximumPoolSize()); + + BlockingQueue blockingQueue = threadPoolExecutor.getQueue(); + assertNotNull(blockingQueue); + assertTrue(blockingQueue instanceof ArrayBlockingQueue); + assertEquals(0, blockingQueue.size()); + assertEquals(1, blockingQueue.remainingCapacity()); + + RejectedExecutionHandler rejectedExecutionHandler = threadPoolExecutor.getRejectedExecutionHandler(); + assertNotNull(rejectedExecutionHandler); + assertTrue( + rejectedExecutionHandler instanceof BrokerManagedAsyncExecutorProvider.BrokerThreadPoolRejectExecutionHandler); + + // test that the executor actually executes tasks + AtomicInteger counter = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(1); + for (int i = 0; i < 1; i++) { + threadPoolExecutor.execute(() -> { + counter.incrementAndGet(); + latch.countDown(); + }); + } + latch.await(); + assertEquals(counter.get(), 1); + } + + @Test(expectedExceptions = ServiceUnavailableException.class) + public void testRejectHandler() + throws InterruptedException { + BrokerManagedAsyncExecutorProvider provider = new BrokerManagedAsyncExecutorProvider(1, 1, 1, _brokerMetrics); + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) provider.getExecutorService(); + + // test the rejection policy + AtomicInteger counter = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(10); + for (int i = 0; i < 10; i++) { + threadPoolExecutor.execute(() -> { + counter.incrementAndGet(); + latch.countDown(); + }); + } + latch.await(); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java index 53e75d05571..4a7cee97ffe 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java @@ -104,10 +104,14 @@ public void setUp() } TestUtils.waitForCondition(aVoid -> { + // should wait for both realtime and offline table external view to be live. ExternalView offlineTableExternalView = _helixAdmin.getResourceExternalView(getHelixClusterName(), OFFLINE_TABLE_NAME); + ExternalView realtimeTableExternalView = + _helixAdmin.getResourceExternalView(getHelixClusterName(), REALTIME_TABLE_NAME); return offlineTableExternalView != null - && offlineTableExternalView.getPartitionSet().size() == NUM_OFFLINE_SEGMENTS; + && offlineTableExternalView.getPartitionSet().size() == NUM_OFFLINE_SEGMENTS + && realtimeTableExternalView != null; }, 30_000L, "Failed to find all OFFLINE segments in the ExternalView"); } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java index d2ea5c9b7aa..7f3cb79749f 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java @@ -197,16 +197,18 @@ public void testCancelQuery() BrokerRoutingManager routingManager = mock(BrokerRoutingManager.class); when(routingManager.routingExists(anyString())).thenReturn(true); RoutingTable rt = mock(RoutingTable.class); - when(rt.getServerInstanceToSegmentsMap()).thenReturn(Collections - .singletonMap(new ServerInstance(new InstanceConfig("server01_9000")), Collections.singletonList("segment01"))); + when(rt.getServerInstanceToSegmentsMap()).thenReturn( + Collections.singletonMap(new ServerInstance(new InstanceConfig("server01_9000")), + Collections.singletonList("segment01"))); when(routingManager.getRoutingTable(any(), Mockito.anyLong())).thenReturn(rt); QueryQuotaManager queryQuotaManager = mock(QueryQuotaManager.class); when(queryQuotaManager.acquire(anyString())).thenReturn(true); CountDownLatch latch = new CountDownLatch(1); + final long[] testRequestId = {-1}; PinotConfiguration config = new PinotConfiguration(Collections.singletonMap("pinot.broker.enable.query.cancellation", "true")); BaseBrokerRequestHandler requestHandler = - new BaseBrokerRequestHandler(config, null, routingManager, new AllowAllAccessControlFactory(), + new BaseBrokerRequestHandler(config, "testBrokerId", routingManager, new AllowAllAccessControlFactory(), queryQuotaManager, tableCache, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet())) { @Override @@ -225,6 +227,7 @@ protected BrokerResponseNative processBrokerRequest(long requestId, BrokerReques @Nullable Map> realtimeRoutingTable, long timeoutMs, ServerStats serverStats, RequestContext requestContext) throws Exception { + testRequestId[0] = requestId; latch.await(); return null; } @@ -239,12 +242,12 @@ protected BrokerResponseNative processBrokerRequest(long requestId, BrokerReques throw new RuntimeException(e); } }); - TestUtils.waitForCondition((aVoid) -> requestHandler.getRunningServers(1).size() == 1, 500, 5000, + TestUtils.waitForCondition((aVoid) -> requestHandler.getRunningServers(testRequestId[0]).size() == 1, 500, 5000, "Failed to submit query"); Map.Entry entry = requestHandler.getRunningQueries().entrySet().iterator().next(); - Assert.assertEquals(entry.getKey().longValue(), 1); + Assert.assertEquals(entry.getKey().longValue(), testRequestId[0]); Assert.assertTrue(entry.getValue().contains("select * from myTable_OFFLINE limit 10")); - Set servers = requestHandler.getRunningServers(1); + Set servers = requestHandler.getRunningServers(testRequestId[0]); Assert.assertEquals(servers.size(), 1); Assert.assertEquals(servers.iterator().next().getHostname(), "server01"); Assert.assertEquals(servers.iterator().next().getPort(), 9000); diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java index a9ee2a5deef..b85004c48f0 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java @@ -181,9 +181,9 @@ public void testLiteralOnlyWithAsBrokerRequestFromSQL() { public void testBrokerRequestHandler() throws Exception { SingleConnectionBrokerRequestHandler requestHandler = - new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), null, null, ACCESS_CONTROL_FACTORY, null, - null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), null, - null, mock(ServerRoutingStatsManager.class)); + new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), "testBrokerId", null, ACCESS_CONTROL_FACTORY, + null, null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), + null, null, mock(ServerRoutingStatsManager.class)); long randNum = RANDOM.nextLong(); byte[] randBytes = new byte[12]; @@ -209,9 +209,9 @@ null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Co public void testBrokerRequestHandlerWithAsFunction() throws Exception { SingleConnectionBrokerRequestHandler requestHandler = - new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), null, null, ACCESS_CONTROL_FACTORY, null, - null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), null, - null, mock(ServerRoutingStatsManager.class)); + new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), "testBrokerId", null, ACCESS_CONTROL_FACTORY, + null, null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), + null, null, mock(ServerRoutingStatsManager.class)); long currentTsMin = System.currentTimeMillis(); JsonNode request = JsonUtils.stringToJsonNode( "{\"sql\":\"SELECT now() as currentTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') as firstDayOf2020\"}"); @@ -416,9 +416,9 @@ null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Co public void testExplainPlanLiteralOnly() throws Exception { SingleConnectionBrokerRequestHandler requestHandler = - new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), null, null, ACCESS_CONTROL_FACTORY, null, - null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), null, - null, mock(ServerRoutingStatsManager.class)); + new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), "testBrokerId", null, ACCESS_CONTROL_FACTORY, + null, null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), + null, null, mock(ServerRoutingStatsManager.class)); // Test 1: select constant JsonNode request = JsonUtils.stringToJsonNode("{\"sql\":\"EXPLAIN PLAN FOR SELECT 1.5, 'test'\"}"); diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java new file mode 100644 index 00000000000..236cb2cdde1 --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.requesthandler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.broker.broker.AccessControlFactory; +import org.apache.pinot.broker.broker.AllowAllAccessControlFactory; +import org.apache.pinot.broker.queryquota.QueryQuotaManager; +import org.apache.pinot.broker.routing.BrokerRoutingManager; +import org.apache.pinot.common.config.provider.TableCache; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.query.service.QueryConfig; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.trace.DefaultRequestContext; +import org.apache.pinot.spi.trace.RequestContext; +import org.apache.pinot.spi.utils.CommonConstants; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class MultiStageBrokerRequestHandlerTest { + + private PinotConfiguration _config; + @Mock + private BrokerRoutingManager _routingManager; + + private AccessControlFactory _accessControlFactory; + @Mock + private QueryQuotaManager _queryQuotaManager; + @Mock + private TableCache _tableCache; + + @Mock + private BrokerMetrics _brokerMetrics; + + private MultiStageBrokerRequestHandler _requestHandler; + + @BeforeClass + public void setUp() { + MockitoAnnotations.openMocks(this); + _config = new PinotConfiguration(); + _config.setProperty(CommonConstants.Broker.CONFIG_OF_BROKER_TIMEOUT_MS, "10000"); + _config.setProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT, "12345"); + _accessControlFactory = new AllowAllAccessControlFactory(); + _requestHandler = + new MultiStageBrokerRequestHandler(_config, "testBrokerId", _routingManager, _accessControlFactory, + _queryQuotaManager, _tableCache, _brokerMetrics); + } + + @Test + public void testSetRequestId() + throws Exception { + String sampleSqlQuery = "SELECT * FROM testTable"; + String sampleJsonRequest = String.format("{\"sql\":\"%s\"}", sampleSqlQuery); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonRequest = objectMapper.readTree(sampleJsonRequest); + RequestContext requestContext = new DefaultRequestContext(); + + List requestIds = new ArrayList<>(); + // Request id should be unique each time, and there should be a difference of 1 between consecutive requestIds. + for (int iteration = 0; iteration < 10; iteration++) { + _requestHandler.handleRequest(jsonRequest, null, null, requestContext); + Assert.assertTrue(requestContext.getRequestId() >= 0, "Request ID should be non-negative"); + requestIds.add(requestContext.getRequestId()); + if (iteration != 0) { + Assert.assertEquals(1, requestIds.get(iteration) - requestIds.get(iteration - 1), + "Request Id should have difference of 1"); + } + } + Assert.assertEquals(10, requestIds.stream().distinct().count(), "Request Id should be unique"); + Assert.assertEquals(1, requestIds.stream().map(x -> (x >> 32)).distinct().count(), + "Request Id should have a broker-id specific mask for the 32 MSB"); + Assert.assertTrue(requestIds.stream().noneMatch(x -> x < 0), "Request Id should not be negative"); + } + + @AfterClass + public void tearDown() { + _requestHandler.shutDown(); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java index 07efbe101ab..a9ff0f18192 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java @@ -20,6 +20,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.mercateo.test.clock.TestClock; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,89 +36,206 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.spi.config.table.RoutingConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import static org.apache.pinot.broker.routing.instanceselector.InstanceSelector.NEW_SEGMENT_EXPIRATION_MILLIS; +import static org.apache.pinot.spi.config.table.RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE; +import static org.apache.pinot.spi.config.table.RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.CONSUMING; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ERROR; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.OFFLINE; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +@SuppressWarnings("unchecked") public class InstanceSelectorTest { - private final static List SEGMENTS = Arrays.asList("segment0", "segment1", "segment2", "segment3", "segment4", - "segment5", "segment6", "segment7", "segment8", "segment9", "segment10", "segment11"); + private AutoCloseable _mocks; + + @Mock + private ZkHelixPropertyStore _propertyStore; + + @Mock + private BrokerMetrics _brokerMetrics; + + @Mock + private BrokerRequest _brokerRequest; + + @Mock + private PinotQuery _pinotQuery; + + @Mock + private TableConfig _tableConfig; + + private TestClock _mutableClock; + + private static final String TABLE_NAME = "testTable_OFFLINE"; + + private static final String BALANCED_INSTANCE_SELECTOR = "balanced"; + + private static List getSegments() { + return SEGMENTS; + } + + private final static List SEGMENTS = + Arrays.asList("segment0", "segment1", "segment2", "segment3", "segment4", "segment5", "segment6", "segment7", + "segment8", "segment9", "segment10", "segment11"); + + private void createSegments(List> segmentPushMillis) { + List segmentZKMetadataPaths = new ArrayList<>(); + List zkRecords = new ArrayList<>(); + for (Pair segment : segmentPushMillis) { + SegmentZKMetadata offlineSegmentZKMetadata0 = new SegmentZKMetadata(segment.getLeft()); + offlineSegmentZKMetadata0.setPushTime(segment.getRight()); + offlineSegmentZKMetadata0.setTimeUnit(TimeUnit.MILLISECONDS); + ZNRecord record = offlineSegmentZKMetadata0.toZNRecord(); + segmentZKMetadataPaths.add( + ZKMetadataProvider.constructPropertyStorePathForSegment(TABLE_NAME, segment.getLeft())); + zkRecords.add(record); + } + when(_propertyStore.get(eq(segmentZKMetadataPaths), any(), anyInt(), anyBoolean())).thenReturn(zkRecords); + } + + private IdealState createIdealState(Map>> segmentState) { + IdealState idealState = new IdealState(TABLE_NAME); + Map> idealStateSegmentAssignment = idealState.getRecord().getMapFields(); + for (Map.Entry>> entry : segmentState.entrySet()) { + Map externalViewInstanceStateMap = new TreeMap<>(); + for (Pair instanceState : entry.getValue()) { + externalViewInstanceStateMap.put(instanceState.getLeft(), instanceState.getRight()); + } + idealStateSegmentAssignment.put(entry.getKey(), externalViewInstanceStateMap); + } + return idealState; + } + + private ExternalView createExternalView(Map>> segmentState) { + ExternalView externalView = new ExternalView(TABLE_NAME); + Map> externalViewSegmentAssignment = externalView.getRecord().getMapFields(); + for (Map.Entry>> entry : segmentState.entrySet()) { + Map externalViewInstanceStateMap = new TreeMap<>(); + for (Pair instanceState : entry.getValue()) { + externalViewInstanceStateMap.put(instanceState.getLeft(), instanceState.getRight()); + } + externalViewSegmentAssignment.put(entry.getKey(), externalViewInstanceStateMap); + } + return externalView; + } + + private static boolean isReplicaGroupType(String selectorType) { + return selectorType.equals(RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) || selectorType.equals( + STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); + } + + private InstanceSelector createTestInstanceSelector(String selectorType) { + RoutingConfig config = new RoutingConfig(null, null, selectorType); + when(_tableConfig.getRoutingConfig()).thenReturn(config); + return InstanceSelectorFactory.getInstanceSelector(_tableConfig, _propertyStore, _brokerMetrics, null, + _mutableClock); + } + + @DataProvider(name = "selectorType") + public Object[] getSelectorType() { + return new Object[]{ + RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE, STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE, + BALANCED_INSTANCE_SELECTOR + }; + } + + @BeforeMethod + public void setUp() { + _mutableClock = TestClock.fixed(Instant.now(), ZoneId.systemDefault()); + _mocks = MockitoAnnotations.openMocks(this); + when(_brokerRequest.getPinotQuery()).thenReturn(_pinotQuery); + when(_pinotQuery.getQueryOptions()).thenReturn(null); + when(_tableConfig.getTableName()).thenReturn(TABLE_NAME); + } + + @AfterMethod + public void tearDown() + throws Exception { + clearInvocations(_tableConfig); + _mocks.close(); + } @Test public void testInstanceSelectorFactory() { TableConfig tableConfig = mock(TableConfig.class); - BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); - AdaptiveServerSelector adaptiveServerSelector = null; + BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); // Routing config is missing - assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof BalancedInstanceSelector); + assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, + brokerMetrics) instanceof BalancedInstanceSelector); // Instance selector type is not configured RoutingConfig routingConfig = mock(RoutingConfig.class); when(tableConfig.getRoutingConfig()).thenReturn(routingConfig); - assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof BalancedInstanceSelector); + assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, + brokerMetrics) instanceof BalancedInstanceSelector); // Replica-group instance selector should be returned when(routingConfig.getInstanceSelectorType()).thenReturn(RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); - assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof ReplicaGroupInstanceSelector); + assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, + brokerMetrics) instanceof ReplicaGroupInstanceSelector); // Strict replica-group instance selector should be returned - when(routingConfig.getInstanceSelectorType()).thenReturn(RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); - assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof StrictReplicaGroupInstanceSelector); + when(routingConfig.getInstanceSelectorType()).thenReturn(STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); + assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, + brokerMetrics) instanceof StrictReplicaGroupInstanceSelector); // Should be backward-compatible with legacy config when(routingConfig.getInstanceSelectorType()).thenReturn(null); when(tableConfig.getTableType()).thenReturn(TableType.OFFLINE); when(routingConfig.getRoutingTableBuilderName()).thenReturn( InstanceSelectorFactory.LEGACY_REPLICA_GROUP_OFFLINE_ROUTING); - assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof ReplicaGroupInstanceSelector); + assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, + brokerMetrics) instanceof ReplicaGroupInstanceSelector); when(tableConfig.getTableType()).thenReturn(TableType.REALTIME); when(routingConfig.getRoutingTableBuilderName()).thenReturn( InstanceSelectorFactory.LEGACY_REPLICA_GROUP_REALTIME_ROUTING); - assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof ReplicaGroupInstanceSelector); + assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, + brokerMetrics) instanceof ReplicaGroupInstanceSelector); } @Test public void testInstanceSelector() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); - AdaptiveServerSelector adaptiveServerSelector = null; - BalancedInstanceSelector balancedInstanceSelector = new BalancedInstanceSelector(offlineTableName, brokerMetrics, - adaptiveServerSelector); + BalancedInstanceSelector balancedInstanceSelector = + new BalancedInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); StrictReplicaGroupInstanceSelector strictReplicaGroupInstanceSelector = - new StrictReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new StrictReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -201,8 +325,8 @@ public void testInstanceSelector() { expectedBalancedInstanceSelectorResult.put(segment1, instance2); expectedBalancedInstanceSelectorResult.put(segment2, instance1); expectedBalancedInstanceSelectorResult.put(segment3, instance3); - InstanceSelector.SelectionResult selectionResult = balancedInstanceSelector.select(brokerRequest, segments, - requestId); + InstanceSelector.SelectionResult selectionResult = + balancedInstanceSelector.select(brokerRequest, segments, requestId); assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); assertTrue(selectionResult.getUnavailableSegments().isEmpty()); Map expectedReplicaGroupInstanceSelectorResult = new HashMap<>(); @@ -392,6 +516,7 @@ public void testInstanceSelector() { assertTrue(selectionResult.getUnavailableSegments().isEmpty()); // Process the changes + // segment4 is a newly added segment with 3 online instances in idealStage and 2 online instances in externalView. balancedInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); replicaGroupInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); strictReplicaGroupInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); @@ -551,6 +676,7 @@ public void testInstanceSelector() { // segment2 -> instance1 // segment3 -> instance1 // segment4 -> instance0 + // instance0 is excluded from serving segment4 because instance0 has error for serving segment1 // StrictReplicaGroupInstanceSelector: // segment1 -> instance2 // segment2 -> instance1 @@ -618,6 +744,7 @@ public void testInstanceSelector() { @Test public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); @@ -626,10 +753,9 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { queryOptions.put("numReplicaGroupsToQuery", "2"); when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); - AdaptiveServerSelector adaptiveServerSelector = null; ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -658,7 +784,7 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { List segments = getSegments(); // add all segments to both idealStateSegmentAssignment and externalViewSegmentAssignment maps and also to online // segments - for (String segment: segments) { + for (String segment : segments) { idealStateSegmentAssignment.put(segment, idealStateInstanceStateMap0); externalViewSegmentAssignment.put(segment, externalViewInstanceStateMap0); onlineSegments.add(segment); @@ -700,18 +826,18 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { @Test public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQueryGreaterThanReplicas() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); Map queryOptions = new HashMap<>(); queryOptions.put("numReplicaGroupsToQuery", "4"); - AdaptiveServerSelector adaptiveServerSelector = null; when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -741,7 +867,7 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQueryGreaterThanRe // add all segments to both idealStateSegmentAssignment and externalViewSegmentAssignment maps and also to online // segments - for (String segment: segments) { + for (String segment : segments) { idealStateSegmentAssignment.put(segment, idealStateInstanceStateMap0); externalViewSegmentAssignment.put(segment, externalViewInstanceStateMap0); onlineSegments.add(segment); @@ -783,17 +909,17 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQueryGreaterThanRe @Test public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); Map queryOptions = new HashMap<>(); - AdaptiveServerSelector adaptiveServerSelector = null; when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -823,7 +949,7 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { // add all segments to both idealStateSegmentAssignment and externalViewSegmentAssignment maps and also to online // segments - for (String segment: segments) { + for (String segment : segments) { idealStateSegmentAssignment.put(segment, idealStateInstanceStateMap0); externalViewSegmentAssignment.put(segment, externalViewInstanceStateMap0); onlineSegments.add(segment); @@ -834,13 +960,13 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { // 2nd query should go to next replica group Map expectedReplicaGroupInstanceSelectorResult = new HashMap<>(); - for (String segment: segments) { + for (String segment : segments) { expectedReplicaGroupInstanceSelectorResult.put(segment, instance0); } InstanceSelector.SelectionResult selectionResult = replicaGroupInstanceSelector.select(brokerRequest, segments, 0); assertEquals(selectionResult.getSegmentToInstanceMap(), expectedReplicaGroupInstanceSelectorResult); - for (String segment: segments) { + for (String segment : segments) { expectedReplicaGroupInstanceSelectorResult.put(segment, instance1); } selectionResult = replicaGroupInstanceSelector.select(brokerRequest, segments, 1); @@ -850,27 +976,23 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { @Test public void testMultiStageStrictReplicaGroupSelector() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); // Create instance-partitions with two replica-groups and 1 partition. Each replica-group has 2 instances. List replicaGroup0 = ImmutableList.of("instance-0", "instance-1"); List replicaGroup1 = ImmutableList.of("instance-2", "instance-3"); - Map> partitionToInstances = ImmutableMap.of( - "0_0", replicaGroup0, - "0_1", replicaGroup1); + Map> partitionToInstances = ImmutableMap.of("0_0", replicaGroup0, "0_1", replicaGroup1); InstancePartitions instancePartitions = new InstancePartitions(offlineTableName); instancePartitions.setInstances(0, 0, partitionToInstances.get("0_0")); instancePartitions.setInstances(0, 1, partitionToInstances.get("0_1")); - BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); Map queryOptions = new HashMap<>(); - when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); - ZkHelixPropertyStore propertyStore = (ZkHelixPropertyStore) mock(ZkHelixPropertyStore.class); - MultiStageReplicaGroupSelector multiStageSelector = - new MultiStageReplicaGroupSelector(offlineTableName, propertyStore, brokerMetrics, null); + new MultiStageReplicaGroupSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); multiStageSelector = spy(multiStageSelector); doReturn(instancePartitions).when(multiStageSelector).getInstancePartitions(); @@ -962,13 +1084,13 @@ public void testMultiStageStrictReplicaGroupSelector() { @Test public void testUnavailableSegments() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); - AdaptiveServerSelector adaptiveServerSelector = null; - BalancedInstanceSelector balancedInstanceSelector = new BalancedInstanceSelector(offlineTableName, brokerMetrics, - adaptiveServerSelector); + BalancedInstanceSelector balancedInstanceSelector = + new BalancedInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); // ReplicaGroupInstanceSelector has the same behavior as BalancedInstanceSelector for the unavailable segments StrictReplicaGroupInstanceSelector strictReplicaGroupInstanceSelector = - new StrictReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new StrictReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC()); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -1154,10 +1276,11 @@ public void testUnavailableSegments() { strictReplicaGroupInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); selectionResult = balancedInstanceSelector.select(brokerRequest, segments, 0); assertTrue(selectionResult.getSegmentToInstanceMap().isEmpty()); - assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + // segment1 and segment0 are considered old therefore we should report it as unavailable. + assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); selectionResult = strictReplicaGroupInstanceSelector.select(brokerRequest, segments, 0); assertTrue(selectionResult.getSegmentToInstanceMap().isEmpty()); - assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); // Disable the OFFLINE instance, both segments should be unavailable // { @@ -1181,8 +1304,8 @@ public void testUnavailableSegments() { assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); // Change the ERROR instance of segment0 to ONLINE, segment0 should be available - // (Note that for StrictReplicaGroupInstanceSelector, segment1 does not have any ONLINE/CONSUMING instance, so it - // won't mark instance down for segment0) + // (Note that for StrictReplicaGroupInstanceSelector, segment1 but it is old so it will mark instance down for + // segment0) // { // segment0: { // (disabled) instance: OFFLINE, @@ -1200,8 +1323,8 @@ public void testUnavailableSegments() { assertEquals(selectionResult.getSegmentToInstanceMap().size(), 1); assertEquals(selectionResult.getUnavailableSegments(), Collections.singletonList(segment1)); selectionResult = strictReplicaGroupInstanceSelector.select(brokerRequest, segments, 0); - assertEquals(selectionResult.getSegmentToInstanceMap().size(), 1); - assertEquals(selectionResult.getUnavailableSegments(), Collections.singletonList(segment1)); + assertTrue(selectionResult.getSegmentToInstanceMap().isEmpty()); + assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); // Disable the ONLINE instance, both segments should be unavailable // { @@ -1250,7 +1373,567 @@ public void testUnavailableSegments() { } } - private static List getSegments() { - return SEGMENTS; + @Test(dataProvider = "selectorType") + public void testNewSegmentFromZKMetadataSelection(String selectorType) { + String oldSeg = "segment0"; + String newSeg = "segment1"; + List> segmentPushTime = ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance1:online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance1, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + InstanceSelector selector = createTestInstanceSelector(selectorType); + + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + { + int requestId = 0; + // First selection, we select instance0 for oldSeg and instance1 for newSeg in balance selector + // For replica group, we select instance0 for oldSeg and newSeg. Because newSeg is not online in instance0, so + // we exclude it from selection result. + Map expectedSelectionResult; + if (isReplicaGroupType(selectorType)) { + expectedSelectionResult = ImmutableMap.of(oldSeg, instance0); + } else { + expectedSelectionResult = ImmutableMap.of(oldSeg, instance0, newSeg, instance1); + } + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedSelectionResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + { + int requestId = 1; + // Second selection, we select instance1 for oldSeg and instance0 for newSeg in balance selector + // Because newSeg is not online in instance0, so we exclude it from selection result. + // For replica group, we select instance1 for oldSeg and newSeg. + Map expectedSelectionResult; + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: + expectedSelectionResult = ImmutableMap.of(oldSeg, instance1); + break; + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: // fall through + case RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedSelectionResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + break; + default: + throw new RuntimeException("unsupported selector type:" + selectorType); + } + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedSelectionResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + // Advance the clock to make newSeg to old segment. + _mutableClock.fastForward(Duration.ofMillis(NEW_SEGMENT_EXPIRATION_MILLIS + 10)); + // Upon re-initialization, newly old segments can only be served from online instances: instance1 + selector.init(enabledInstances, idealState, externalView, onlineSegments); + { + int requestId = 0; + Map expectedSelectionResult; + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: // fall through + case RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedSelectionResult = ImmutableMap.of(oldSeg, instance0, newSeg, instance1); + break; + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedSelectionResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + break; + default: + throw new RuntimeException("unsupported selector type:" + selectorType); + } + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedSelectionResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + { + int requestId = 1; + Map expectedSelectionResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedSelectionResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + } + + // Test that we don't report new segment as unavailable till it gets old. + @Test(dataProvider = "selectorType") + public void testNewSegmentFromZKMetadataReportingUnavailable(String selectorType) { + // Set segment0 as new segment + String newSeg = "segment0"; + String oldSeg = "segment1"; + List> segmentPushTime = ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100), + Pair.of(oldSeg, _mutableClock.millis() - NEW_SEGMENT_EXPIRATION_MILLIS - 100)); + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(newSeg, oldSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for new segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for new segments + // [segment0] -> [] + // [segment1] -> [instance0: online] + Map>> externalViewMap = + ImmutableMap.of(newSeg, ImmutableList.of(), oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedResult = ImmutableMap.of(oldSeg, instance0); + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Advance the clock to make newSeg to old segment and we see newSeg is reported as unavailable segment. + _mutableClock.fastForward(Duration.ofMillis(NEW_SEGMENT_EXPIRATION_MILLIS + 10)); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedResult = ImmutableMap.of(); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg, oldSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + } + + @Test(dataProvider = "selectorType") + public void testNewSegmentGetsOldWithErrorState(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentPushTime = ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of()); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedResult = ImmutableMap.of(oldSeg, instance0); + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Report error instance for segment1 since segment1 becomes old and we should report it as unavailable. + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ERROR))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedResult = ImmutableMap.of(); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg, newSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + + // Get segment1 back online in instance1 + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ERROR), Pair.of(instance1, ONLINE))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + } else { + expectedResult = ImmutableMap.of(oldSeg, instance0, newSeg, instance1); + } + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + + // Test that we mark new segment as old when external view state converges with ideal state. + @Test(dataProvider = "selectorType") + public void testNewSegmentGetsOldWithStateConverge(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentPushTime = ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of()); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedBalancedInstanceSelectorResult = ImmutableMap.of(oldSeg, instance0); + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Segment1 is not old anymore with state converge. + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + + // Segment1 becomes unavailable. + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of()); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + + selector.onAssignmentChange(idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedBalancedInstanceSelectorResult = ImmutableMap.of(); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg, newSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + } + + @Test(dataProvider = "selectorType") + public void testNewSegmentsFromIDWithMissingEV(String selectorType) { + String oldSeg0 = "segment0"; + String oldSeg1 = "segment1"; + Set onlineSegments = ImmutableSet.of(oldSeg0, oldSeg1); + + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + + Map>> idealSateMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // Add a new segment to ideal state with missing external view. + String newSeg = "segment2"; + onlineSegments = ImmutableSet.of(oldSeg0, oldSeg1, newSeg); + idealSateMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + idealState = createIdealState(idealSateMap); + externalViewMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + + int requestId = 0; + Map expectedResult; + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: + expectedResult = ImmutableMap.of(oldSeg0, instance0, oldSeg1, instance1); + break; + case REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: // fall through + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedResult = ImmutableMap.of(oldSeg0, instance0, oldSeg1, instance0); + break; + default: + throw new RuntimeException("unsupported type:" + selectorType); + } + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Advance the clock to make newSeg to old segment. + // On state update, all segments become unavailable. + _mutableClock.fastForward(Duration.ofMillis(NEW_SEGMENT_EXPIRATION_MILLIS + 10)); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg0, oldSeg1, newSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + } + + // Test that on instance change, we exclude not enabled instance from serving for new segments. + @Test(dataProvider = "selectorType") + public void testExcludeNotEnabledInstanceForNewSegment(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentPushTime = ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // First selection, we select instance1 for newSeg. + int requestId = 0; + Map expectedResult; + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: + expectedResult = ImmutableMap.of(oldSeg, instance0); + break; + case REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedResult = ImmutableMap.of(oldSeg, instance0, newSeg, instance0); + break; + default: + throw new RuntimeException("Unsupported type:" + selectorType); + } + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Remove instance0 from enabledInstances. + enabledInstances = ImmutableSet.of(instance1); + List changeInstance = ImmutableList.of(instance0); + selector.onInstancesChange(enabledInstances, changeInstance); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + // We don't include instance0 in selection anymore. + expectedResult = ImmutableMap.of(oldSeg, instance1); + + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + + @Test(dataProvider = "selectorType") + public void testExcludeInstanceNotInIdealState(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentPushTime = + ImmutableList.of(Pair.of(oldSeg, _mutableClock.millis() - NEW_SEGMENT_EXPIRATION_MILLIS - 100), + Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + String instance2 = "instance2"; + Set enabledInstances = ImmutableSet.of(instance0, instance1, instance2); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance2: online] + // [segment1] -> [instance2: online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance2, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance2, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // No selection because the external view is not in ideal state. + int requestId = 0; + Map expectedBalancedInstanceSelectorResult = ImmutableMap.of(); + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg)); + } + + @Test(dataProvider = "selectorType") + public void testExcludeIdealStateOffline(String selectorType) { + // Set segment0 as old segment + String newSeg = "segment0"; + String oldSeg = "segment1"; + + List> segmentPushTime = ImmutableList.of( + Pair.of(newSeg, _mutableClock.millis() - 100), + Pair.of(oldSeg, _mutableClock.millis() - NEW_SEGMENT_EXPIRATION_MILLIS - 100)); + + createSegments(segmentPushTime); + Set onlineSegments = ImmutableSet.of(newSeg, oldSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:offline] + Map>> idealSateMap = + ImmutableMap.of( + newSeg, ImmutableList.of(Pair.of(instance0, OFFLINE), Pair.of(instance1, ONLINE)), + oldSeg, ImmutableList.of(Pair.of(instance0, OFFLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:offline, instance1:online] + Map>> externalViewMap = + ImmutableMap.of( + newSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), + oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedBalancedInstanceSelectorResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcherTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcherTest.java new file mode 100644 index 00000000000..151b9f4689b --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcherTest.java @@ -0,0 +1,175 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentmetadata; + +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.broker.routing.segmentpruner.SegmentPruner; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.controller.helix.ControllerTest; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.fail; + + +public class SegmentZkMetadataFetcherTest extends ControllerTest { + private static final String OFFLINE_TABLE_NAME = "testTable_OFFLINE"; + + @Test + public void testSegmentZkMetadataFetcherShouldNotAllowIncorrectRegisterOrInitBehavior() { + ZkHelixPropertyStore mockPropertyStore = Mockito.mock(ZkHelixPropertyStore.class); + IdealState idealState = Mockito.mock(IdealState.class); + ExternalView externalView = Mockito.mock(ExternalView.class); + + // empty listener at beginning + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, mockPropertyStore); + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 0); + + // should allow register new listener + segmentZkMetadataFetcher.register(mock(SegmentZkMetadataFetchListener.class)); + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 1); + + // should not allow register new listener once initialized + segmentZkMetadataFetcher.init(idealState, externalView, Collections.emptySet()); + try { + segmentZkMetadataFetcher.register(mock(SegmentZkMetadataFetchListener.class)); + fail(); + } catch (RuntimeException rte) { + assertTrue(rte.getMessage().contains("has already been initialized")); + } + + // should not allow duplicate init either + try { + segmentZkMetadataFetcher.init(idealState, externalView, Collections.emptySet()); + fail(); + } catch (RuntimeException rte) { + assertTrue(rte.getMessage().contains("has already been initialized")); + } + } + + @Test + public void testSegmentZkMetadataFetcherShouldNotPullZkWhenNoPrunerRegistered() { + ZkHelixPropertyStore mockPropertyStore = Mockito.mock(ZkHelixPropertyStore.class); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, mockPropertyStore); + // NOTE: Ideal state and external view are not used in the current implementation + IdealState idealState = Mockito.mock(IdealState.class); + ExternalView externalView = Mockito.mock(ExternalView.class); + + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 0); + segmentZkMetadataFetcher.init(idealState, externalView, Collections.singleton("foo")); + Mockito.verify(mockPropertyStore, times(0)).get(any(), any(), anyInt(), anyBoolean()); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, Collections.singleton("foo")); + Mockito.verify(mockPropertyStore, times(0)).get(any(), any(), anyInt(), anyBoolean()); + segmentZkMetadataFetcher.refreshSegment("foo"); + Mockito.verify(mockPropertyStore, times(0)).get(any(), any(), anyInt(), anyBoolean()); + } + + @Test + public void testSegmentZkMetadataFetcherShouldPullZkOnlyOncePerSegmentWhenMultiplePrunersRegistered() { + ZkHelixPropertyStore mockPropertyStore = mock(ZkHelixPropertyStore.class); + when(mockPropertyStore.get(any(), any(), anyInt(), anyBoolean())).thenAnswer(inv -> { + List pathList = inv.getArgument(0); + List result = new ArrayList<>(pathList.size()); + for (String path : pathList) { + String[] pathParts = path.split("/"); + String segmentName = pathParts[pathParts.length - 1]; + SegmentZKMetadata fakeSegmentZkMetadata = new SegmentZKMetadata(segmentName); + result.add(fakeSegmentZkMetadata.toZNRecord()); + } + return result; + }); + SegmentPruner pruner1 = mock(SegmentPruner.class); + SegmentPruner pruner2 = mock(SegmentPruner.class); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, mockPropertyStore); + segmentZkMetadataFetcher.register(pruner1); + segmentZkMetadataFetcher.register(pruner2); + // NOTE: Ideal state and external view are not used in the current implementation + IdealState idealState = mock(IdealState.class); + ExternalView externalView = mock(ExternalView.class); + + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 2); + // should call property store once for "foo" and "bar" as a batch + segmentZkMetadataFetcher.init(idealState, externalView, ImmutableSet.of("foo", "bar")); + verify(mockPropertyStore, times(1)).get(argThat(new ListMatcher("foo", "bar")), any(), anyInt(), anyBoolean()); + verify(pruner1, times(1)).init(any(), any(), argThat(new ListMatcher("foo", "bar")), any()); + verify(pruner2, times(1)).init(any(), any(), argThat(new ListMatcher("foo", "bar")), any()); + + // should call property store only once b/c "alice" was missing + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, ImmutableSet.of("bar", "alice")); + verify(mockPropertyStore, times(1)).get(argThat(new ListMatcher("alice")), any(), anyInt(), anyBoolean()); + verify(pruner1, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher("alice")), any()); + verify(pruner2, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher("alice")), any()); + + // should call property store once more b/c "foo" was cleared when onAssignmentChange called with "bar" and "alice" + segmentZkMetadataFetcher.refreshSegment("foo"); + verify(mockPropertyStore, times(1)).get(endsWith("foo"), any(), anyInt()); + verify(pruner1, times(1)).refreshSegment(eq("foo"), any()); + verify(pruner2, times(1)).refreshSegment(eq("foo"), any()); + clearInvocations(mockPropertyStore, pruner1, pruner2); + + // update with all existing segments will call into property store and pruner with empty list + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, ImmutableSet.of("bar", "alice")); + verify(mockPropertyStore, times(1)).get(argThat(new ListMatcher()), any(), anyInt(), anyBoolean()); + verify(pruner1, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher()), any()); + verify(pruner2, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher()), any()); + + // calling refresh will still force pull from property store + segmentZkMetadataFetcher.refreshSegment("foo"); + verify(mockPropertyStore, times(1)).get(endsWith("foo"), any(), anyInt()); + verify(pruner1, times(1)).refreshSegment(eq("foo"), any()); + verify(pruner2, times(1)).refreshSegment(eq("foo"), any()); + } + + private static class ListMatcher implements ArgumentMatcher> { + private final List _valueToMatch; + + private ListMatcher(String... values) { + _valueToMatch = Arrays.asList(values); + } + + @Override + public boolean matches(List arg) { + if (arg.size() != _valueToMatch.size()) { + return false; + } + for (int i = 0; i < arg.size(); i++) { + if (!arg.get(i).endsWith(_valueToMatch.get(i))) { + return false; + } + } + return true; + } + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManagerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManagerTest.java new file mode 100644 index 00000000000..6d5999707b4 --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManagerTest.java @@ -0,0 +1,260 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.helix.manager.zk.ZkBaseDataAccessor; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer; +import org.apache.helix.zookeeper.impl.client.ZkClient; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetcher; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.core.routing.TablePartitionInfo; +import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; +import org.apache.pinot.spi.config.table.ColumnPartitionConfig; +import org.apache.pinot.spi.config.table.SegmentPartitionConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertEqualsNoOrder; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + + +public class SegmentPartitionMetadataManagerTest extends ControllerTest { + private static final String OFFLINE_TABLE_NAME = "testTable_OFFLINE"; + private static final String PARTITION_COLUMN = "memberId"; + private static final String PARTITION_COLUMN_FUNC = "Murmur"; + private static final int NUM_PARTITIONS = 2; + private static final String PARTITION_COLUMN_FUNC_ALT = "Modulo"; + private static final int NUM_PARTITIONS_ALT = 4; + private static final String SERVER_0 = "server0"; + private static final String SERVER_1 = "server1"; + + private ZkClient _zkClient; + private ZkHelixPropertyStore _propertyStore; + + @BeforeClass + public void setUp() { + startZk(); + _zkClient = new ZkClient(getZkUrl(), ZkClient.DEFAULT_SESSION_TIMEOUT, ZkClient.DEFAULT_CONNECTION_TIMEOUT, + new ZNRecordSerializer()); + _propertyStore = + new ZkHelixPropertyStore<>(new ZkBaseDataAccessor<>(_zkClient), "/TimeBoundaryManagerTest/PROPERTYSTORE", null); + } + + @AfterClass + public void tearDown() { + _zkClient.close(); + stopZk(); + } + + @Test + public void testPartitionMetadataManagerProcessingThroughSegmentChangesSinglePartitionTable() { + // NOTE: Ideal state and external view are not used in the current implementation + TableConfig tableConfig = + getTableConfig(new String[]{PARTITION_COLUMN}, new String[]{PARTITION_COLUMN_FUNC}, new int[]{NUM_PARTITIONS}); + ExternalView externalView = new ExternalView(tableConfig.getTableName()); + Map> segmentAssignment = externalView.getRecord().getMapFields(); + Map onlineInstanceStateMap = ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE); + Set onlineSegments = new HashSet<>(); + // NOTE: Ideal state is not used in the current implementation. + IdealState idealState = new IdealState(""); + + SegmentPartitionMetadataManager partitionMetadataManager = + new SegmentPartitionMetadataManager(OFFLINE_TABLE_NAME, PARTITION_COLUMN, PARTITION_COLUMN_FUNC, + NUM_PARTITIONS); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(partitionMetadataManager); + + // Initial state should be all empty + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + TablePartitionInfo tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding segment without partition metadata should be recorded in the invalid segments + String segmentWithoutPartitionMetadata = "segmentWithoutPartitionMetadata"; + onlineSegments.add(segmentWithoutPartitionMetadata); + segmentAssignment.put(segmentWithoutPartitionMetadata, onlineInstanceStateMap); + SegmentZKMetadata segmentZKMetadataWithoutPartitionMetadata = + new SegmentZKMetadata(segmentWithoutPartitionMetadata); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, + segmentZKMetadataWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertEquals(tablePartitionInfo.getSegmentsWithInvalidPartition(), + Collections.singletonList(segmentWithoutPartitionMetadata)); + + // Removing segment without partition metadata should remove it from the invalid segments + onlineSegments.remove(segmentWithoutPartitionMetadata); + segmentAssignment.remove(segmentWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding segments inline with the partition column config should yield correct partition results + String segment0 = "segment0"; + onlineSegments.add(segment0); + segmentAssignment.put(segment0, Collections.singletonMap(SERVER_0, ONLINE)); + setSegmentZKPartitionMetadata(segment0, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 0); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + TablePartitionInfo.PartitionInfo[] partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertNull(partitionInfoMap[1]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding one more segments + String segment1 = "segment1"; + onlineSegments.add(segment1); + segmentAssignment.put(segment1, Collections.singletonMap(SERVER_1, ONLINE)); + setSegmentZKPartitionMetadata(segment1, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 1); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Updating partition metadata without refreshing should have no effect + setSegmentZKPartitionMetadata(segment0, PARTITION_COLUMN_FUNC_ALT, NUM_PARTITIONS_ALT, 0); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Refreshing the changed segment should update the partition info + segmentZkMetadataFetcher.refreshSegment(segment0); + // segment0 is no longer inline with the table config, and it should be recorded in the invalid segments + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertNull(partitionInfoMap[0]); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertEquals(tablePartitionInfo.getSegmentsWithInvalidPartition(), Collections.singletonList(segment0)); + + // Refresh the changed segment back to inline, and both segments should now be back on the partition list + setSegmentZKPartitionMetadata(segment0, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 0); + segmentZkMetadataFetcher.refreshSegment(segment0); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Changing one of the segments to be on a different server should update the fully replicated servers + segmentAssignment.put(segment1, Collections.singletonMap(SERVER_0, ONLINE)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding one more segment to partition-1 but located on a different server will update the partition map, but + // remove the fully replicated server because it is no longer having full replica on a single server + String segment2 = "segment2"; + onlineSegments.add(segment2); + segmentAssignment.put(segment2, Collections.singletonMap(SERVER_1, ONLINE)); + setSegmentZKPartitionMetadata(segment2, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 1); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertTrue(partitionInfoMap[1]._fullyReplicatedServers.isEmpty()); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Updating the new segment to be replicated on 2 servers should add the fully replicated server back + segmentAssignment.put(segment2, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Making all of them replicated will show full list + segmentAssignment.put(segment0, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentAssignment.put(segment1, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentAssignment.put(segment2, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, ImmutableSet.of(SERVER_0, SERVER_1)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, ImmutableSet.of(SERVER_0, SERVER_1)); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + } + + private TableConfig getTableConfig(String[] partitionColumns, String[] partitionFunctions, int[] partitionSizes) { + Map partitionColumnMetadataMap = new HashMap<>(); + for (int idx = 0; idx < partitionColumns.length; idx++) { + partitionColumnMetadataMap.put(partitionColumns[idx], + new ColumnPartitionConfig(partitionFunctions[idx], partitionSizes[idx])); + } + return new TableConfigBuilder(TableType.OFFLINE).setTableName(OFFLINE_TABLE_NAME) + .setSegmentPartitionConfig(new SegmentPartitionConfig(partitionColumnMetadataMap)).build(); + } + + private void setSegmentZKPartitionMetadata(String segment, String partitionFunction, int numPartitions, + int partitionId) { + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segment); + segmentZKMetadata.setPartitionMetadata(new SegmentPartitionMetadata(Collections.singletonMap(PARTITION_COLUMN, + new ColumnPartitionMetadata(partitionFunction, numPartitions, Collections.singleton(partitionId), null)))); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, segmentZKMetadata); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java index cc60739d369..feaad35169b 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java @@ -35,6 +35,7 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer; import org.apache.helix.zookeeper.impl.client.ZkClient; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetcher; import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; @@ -263,9 +264,12 @@ public void testPartitionAwareSegmentPruner() { ExternalView externalView = Mockito.mock(ExternalView.class); SinglePartitionColumnSegmentPruner singlePartitionColumnSegmentPruner = - new SinglePartitionColumnSegmentPruner(OFFLINE_TABLE_NAME, PARTITION_COLUMN_1, _propertyStore); + new SinglePartitionColumnSegmentPruner(OFFLINE_TABLE_NAME, PARTITION_COLUMN_1); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, + _propertyStore); + segmentZkMetadataFetcher.register(singlePartitionColumnSegmentPruner); Set onlineSegments = new HashSet<>(); - singlePartitionColumnSegmentPruner.init(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, Collections.emptySet()), Collections.emptySet()); assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, Collections.emptySet()), @@ -289,7 +293,7 @@ public void testPartitionAwareSegmentPruner() { new SegmentZKMetadata(segmentWithoutPartitionMetadata); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, segmentZKMetadataWithoutPartitionMetadata); - singlePartitionColumnSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Collections.singletonList(segmentWithoutPartitionMetadata))), Collections.singletonList(segmentWithoutPartitionMetadata)); @@ -309,7 +313,7 @@ public void testPartitionAwareSegmentPruner() { String segment1 = "segment1"; onlineSegments.add(segment1); setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment1, "Murmur", 4, 0); - singlePartitionColumnSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals( singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), new HashSet<>(Arrays.asList(segment0, segment1))); @@ -322,7 +326,7 @@ public void testPartitionAwareSegmentPruner() { // Update partition metadata without refreshing should have no effect setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment0, "Modulo", 4, 1); - singlePartitionColumnSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals( singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), new HashSet<>(Arrays.asList(segment0, segment1))); @@ -334,7 +338,7 @@ public void testPartitionAwareSegmentPruner() { new HashSet<>(Collections.singletonList(segment1))); // Refresh the changed segment should update the segment pruner - singlePartitionColumnSegmentPruner.refreshSegment(segment0); + segmentZkMetadataFetcher.refreshSegment(segment0); assertEquals( singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), new HashSet<>(Arrays.asList(segment0, segment1))); @@ -348,8 +352,10 @@ public void testPartitionAwareSegmentPruner() { // Multi-column partitioned segment. MultiPartitionColumnsSegmentPruner multiPartitionColumnsSegmentPruner = new MultiPartitionColumnsSegmentPruner(OFFLINE_TABLE_NAME, - Stream.of(PARTITION_COLUMN_1, PARTITION_COLUMN_2).collect(Collectors.toSet()), _propertyStore); - multiPartitionColumnsSegmentPruner.init(idealState, externalView, onlineSegments); + Stream.of(PARTITION_COLUMN_1, PARTITION_COLUMN_2).collect(Collectors.toSet())); + segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(multiPartitionColumnsSegmentPruner); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest1, Collections.emptySet()), Collections.emptySet()); assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest2, Collections.emptySet()), @@ -367,10 +373,10 @@ public void testPartitionAwareSegmentPruner() { Map partitionColumn2FunctionConfig = new HashMap<>(); partitionColumn2FunctionConfig.put("columnValues", "xyz|abc"); partitionColumn2FunctionConfig.put("columnValuesDelimiter", "|"); - columnPartitionMetadataMap.put(PARTITION_COLUMN_2, - new ColumnPartitionMetadata("BoundedColumnValue", 3, Collections.singleton(1), partitionColumn2FunctionConfig)); + columnPartitionMetadataMap.put(PARTITION_COLUMN_2, new ColumnPartitionMetadata( + "BoundedColumnValue", 3, Collections.singleton(1), partitionColumn2FunctionConfig)); setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment2, columnPartitionMetadataMap); - multiPartitionColumnsSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals( multiPartitionColumnsSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), new HashSet<>(Arrays.asList(segment0, segment1))); @@ -399,10 +405,13 @@ public void testTimeSegmentPruner() { TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); setSchemaDateTimeFieldSpec(RAW_TABLE_NAME, TimeUnit.DAYS); - - TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); + TimeSegmentPruner segmentPruner = SegmentPrunerFactory.createTimeSegmentPruner(tableConfig, + _propertyStore); Set onlineSegments = new HashSet<>(); - segmentPruner.init(idealState, externalView, onlineSegments); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, + _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, Collections.emptySet()), Collections.emptySet()); assertEquals(segmentPruner.prune(brokerRequest2, Collections.emptySet()), Collections.emptySet()); assertEquals(segmentPruner.prune(brokerRequest3, Collections.emptySet()), Collections.emptySet()); @@ -413,10 +422,12 @@ public void testTimeSegmentPruner() { // Initialize with non-empty onlineSegments // Segments without metadata (not updated yet) should not be pruned - segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); + segmentPruner = SegmentPrunerFactory.createTimeSegmentPruner(tableConfig, _propertyStore); + segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); String newSegment = "newSegment"; onlineSegments.add(newSegment); - segmentPruner.init(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(newSegment)), Collections.singletonList(newSegment)); assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(newSegment)), @@ -440,7 +451,7 @@ public void testTimeSegmentPruner() { segmentZKMetadataWithoutTimeRangeMetadata.setStatus(CommonConstants.Segment.Realtime.Status.DONE); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, REALTIME_TABLE_NAME, segmentZKMetadataWithoutTimeRangeMetadata); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals( segmentPruner.prune(brokerRequest1, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), Collections.singletonList(segmentWithoutTimeRangeMetadata)); @@ -476,7 +487,7 @@ public void testTimeSegmentPruner() { onlineSegments.add(segment2); setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, 50, 65, TimeUnit.DAYS); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), new HashSet<>(Arrays.asList(segment0, segment1, segment2))); assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), @@ -510,7 +521,7 @@ public void testTimeSegmentPruner() { Collections.emptySet()); // Refresh the changed segment should update the segment pruner - segmentPruner.refreshSegment(segment2); + segmentZkMetadataFetcher.refreshSegment(segment2); assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), new HashSet<>(Arrays.asList(segment0, segment1, segment2))); assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), @@ -541,7 +552,10 @@ public void testTimeSegmentPrunerSimpleDateFormat() { TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); setSchemaDateTimeFieldSpecSDF(RAW_TABLE_NAME, SDF_PATTERN); - TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); + TimeSegmentPruner segmentPruner = SegmentPrunerFactory.createTimeSegmentPruner(tableConfig, _propertyStore); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, + _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, RAW_TABLE_NAME); DateTimeFormatSpec dateTimeFormatSpec = schema.getSpecForTimeColumn(TIME_COLUMN).getFormatSpec(); @@ -561,7 +575,7 @@ public void testTimeSegmentPrunerSimpleDateFormat() { setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, dateTimeFormatSpec.fromFormatToMillis("20200401"), dateTimeFormatSpec.fromFormatToMillis("20200430"), TimeUnit.MILLISECONDS); - segmentPruner.init(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), Collections.singleton(segment0)); assertEquals(segmentPruner.prune(brokerRequest2, onlineSegments), new HashSet<>(Arrays.asList(segment0, segment1))); assertEquals(segmentPruner.prune(brokerRequest3, onlineSegments), Collections.singleton(segment1)); @@ -581,7 +595,10 @@ public void testTimeSegmentPrunerSql() { TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); setSchemaDateTimeFieldSpec(RAW_TABLE_NAME, TimeUnit.DAYS); - TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); + TimeSegmentPruner segmentPruner = SegmentPrunerFactory.createTimeSegmentPruner(tableConfig, _propertyStore); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, + _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); Set onlineSegments = new HashSet<>(); String segment0 = "segment0"; onlineSegments.add(segment0); @@ -592,7 +609,7 @@ public void testTimeSegmentPrunerSql() { String segment2 = "segment2"; onlineSegments.add(segment2); setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, 50, 65, TimeUnit.DAYS); - segmentPruner.init(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), new HashSet<>(Arrays.asList(segment0, segment2))); assertEquals(segmentPruner.prune(brokerRequest2, onlineSegments), new HashSet<>(Arrays.asList(segment0, segment1))); @@ -610,7 +627,10 @@ public void testEmptySegmentPruner() { TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); // init with list of segments - EmptySegmentPruner segmentPruner = new EmptySegmentPruner(tableConfig, _propertyStore); + EmptySegmentPruner segmentPruner = new EmptySegmentPruner(tableConfig); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, + _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); Set onlineSegments = new HashSet<>(); String segment0 = "segment0"; onlineSegments.add(segment0); @@ -618,7 +638,7 @@ public void testEmptySegmentPruner() { String segment1 = "segment1"; onlineSegments.add(segment1); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment1, 0); - segmentPruner.init(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), new HashSet<>(Collections.singletonList(segment0))); assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1))), @@ -627,9 +647,11 @@ public void testEmptySegmentPruner() { new HashSet<>(Collections.singletonList(segment0))); // init with empty list of segments - segmentPruner = new EmptySegmentPruner(tableConfig, _propertyStore); + segmentPruner = new EmptySegmentPruner(tableConfig); + segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); onlineSegments.clear(); - segmentPruner.init(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, Collections.emptySet()), Collections.emptySet()); assertEquals(segmentPruner.prune(brokerRequest2, Collections.emptySet()), Collections.emptySet()); assertEquals(segmentPruner.prune(brokerRequest3, Collections.emptySet()), Collections.emptySet()); @@ -637,7 +659,7 @@ public void testEmptySegmentPruner() { // Segments without metadata (not updated yet) should not be pruned String newSegment = "newSegment"; onlineSegments.add(newSegment); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(newSegment)), Collections.singleton(newSegment)); assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(newSegment)), @@ -654,7 +676,7 @@ public void testEmptySegmentPruner() { segmentZKMetadataWithoutTotalDocsMetadata.setStatus(CommonConstants.Segment.Realtime.Status.IN_PROGRESS); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, REALTIME_TABLE_NAME, segmentZKMetadataWithoutTotalDocsMetadata); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(segmentWithoutTotalDocsMetadata)), Collections.singleton(segmentWithoutTotalDocsMetadata)); assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(segmentWithoutTotalDocsMetadata)), @@ -667,7 +689,7 @@ public void testEmptySegmentPruner() { String segmentWithNegativeTotalDocsMetadata = "segmentWithNegativeTotalDocsMetadata"; onlineSegments.add(segmentWithNegativeTotalDocsMetadata); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segmentWithNegativeTotalDocsMetadata, -1); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(segmentWithNegativeTotalDocsMetadata)), Collections.singleton(segmentWithNegativeTotalDocsMetadata)); assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(segmentWithNegativeTotalDocsMetadata)), @@ -685,7 +707,7 @@ public void testEmptySegmentPruner() { onlineSegments.add(segment2); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment2, -1); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), new HashSet<>(Arrays.asList(segment0, segment2))); assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), @@ -704,7 +726,7 @@ public void testEmptySegmentPruner() { new HashSet<>(Arrays.asList(segment0, segment2))); // Refresh the changed segment should update the segment pruner - segmentPruner.refreshSegment(segment2); + segmentZkMetadataFetcher.refreshSegment(segment2); assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), new HashSet<>(Collections.singletonList(segment0))); assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), diff --git a/pinot-clients/pinot-java-client/pom.xml b/pinot-clients/pinot-java-client/pom.xml index df7f8f3d9d1..dcb1e1b5051 100644 --- a/pinot-clients/pinot-java-client/pom.xml +++ b/pinot-clients/pinot-java-client/pom.xml @@ -74,30 +74,10 @@ org.asynchttpclient async-http-client - - - io.netty - netty - - - io.netty - netty-transport-native-kqueue - - - io.netty - netty-transport-native-unix-common - - com.101tec zkclient - - - io.netty - netty - - org.slf4j diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java index 7bc2bc9e6f7..530f59958d5 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java @@ -24,6 +24,7 @@ import io.netty.handler.ssl.JdkSslContext; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -34,6 +35,7 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; +import org.apache.pinot.client.utils.BrokerSelectorUtils; import org.apache.pinot.client.utils.ConnectionUtils; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.JsonUtils; @@ -188,9 +190,17 @@ protected void updateBrokerData() _brokerData = getBrokerData(responses); } - public String getBroker(String tableName) { - List brokers = - (tableName == null) ? _brokerData.getBrokers() : _brokerData.getTableToBrokerMap().get(tableName); + public String getBroker(String... tableNames) { + List brokers = null; + if (tableNames != null) { + // returning list of common brokers hosting all the tables. + brokers = BrokerSelectorUtils.getTablesCommonBrokers(Arrays.asList(tableNames), + _brokerData.getTableToBrokerMap()); + } + + if (brokers == null || brokers.isEmpty()) { + brokers = _brokerData.getBrokers(); + } return brokers.get(_random.nextInt(brokers.size())); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java index 54d88ba8cd0..3c4080bac20 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java @@ -65,7 +65,7 @@ public void run() { } } - public String getBroker(String tableName) { + public String getBroker(String... tableName) { return _brokerCache.getBroker(tableName); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java index 9422061395c..c8cd0b8ce39 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java @@ -24,7 +24,8 @@ /** * Reimplementation of BrokerResponse from pinot-common, so that pinot-api does not depend on pinot-common. */ -class BrokerResponse { +public class BrokerResponse { + private String _requestId; private JsonNode _aggregationResults; private JsonNode _selectionResults; private JsonNode _resultTable; @@ -35,6 +36,7 @@ private BrokerResponse() { } private BrokerResponse(JsonNode brokerResponse) { + _requestId = brokerResponse.get("requestId") != null ? brokerResponse.get("requestId").asText() : "unknown"; _aggregationResults = brokerResponse.get("aggregationResults"); _exceptions = brokerResponse.get("exceptions"); _selectionResults = brokerResponse.get("selectionResults"); @@ -81,4 +83,8 @@ static BrokerResponse fromJson(JsonNode json) { static BrokerResponse empty() { return new BrokerResponse(); } + + public String getRequestId() { + return _requestId; + } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java index 7da5948ea0b..6b43da27cd6 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java @@ -23,10 +23,10 @@ public interface BrokerSelector { /** * Returns the broker address in the form host:port - * @param table + * @param tableNames * @return */ - String selectBroker(String table); + String selectBroker(String... tableNames); /** * Returns list of all brokers. diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java index c236446d3d3..f43d4659158 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java @@ -20,12 +20,11 @@ import java.util.List; import java.util.Properties; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; -import org.apache.pinot.sql.parsers.CalciteSqlCompiler; +import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.apache.pinot.sql.parsers.SqlNodeAndOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,24 +36,24 @@ public class Connection { public static final String FAIL_ON_EXCEPTIONS = "failOnExceptions"; private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); - private final PinotClientTransport _transport; + private final PinotClientTransport _transport; private final BrokerSelector _brokerSelector; private final boolean _failOnExceptions; - Connection(List brokerList, PinotClientTransport transport) { + Connection(List brokerList, PinotClientTransport transport) { this(new Properties(), new SimpleBrokerSelector(brokerList), transport); } - Connection(Properties properties, List brokerList, PinotClientTransport transport) { + Connection(Properties properties, List brokerList, PinotClientTransport transport) { this(properties, new SimpleBrokerSelector(brokerList), transport); LOGGER.info("Created connection to broker list {}", brokerList); } - Connection(BrokerSelector brokerSelector, PinotClientTransport transport) { + Connection(BrokerSelector brokerSelector, PinotClientTransport transport) { this(new Properties(), brokerSelector, transport); } - Connection(Properties properties, BrokerSelector brokerSelector, PinotClientTransport transport) { + Connection(Properties properties, BrokerSelector brokerSelector, PinotClientTransport transport) { _brokerSelector = brokerSelector; _transport = transport; @@ -116,8 +115,8 @@ public ResultSetGroup execute(Request request) */ public ResultSetGroup execute(@Nullable String tableName, String query) throws PinotClientException { - tableName = tableName == null ? resolveTableName(query) : tableName; - String brokerHostPort = _brokerSelector.selectBroker(tableName); + String[] tableNames = (tableName == null) ? resolveTableName(query) : new String[]{tableName}; + String brokerHostPort = _brokerSelector.selectBroker(tableNames); if (brokerHostPort == null) { throw new PinotClientException("Could not find broker to query for table: " + tableName); } @@ -148,7 +147,7 @@ public ResultSetGroup execute(@Nullable String tableName, Request request) * @return A future containing the result of the query * @throws PinotClientException If an exception occurs while processing the query */ - public Future executeAsync(String query) + public CompletableFuture executeAsync(String query) throws PinotClientException { return executeAsync(null, query); } @@ -161,7 +160,7 @@ public Future executeAsync(String query) * @throws PinotClientException If an exception occurs while processing the query */ @Deprecated - public Future executeAsync(Request request) + public CompletableFuture executeAsync(Request request) throws PinotClientException { return executeAsync(null, request.getQuery()); } @@ -173,24 +172,31 @@ public Future executeAsync(Request request) * @return A future containing the result of the query * @throws PinotClientException If an exception occurs while processing the query */ - public Future executeAsync(@Nullable String tableName, String query) + public CompletableFuture executeAsync(@Nullable String tableName, String query) throws PinotClientException { - tableName = tableName == null ? resolveTableName(query) : tableName; - String brokerHostPort = _brokerSelector.selectBroker(tableName); + String[] tableNames = (tableName == null) ? resolveTableName(query) : new String[]{tableName}; + String brokerHostPort = _brokerSelector.selectBroker(tableNames); if (brokerHostPort == null) { throw new PinotClientException("Could not find broker to query for statement: " + query); } - return new ResultSetGroupFuture(_transport.executeQueryAsync(brokerHostPort, query)); + return _transport.executeQueryAsync(brokerHostPort, query).thenApply(ResultSetGroup::new); } + /** + * Returns the name of all the tables used in a sql query. + * + * @return name of all the tables used in a sql query. + */ @Nullable - private static String resolveTableName(String query) { + private static String[] resolveTableName(String query) { try { - return CalciteSqlCompiler.compileToBrokerRequest(query).querySource.tableName; + SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); + return RequestUtils.getTableNames(CalciteSqlParser.compileSqlNodeToPinotQuery(sqlNodeAndOptions.getSqlNode())) + .toArray(new String[0]); } catch (Exception e) { - LOGGER.error("Cannot parse table name from query: {}", query, e); - return null; + LOGGER.error("Cannot parse table name from query: {}. Fallback to broker selector default.", query, e); } + return null; } /** @@ -213,43 +219,13 @@ public void close() _brokerSelector.close(); } - private static class ResultSetGroupFuture implements Future { - private final Future _responseFuture; - - public ResultSetGroupFuture(Future responseFuture) { - _responseFuture = responseFuture; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return _responseFuture.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return _responseFuture.isCancelled(); - } - - @Override - public boolean isDone() { - return _responseFuture.isDone(); - } - - @Override - public ResultSetGroup get() - throws InterruptedException, ExecutionException { - try { - return get(60000L, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - throw new ExecutionException(e); - } - } - - @Override - public ResultSetGroup get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - BrokerResponse response = _responseFuture.get(timeout, unit); - return new ResultSetGroup(response); - } + /** + * Provides access to the underlying transport mechanism for this connection. + * There may be client metrics useful for monitoring and other observability goals. + * + * @return pinot client transport. + */ + public PinotClientTransport getTransport() { + return _transport; } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java index dbb798fbd45..1ed65dd2ebc 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java @@ -28,7 +28,7 @@ * Creates connections to Pinot, given various initialization methods. */ public class ConnectionFactory { - private static PinotClientTransport _defaultTransport; + private static volatile PinotClientTransport _defaultTransport; private ConnectionFactory() { } @@ -126,6 +126,23 @@ public static Connection fromController(Properties properties, String controller throw new PinotClientException(e); } } + + /** + * @param properties + * @param controllerUrl url host:port of the controller + * @param transport pinot transport + * @return A connection that connects to brokers as per the given controller + */ + public static Connection fromController(Properties properties, String controllerUrl, PinotClientTransport transport) { + try { + return new Connection(properties, + new ControllerBasedBrokerSelector(properties, controllerUrl), + transport); + } catch (Exception e) { + throw new PinotClientException(e); + } + } + /** * Creates a connection to a Pinot cluster, given its Zookeeper URL * diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java index d49ed48dec0..cdc2190454e 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java @@ -55,8 +55,8 @@ public ControllerBasedBrokerSelector(Properties properties, String controllerUrl @Override - public String selectBroker(String table) { - return _brokerCache.getBroker(table); + public String selectBroker(String... tableNames) { + return _brokerCache.getBroker(tableNames); } @Override diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java index 7c7fcfc8b63..729372ee330 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java @@ -21,6 +21,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -32,6 +33,7 @@ import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer; +import org.apache.pinot.client.utils.BrokerSelectorUtils; /** @@ -86,24 +88,16 @@ private void refresh() { @Nullable @Override - public String selectBroker(String table) { - if (table != null) { - String tableName = - table.replace(ExternalViewReader.OFFLINE_SUFFIX, "").replace(ExternalViewReader.REALTIME_SUFFIX, ""); - List list = _tableToBrokerListMapRef.get().get(tableName); - if (list != null && !list.isEmpty()) { - return list.get(RANDOM.nextInt(list.size())); - } - // In case tableName is formatted as . - int idx = tableName.indexOf('.'); - if (idx > 0) { - tableName = tableName.substring(idx + 1); - } - list = _tableToBrokerListMapRef.get().get(tableName); + public String selectBroker(String... tableNames) { + if (tableNames != null) { + // getting list of brokers hosting all the tables. + List list = BrokerSelectorUtils.getTablesCommonBrokers(Arrays.asList(tableNames), + _tableToBrokerListMapRef.get()); if (list != null && !list.isEmpty()) { return list.get(RANDOM.nextInt(list.size())); } } + // Return a broker randomly if table is null or no broker is found for the specified table. List list = _allBrokerListRef.get(); if (list != null && !list.isEmpty()) { diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java index cd0e49139ad..c87ae3fd80f 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.client; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -28,8 +29,8 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.net.ssl.SSLContext; @@ -39,9 +40,9 @@ import org.apache.pinot.spi.utils.JsonUtils; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ClientStats; import org.asynchttpclient.DefaultAsyncHttpClientConfig.Builder; import org.asynchttpclient.Dsl; -import org.asynchttpclient.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +50,7 @@ /** * JSON encoded Pinot client transport over AsyncHttpClient. */ -public class JsonAsyncHttpPinotClientTransport implements PinotClientTransport { +public class JsonAsyncHttpPinotClientTransport implements PinotClientTransport { private static final Logger LOGGER = LoggerFactory.getLogger(JsonAsyncHttpPinotClientTransport.class); private static final ObjectReader OBJECT_READER = JsonUtils.DEFAULT_READER; private static final String DEFAULT_EXTRA_QUERY_OPTION_STRING = "groupByMode=sql;responseFormat=sql"; @@ -66,7 +67,7 @@ public JsonAsyncHttpPinotClientTransport() { _headers = new HashMap<>(); _scheme = CommonConstants.HTTP_PROTOCOL; _extraOptionStr = DEFAULT_EXTRA_QUERY_OPTION_STRING; - _httpClient = Dsl.asyncHttpClient(); + _httpClient = Dsl.asyncHttpClient(Dsl.config().setRequestTimeout(_brokerReadTimeout)); } public JsonAsyncHttpPinotClientTransport(Map headers, String scheme, String extraOptionString, @@ -82,7 +83,8 @@ public JsonAsyncHttpPinotClientTransport(Map headers, String sch builder.setSslContext(new JdkSslContext(sslContext, true, ClientAuth.OPTIONAL)); } - builder.setReadTimeout(connectionTimeouts.getReadTimeoutMs()) + builder.setRequestTimeout(_brokerReadTimeout) + .setReadTimeout(connectionTimeouts.getReadTimeoutMs()) .setConnectTimeout(connectionTimeouts.getConnectTimeoutMs()) .setHandshakeTimeout(connectionTimeouts.getHandshakeTimeoutMs()) .setUserAgent(ConnectionUtils.getUserAgentVersionFromClassPath("ua", appId)) @@ -103,7 +105,8 @@ public JsonAsyncHttpPinotClientTransport(Map headers, String sch builder.setSslContext(sslContext); } - builder.setReadTimeout(connectionTimeouts.getReadTimeoutMs()) + builder.setRequestTimeout(_brokerReadTimeout) + .setReadTimeout(connectionTimeouts.getReadTimeoutMs()) .setConnectTimeout(connectionTimeouts.getConnectTimeoutMs()) .setHandshakeTimeout(connectionTimeouts.getHandshakeTimeoutMs()) .setUserAgent(ConnectionUtils.getUserAgentVersionFromClassPath("ua", appId)) @@ -122,7 +125,7 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) { + public CompletableFuture executeQueryAsync(String brokerAddress, String query) { try { ObjectNode json = JsonNodeFactory.instance.objectNode(); json.put("sql", query); @@ -134,11 +137,23 @@ public Future executeQueryAsync(String brokerAddress, String que if (_headers != null) { _headers.forEach((k, v) -> requestBuilder.addHeader(k, v)); } + LOGGER.debug("Sending query {} to {}", query, url); + return requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8").setBody(json.toString()) + .execute().toCompletableFuture().thenApply(httpResponse -> { + LOGGER.debug("Completed query, HTTP status is {}", httpResponse.getStatusCode()); + + if (httpResponse.getStatusCode() != 200) { + throw new PinotClientException( + "Pinot returned HTTP status " + httpResponse.getStatusCode() + ", expected 200"); + } - Future response = - requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8").setBody(json.toString()) - .execute(); - return new BrokerResponseFuture(response, query, url, _brokerReadTimeout); + String responseBody = httpResponse.getResponseBody(StandardCharsets.UTF_8); + try { + return BrokerResponse.fromJson(OBJECT_READER.readTree(responseBody)); + } catch (JsonProcessingException e) { + throw new CompletionException(e); + } + }); } catch (Exception e) { throw new PinotClientException(e); } @@ -155,7 +170,7 @@ public BrokerResponse executeQuery(String brokerAddress, Request request) } @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, Request request) throws PinotClientException { return executeQueryAsync(brokerAddress, request.getQuery()); } @@ -173,60 +188,8 @@ public void close() } } - private static class BrokerResponseFuture implements Future { - private final Future _response; - private final String _query; - private final String _url; - private final long _brokerReadTimeout; - - public BrokerResponseFuture(Future response, String query, String url, long brokerReadTimeout) { - _response = response; - _query = query; - _url = url; - _brokerReadTimeout = brokerReadTimeout; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return _response.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return _response.isCancelled(); - } - - @Override - public boolean isDone() { - return _response.isDone(); - } - - @Override - public BrokerResponse get() - throws ExecutionException { - return get(_brokerReadTimeout, TimeUnit.MILLISECONDS); - } - - @Override - public BrokerResponse get(long timeout, TimeUnit unit) - throws ExecutionException { - try { - LOGGER.debug("Sending query {} to {}", _query, _url); - - Response httpResponse = _response.get(timeout, unit); - - LOGGER.debug("Completed query, HTTP status is {}", httpResponse.getStatusCode()); - - if (httpResponse.getStatusCode() != 200) { - throw new PinotClientException( - "Pinot returned HTTP status " + httpResponse.getStatusCode() + ", expected 200"); - } - - String responseBody = httpResponse.getResponseBody(StandardCharsets.UTF_8); - return BrokerResponse.fromJson(OBJECT_READER.readTree(responseBody)); - } catch (Exception e) { - throw new ExecutionException(e); - } - } + @Override + public ClientStats getClientMetrics() { + return _httpClient.getClientStats(); } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java index fbd398906ad..9e4d2a6656c 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java @@ -18,18 +18,18 @@ */ package org.apache.pinot.client; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; /** * Interface for plugging different client transports. */ -public interface PinotClientTransport { +public interface PinotClientTransport { BrokerResponse executeQuery(String brokerAddress, String query) throws PinotClientException; - Future executeQueryAsync(String brokerAddress, String query) + CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException; @Deprecated @@ -37,9 +37,19 @@ BrokerResponse executeQuery(String brokerAddress, Request request) throws PinotClientException; @Deprecated - Future executeQueryAsync(String brokerAddress, Request request) + CompletableFuture executeQueryAsync(String brokerAddress, Request request) throws PinotClientException; void close() throws PinotClientException; + + /** + * Access to the client metrics implementation if any. + * This may be useful for observability into the client implementation. + * + * @return underlying client metrics if any + */ + default METRICS getClientMetrics() { + throw new UnsupportedOperationException("No useful client metrics available"); + } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java index 314c94989ba..600627f5fc4 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.client; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; /** @@ -72,7 +72,7 @@ public ResultSetGroup execute() { * * @return The query results */ - public Future executeAsync() { + public CompletableFuture executeAsync() { return _connection.executeAsync(fillStatementWithParameters()); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java index 51f55cfd1fe..547ba94ed31 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java @@ -37,7 +37,7 @@ public SimpleBrokerSelector(List brokerList) { } @Override - public String selectBroker(String table) { + public String selectBroker(String... tableNames) { return _brokerList.get(_random.nextInt(_brokerList.size())); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java index 1e19dff7891..b3db7fff4d6 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java @@ -38,7 +38,7 @@ void init() * @param tableName * @return Broker address corresponding to the table */ - String getBroker(String tableName); + String getBroker(String... tableName); /** * Returns all the brokers currently in the cache diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/BrokerSelectorUtils.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/BrokerSelectorUtils.java new file mode 100644 index 00000000000..1ca15255cdc --- /dev/null +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/BrokerSelectorUtils.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.client.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.pinot.client.ExternalViewReader; + + +public class BrokerSelectorUtils { + + private BrokerSelectorUtils() { + } + + /** + * + * @param tableNames: List of table names. + * @param brokerData: map holding data for table hosting on brokers. + * @return list of common brokers hosting all the tables. + */ + public static List getTablesCommonBrokers(List tableNames, Map> brokerData) { + List> tablesBrokersList = new ArrayList<>(); + for (String name: tableNames) { + String tableName = getTableNameWithoutSuffix(name); + int idx = tableName.indexOf('.'); + + if (brokerData.containsKey(tableName)) { + tablesBrokersList.add(brokerData.get(tableName)); + } else if (idx > 0) { + // In case tableName is formatted as .
+ tableName = tableName.substring(idx + 1); + tablesBrokersList.add(brokerData.get(tableName)); + } + } + + // return null if tablesBrokersList is empty or contains null + if (tablesBrokersList.isEmpty() + || tablesBrokersList.stream().anyMatch(Objects::isNull)) { + return null; + } + + List commonBrokers = tablesBrokersList.get(0); + for (int i = 1; i < tablesBrokersList.size(); i++) { + commonBrokers.retainAll(tablesBrokersList.get(i)); + } + return commonBrokers; + } + + private static String getTableNameWithoutSuffix(String tableName) { + return + tableName.replace(ExternalViewReader.OFFLINE_SUFFIX, ""). + replace(ExternalViewReader.REALTIME_SUFFIX, ""); + } +} diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/BrokerResponseTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/BrokerResponseTest.java new file mode 100644 index 00000000000..219c7a09480 --- /dev/null +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/BrokerResponseTest.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectReader; +import org.apache.pinot.spi.utils.JsonUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class BrokerResponseTest { + private static final ObjectReader OBJECT_READER = JsonUtils.DEFAULT_READER; + + @Test + public void parseResultWithRequestId() + throws JsonProcessingException { + String responseJson = "{\"requestId\":\"1\",\"traceInfo\":{},\"numDocsScanned\":36542," + + "\"aggregationResults\":[{\"function\":\"count_star\",\"value\":\"36542\"}],\"timeUsedMs\":30," + + "\"segmentStatistics\":[],\"exceptions\":[],\"totalDocs\":115545}"; + BrokerResponse brokerResponse = BrokerResponse.fromJson(OBJECT_READER.readTree(responseJson)); + Assert.assertEquals("1", brokerResponse.getRequestId()); + Assert.assertTrue(!brokerResponse.hasExceptions()); + } + + @Test + public void parseResultWithoutRequestId() + throws JsonProcessingException { + String responseJson = "{\"traceInfo\":{},\"numDocsScanned\":36542," + + "\"aggregationResults\":[{\"function\":\"count_star\",\"value\":\"36542\"}],\"timeUsedMs\":30," + + "\"segmentStatistics\":[],\"exceptions\":[],\"totalDocs\":115545}"; + BrokerResponse brokerResponse = BrokerResponse.fromJson(OBJECT_READER.readTree(responseJson)); + Assert.assertEquals("unknown", brokerResponse.getRequestId()); + Assert.assertTrue(!brokerResponse.hasExceptions()); + } +} diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java index 037e8bc9af1..5a755afc558 100644 --- a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java @@ -101,6 +101,19 @@ public void testBrokerListWithHeaders() { Assert.assertEquals(connection.getBrokerList(), brokers); } + @Test + public void testConnectionTransport() { + // Create properties + Properties properties = new Properties(); + properties.setProperty("brokerList", "127.0.0.1:1234,localhost:2345"); + + // Create the connection + Connection connection = ConnectionFactory.fromProperties(properties); + + Assert.assertNotNull(connection.getTransport()); + Assert.assertNotNull(connection.getTransport().getClientMetrics()); + } + // For testing DynamicBrokerSelector /** diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java index 404b1b279ab..0da2a2f2605 100644 --- a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java @@ -19,7 +19,7 @@ package org.apache.pinot.client; import java.util.Collections; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; @@ -36,7 +36,8 @@ public class PreparedStatementTest { public void testPreparedStatementWithDynamicBroker() { // Create a connection with dynamic broker selector. BrokerSelector mockBrokerSelector = Mockito.mock(BrokerSelector.class); - Mockito.when(mockBrokerSelector.selectBroker(Mockito.anyString())).thenAnswer(i -> i.getArgument(0)); + Mockito.when(mockBrokerSelector.selectBroker(Mockito.anyString())) + .thenAnswer(i -> i.getArgument(0)); Connection connection = new Connection(mockBrokerSelector, _dummyPinotClientTransport); PreparedStatement preparedStatement = connection.prepareStatement("SELECT foo FROM bar WHERE baz = ?"); @@ -78,27 +79,21 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { - _lastBrokerAddress = brokerAddress; - _lastQuery = query; - return null; + return CompletableFuture.completedFuture(executeQuery(brokerAddress, query)); } @Override public BrokerResponse executeQuery(String brokerAddress, Request request) throws PinotClientException { - _lastBrokerAddress = brokerAddress; - _lastQuery = request.getQuery(); - return BrokerResponse.empty(); + return executeQuery(brokerAddress, request.getQuery()); } @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, Request request) throws PinotClientException { - _lastBrokerAddress = brokerAddress; - _lastQuery = request.getQuery(); - return null; + return executeQueryAsync(brokerAddress, request.getQuery()); } public String getLastQuery() { diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java index c5135876962..91590a413ca 100644 --- a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java @@ -21,7 +21,7 @@ import java.io.InputStream; import java.util.Collections; import java.util.Properties; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import org.apache.pinot.spi.utils.JsonUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -165,7 +165,7 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { return null; } @@ -177,7 +177,7 @@ public BrokerResponse executeQuery(String brokerAddress, Request request) } @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, Request request) throws PinotClientException { return null; } diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json index ff9a775e749..511b7040497 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json @@ -1,4 +1,5 @@ { + "requestId": "1", "traceInfo": {}, "numDocsScanned": 36542, "aggregationResults": [ diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json index bfe0b26c259..b589cef4c01 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json @@ -1,4 +1,5 @@ { + "requestId": "1", "traceInfo": {}, "numDocsScanned": 22598, "aggregationResults": [ diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json index bc7c99ccb53..7a4e7e4fd5a 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json @@ -1,4 +1,5 @@ { + "requestId": "1", "traceInfo": {}, "numDocsScanned": 0, "aggregationResults": [], diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json index e3068dc263c..7825e876209 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json @@ -1,4 +1,5 @@ { + "requestId": "1", "selectionResults": { "columns": [ "ActualElapsedTime", diff --git a/pinot-clients/pinot-jdbc-client/pom.xml b/pinot-clients/pinot-jdbc-client/pom.xml index d4b2dd34d2b..e0c5e94be39 100644 --- a/pinot-clients/pinot-jdbc-client/pom.xml +++ b/pinot-clients/pinot-jdbc-client/pom.xml @@ -86,26 +86,10 @@ org.asynchttpclient async-http-client - - - io.netty - netty - - - io.netty - netty-transport-native-unix-common - - com.101tec zkclient - - - io.netty - netty - - org.slf4j diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java index 68471226054..9e703b5f3e9 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java @@ -24,12 +24,15 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import org.apache.pinot.client.base.AbstractBaseConnection; import org.apache.pinot.client.controller.PinotControllerTransport; import org.apache.pinot.client.controller.PinotControllerTransportFactory; import org.apache.pinot.client.controller.response.ControllerTenantBrokerResponse; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,10 +40,16 @@ public class PinotConnection extends AbstractBaseConnection { private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); + protected static final String[] POSSIBLE_QUERY_OPTIONS = { + QueryOptionKey.ENABLE_NULL_HANDLING, + QueryOptionKey.USE_MULTISTAGE_ENGINE + }; private org.apache.pinot.client.Connection _session; private boolean _closed; private String _controllerURL; private PinotControllerTransport _controllerTransport; + private final Map _queryOptions = new HashMap(); + public static final String BROKER_LIST = "brokers"; PinotConnection(String controllerURL, PinotClientTransport transport, String tenant, @@ -64,12 +73,52 @@ public class PinotConnection extends AbstractBaseConnection { brokers = getBrokerList(controllerURL, tenant); } _session = new org.apache.pinot.client.Connection(properties, brokers, transport); + + for (String possibleQueryOption: POSSIBLE_QUERY_OPTIONS) { + Object property = properties.getProperty(possibleQueryOption); + if (property != null) { + _queryOptions.put(possibleQueryOption, parseOptionValue(property)); + } + } + } + + private Object parseOptionValue(Object value) { + if (value instanceof String) { + String str = (String) value; + + try { + Long numVal = Long.valueOf(str); + if (numVal != null) { + return numVal; + } + } catch (NumberFormatException e) { + } + + try { + Double numVal = Double.valueOf(str); + if (numVal != null) { + return numVal; + } + } catch (NumberFormatException e) { + } + + Boolean boolVal = Boolean.valueOf(str.toLowerCase()); + if (boolVal != null) { + return boolVal; + } + } + + return value; } public org.apache.pinot.client.Connection getSession() { return _session; } + public Map getQueryOptions() { + return _queryOptions; + } + private List getBrokerList(String controllerURL, String tenant) { ControllerTenantBrokerResponse controllerTenantBrokerResponse = _controllerTransport.getBrokersFromController(controllerURL, tenant); diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java index e3b601859f5..8edac605d80 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java @@ -34,7 +34,7 @@ public class PinotPreparedStatement extends AbstractBasePreparedStatement { private static final String LIMIT_STATEMENT = "LIMIT"; - private Connection _connection; + private PinotConnection _connection; private org.apache.pinot.client.Connection _session; private ResultSetGroup _resultSetGroup; private PreparedStatement _preparedStatement; @@ -51,6 +51,7 @@ public PinotPreparedStatement(PinotConnection connection, String query) { if (!DriverUtils.queryContainsLimitStatement(_query)) { _query += " " + LIMIT_STATEMENT + " " + _maxRows; } + _query = DriverUtils.enableQueryOptions(_query, _connection.getQueryOptions()); _preparedStatement = new PreparedStatement(_session, _query); } @@ -167,7 +168,6 @@ public boolean execute() _resultSet.beforeFirst(); return true; } else { - _resultSet = null; return false; } } @@ -177,7 +177,7 @@ public ResultSet executeQuery(String sql) throws SQLException { validateState(); try { - _resultSetGroup = _session.execute(sql); + _resultSetGroup = _session.execute(DriverUtils.enableQueryOptions(sql, _connection.getQueryOptions())); if (_resultSetGroup.getResultSetCount() == 0) { _resultSet = PinotResultSet.empty(); return _resultSet; @@ -211,13 +211,7 @@ public ResultSet executeQuery() public boolean execute(String sql) throws SQLException { _resultSet = executeQuery(sql); - if (_resultSet.next()) { - _resultSet.beforeFirst(); - return true; - } else { - _resultSet = null; - return false; - } + return _resultSet != null; } @Override diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java index 3e900c64eab..4ceaa76094a 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java @@ -194,8 +194,7 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { try { String value = getString(columnIndex); - BigDecimal bigDecimal = new BigDecimal(value).setScale(scale); - return bigDecimal; + return value == null ? null : new BigDecimal(value).setScale(scale); } catch (Exception e) { throw new SQLException("Unable to fetch BigDecimal value", e); } @@ -205,7 +204,8 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) public boolean getBoolean(int columnIndex) throws SQLException { validateColumn(columnIndex); - return Boolean.parseBoolean(_resultSet.getString(_currentRow, columnIndex - 1)); + String value = getString(columnIndex); + return value == null ? false : Boolean.parseBoolean(value); } @Override @@ -213,7 +213,7 @@ public byte[] getBytes(int columnIndex) throws SQLException { try { String value = getString(columnIndex); - return Hex.decodeHex(value.toCharArray()); + return value == null ? null : Hex.decodeHex(value.toCharArray()); } catch (Exception e) { throw new SQLException(String.format("Unable to fetch value for column %d", columnIndex), e); } @@ -232,7 +232,7 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException { try { String value = getString(columnIndex); - return DateTimeUtils.getDateFromString(value, cal); + return value == null ? null : DateTimeUtils.getDateFromString(value, cal); } catch (Exception e) { throw new SQLException("Unable to fetch date", e); } @@ -242,32 +242,32 @@ public Date getDate(int columnIndex, Calendar cal) public double getDouble(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getDouble(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0.0 : Double.parseDouble(value); } @Override public float getFloat(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getFloat(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0.0f : Float.parseFloat(value); } @Override public int getInt(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getInt(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0 : Integer.parseInt(value); } @Override public long getLong(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getLong(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0 : Long.parseLong(value); } @Override @@ -282,7 +282,7 @@ public int getRow() public short getShort(int columnIndex) throws SQLException { Integer value = getInt(columnIndex); - return value.shortValue(); + return value == null ? null : value.shortValue(); } @Override @@ -359,7 +359,7 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException { try { String value = getString(columnIndex); - return DateTimeUtils.getTimeFromString(value, cal); + return value == null ? null : DateTimeUtils.getTimeFromString(value, cal); } catch (Exception e) { throw new SQLException("Unable to fetch date", e); } @@ -370,7 +370,7 @@ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { try { String value = getString(columnIndex); - return DateTimeUtils.getTimestampFromString(value, cal); + return value == null ? null : DateTimeUtils.getTimestampFromString(value, cal); } catch (Exception e) { throw new SQLException("Unable to fetch date", e); } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java index dcedad18a93..cdfd155d047 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java @@ -28,7 +28,7 @@ public class PinotStatement extends AbstractBaseStatement { private static final String LIMIT_STATEMENT = "LIMIT"; - private final Connection _connection; + private final PinotConnection _connection; private final org.apache.pinot.client.Connection _session; private boolean _closed; private ResultSet _resultSet; @@ -63,7 +63,8 @@ public ResultSet executeQuery(String sql) if (!DriverUtils.queryContainsLimitStatement(sql)) { sql += " " + LIMIT_STATEMENT + " " + _maxRows; } - ResultSetGroup resultSetGroup = _session.execute(sql); + String enabledSql = DriverUtils.enableQueryOptions(sql, _connection.getQueryOptions()); + ResultSetGroup resultSetGroup = _session.execute(enabledSql); if (resultSetGroup.getResultSetCount() == 0) { _resultSet = PinotResultSet.empty(); return _resultSet; @@ -79,13 +80,7 @@ public ResultSet executeQuery(String sql) public boolean execute(String sql) throws SQLException { _resultSet = executeQuery(sql); - if (_resultSet.next()) { - _resultSet.beforeFirst(); - return true; - } else { - _resultSet = null; - return false; - } + return _resultSet != null; } @Override diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java index d995342be62..75bd6acc289 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java @@ -71,7 +71,7 @@ public static SSLContext getSSLContextFromJDBCProps(Properties properties) { public static void handleAuth(Properties info, Map headers) throws SQLException { - if (info.contains(USER_PROPERTY) && !headers.containsKey(AUTH_HEADER)) { + if (info.containsKey(USER_PROPERTY) && !headers.containsKey(AUTH_HEADER)) { String username = info.getProperty(USER_PROPERTY); String password = info.getProperty(PASSWORD_PROPERTY, ""); if (StringUtils.isAnyEmpty(username, password)) { @@ -217,4 +217,38 @@ public static boolean queryContainsLimitStatement(String query) { Matcher matcher = pattern.matcher(query); return matcher.find(); } + + public static String enableQueryOptions(String sql, Map options) { + StringBuilder optionsBuilder = new StringBuilder(); + for (Map.Entry optionEntry: options.entrySet()) { + if (!sql.contains(optionEntry.getKey())) { + optionsBuilder.append(DriverUtils.createSetQueryOptionString(optionEntry.getKey(), optionEntry.getValue())); + } + } + optionsBuilder.append(sql); + return optionsBuilder.toString(); + } + + public static String createSetQueryOptionString(String optionKey, Object optionValue) { + StringBuilder optionBuilder = new StringBuilder(); + optionBuilder.append("SET ").append(optionKey); + + if (optionValue != null) { + optionBuilder.append('='); + + if (optionValue instanceof Boolean) { + optionBuilder.append(((Boolean) optionValue).booleanValue()); + } else if (optionValue instanceof Integer || optionValue instanceof Long) { + optionBuilder.append(((Number) optionValue).longValue()); + } else if (optionValue instanceof Float || optionValue instanceof Double) { + optionBuilder.append(((Number) optionValue).doubleValue()); + } else { + throw new IllegalArgumentException( + "Option Type " + optionValue.getClass().getSimpleName() + " is not supported."); + } + } + + optionBuilder.append(";\n"); + return optionBuilder.toString(); + } } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java index 86ad814ffab..40a0dbd768f 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.client; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; public class DummyPinotClientTransport implements PinotClientTransport { @@ -32,7 +32,7 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { _lastQuery = query; return null; @@ -46,7 +46,7 @@ public BrokerResponse executeQuery(String brokerAddress, Request request) } @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, Request request) throws PinotClientException { _lastQuery = request.getQuery(); return null; diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java index 0cc203a9943..2b77206c550 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java @@ -26,6 +26,8 @@ import java.util.Properties; import org.apache.commons.codec.binary.Hex; import org.apache.pinot.client.utils.DateTimeUtils; +import org.apache.pinot.client.utils.DriverUtils; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.testng.Assert; import org.testng.annotations.Test; @@ -35,6 +37,7 @@ public class PinotPreparedStatementTest { "SELECT * FROM dummy WHERE name = ? and age = ? and score = ? and ts = ? and eligible = ? and sub_score = ?"; public static final String DATE_QUERY = "SELECT * FROM dummy WHERE date = ? and updated_at = ? and created_at = ?"; public static final String SINGLE_STRING_QUERY = "SELECT * FROM dummy WHERE value = ?"; + private static final String BASIC_TEST_QUERY = "SELECT * FROM dummy"; private DummyPinotClientTransport _dummyPinotClientTransport = new DummyPinotClientTransport(); private DummyPinotControllerTransport _dummyPinotControllerTransport = DummyPinotControllerTransport.create(); @@ -120,4 +123,32 @@ public void testSetAdditionalDataTypes() Assert.assertEquals(lastExecutedQuery.substring(0, lastExecutedQuery.indexOf("LIMIT")).trim(), String.format("SELECT * FROM dummy WHERE value = '%s'", Hex.encodeHexString(value.getBytes()))); } + + @Test + public void testSetEnableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + PreparedStatement preparedStatement = pinotConnection.prepareStatement(BASIC_TEST_QUERY); + preparedStatement.executeQuery(); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetEnableNullHandling2() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + PreparedStatement preparedStatement = pinotConnection.prepareStatement(""); + preparedStatement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java index 7e92cc10400..abcddfbe9e5 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java @@ -23,7 +23,7 @@ import java.util.Calendar; import java.util.Collections; import java.util.Date; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import org.apache.commons.io.IOUtils; import org.apache.pinot.client.utils.DateTimeUtils; import org.apache.pinot.spi.utils.JsonUtils; @@ -189,7 +189,7 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { return null; } @@ -201,7 +201,7 @@ public BrokerResponse executeQuery(String brokerAddress, Request request) } @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, Request request) throws PinotClientException { return null; } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java index db4e1dfbb0d..fd47c6db900 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java @@ -20,11 +20,15 @@ import java.sql.ResultSet; import java.sql.Statement; +import java.util.Properties; +import org.apache.pinot.client.utils.DriverUtils; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.testng.Assert; import org.testng.annotations.Test; public class PinotStatementTest { + private static final String BASIC_TEST_QUERY = "SELECT * FROM dummy"; private DummyPinotClientTransport _dummyPinotClientTransport = new DummyPinotClientTransport(); private DummyPinotControllerTransport _dummyPinotControllerTransport = DummyPinotControllerTransport.create(); @@ -34,8 +38,116 @@ public void testExecuteQuery() PinotConnection connection = new PinotConnection("dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); Statement statement = new PinotStatement(connection); - ResultSet resultSet = statement.executeQuery("select * from dummy"); + ResultSet resultSet = statement.executeQuery(BASIC_TEST_QUERY); Assert.assertNotNull(resultSet); Assert.assertEquals(statement.getConnection(), connection); } + + @Test + public void testSetEnableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetDisableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "false"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, false) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testPresetEnableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + String presetSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + statement.executeQuery(presetSql); + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, presetSql.length()), presetSql); + } + + @Test + public void testSetUseMultistageEngine() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetMultipleQueryOptions() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String resultingQuery = _dummyPinotClientTransport.getLastQuery(); + Assert.assertTrue( + resultingQuery.contains(DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true))); + Assert.assertTrue( + resultingQuery.contains(DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, true))); + } + + @Test + public void testSetOptionAsInteger() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "2"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, 2) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetOptionAsFloat() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "2.5"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, 2.5) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } } diff --git a/pinot-common/pom.xml b/pinot-common/pom.xml index 4315529ef98..6123b1a2eec 100644 --- a/pinot-common/pom.xml +++ b/pinot-common/pom.xml @@ -326,10 +326,6 @@ org.apache.helix helix-core - - io.netty - netty - javax.xml.bind jaxb-api diff --git a/pinot-common/src/main/codegen/config.fmpp b/pinot-common/src/main/codegen/config.fmpp index e6955766bc7..178029a3b8a 100644 --- a/pinot-common/src/main/codegen/config.fmpp +++ b/pinot-common/src/main/codegen/config.fmpp @@ -526,6 +526,7 @@ data: { # List of extended statement syntax to add statementParserMethods: [ "SqlInsertFromFile()" + "SqlPhysicalExplain()" ] # List of custom function syntax to add diff --git a/pinot-common/src/main/codegen/includes/parserImpls.ftl b/pinot-common/src/main/codegen/includes/parserImpls.ftl index 449d8ab3b9a..79c1a30f44a 100644 --- a/pinot-common/src/main/codegen/includes/parserImpls.ftl +++ b/pinot-common/src/main/codegen/includes/parserImpls.ftl @@ -119,3 +119,34 @@ void SqlAtTimeZone(List list, ExprContext exprContext, Span s) : list.addAll(list2); } } + +SqlNode SqlPhysicalExplain() : +{ + SqlNode stmt; + SqlExplainLevel detailLevel = SqlExplainLevel.EXPPLAN_ATTRIBUTES; + SqlExplain.Depth depth = SqlExplain.Depth.PHYSICAL; + final SqlExplainFormat format; +} +{ + + [ detailLevel = ExplainDetailLevel() ] + ( + LOOKAHEAD(2) + { format = SqlExplainFormat.XML; } + | + LOOKAHEAD(2) + { format = SqlExplainFormat.JSON; } + | + { format = SqlExplainFormat.DOT; } + | + { format = SqlExplainFormat.TEXT; } + ) + stmt = SqlQueryOrDml() { + return new SqlPhysicalExplain(getPos(), + stmt, + detailLevel.symbol(SqlParserPos.ZERO), + depth.symbol(SqlParserPos.ZERO), + format.symbol(SqlParserPos.ZERO), + nDynamicParams); + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstanceAssignmentConfigUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstanceAssignmentConfigUtils.java index 6a0ae1188e3..b37429c527f 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstanceAssignmentConfigUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstanceAssignmentConfigUtils.java @@ -44,11 +44,10 @@ private InstanceAssignmentConfigUtils() { * backward-compatibility) COMPLETED server tag is overridden to be different from the CONSUMING server tag. */ public static boolean shouldRelocateCompletedSegments(TableConfig tableConfig) { - Map instanceAssignmentConfigMap = - tableConfig.getInstanceAssignmentConfigMap(); + Map instanceAssignmentConfigMap = tableConfig.getInstanceAssignmentConfigMap(); return (instanceAssignmentConfigMap != null - && instanceAssignmentConfigMap.get(InstancePartitionsType.COMPLETED) != null) || TagNameUtils - .isRelocateCompletedSegments(tableConfig.getTenantConfig()); + && instanceAssignmentConfigMap.get(InstancePartitionsType.COMPLETED.toString()) != null) + || TagNameUtils.isRelocateCompletedSegments(tableConfig.getTenantConfig()); } /** @@ -60,21 +59,20 @@ public static boolean allowInstanceAssignment(TableConfig tableConfig, return true; } TableType tableType = tableConfig.getTableType(); - Map instanceAssignmentConfigMap = - tableConfig.getInstanceAssignmentConfigMap(); + Map instanceAssignmentConfigMap = tableConfig.getInstanceAssignmentConfigMap(); switch (instancePartitionsType) { // Allow OFFLINE instance assignment if the offline table has it configured or (for backward-compatibility) is // using replica-group segment assignment case OFFLINE: return tableType == TableType.OFFLINE && ((instanceAssignmentConfigMap != null - && instanceAssignmentConfigMap.get(InstancePartitionsType.OFFLINE) != null) + && instanceAssignmentConfigMap.get(InstancePartitionsType.OFFLINE.toString()) != null) || AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY .equalsIgnoreCase(tableConfig.getValidationConfig().getSegmentAssignmentStrategy())); // Allow CONSUMING/COMPLETED instance assignment if the real-time table has it configured case CONSUMING: case COMPLETED: return tableType == TableType.REALTIME && (instanceAssignmentConfigMap != null - && instanceAssignmentConfigMap.get(instancePartitionsType) != null); + && instanceAssignmentConfigMap.get(instancePartitionsType.toString()) != null); default: throw new IllegalStateException(); } @@ -89,10 +87,10 @@ public static InstanceAssignmentConfig getInstanceAssignmentConfig(TableConfig t "Instance assignment is not allowed for the given table config"); // Use the instance assignment config from the table config if it exists - Map instanceAssignmentConfigMap = - tableConfig.getInstanceAssignmentConfigMap(); + Map instanceAssignmentConfigMap = tableConfig.getInstanceAssignmentConfigMap(); if (instanceAssignmentConfigMap != null) { - InstanceAssignmentConfig instanceAssignmentConfig = instanceAssignmentConfigMap.get(instancePartitionsType); + InstanceAssignmentConfig instanceAssignmentConfig = + instanceAssignmentConfigMap.get(instancePartitionsType.toString()); if (instanceAssignmentConfig != null) { return instanceAssignmentConfig; } @@ -116,12 +114,12 @@ public static InstanceAssignmentConfig getInstanceAssignmentConfig(TableConfig t Preconditions.checkState(numPartitions > 0, "Number of partitions for column: %s is not properly configured", partitionColumn); replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, numPartitions, - replicaGroupStrategyConfig.getNumInstancesPerPartition(), minimizeDataMovement); + replicaGroupStrategyConfig.getNumInstancesPerPartition(), minimizeDataMovement, partitionColumn); } else { // If partition column is not configured, use replicaGroupStrategyConfig.getNumInstancesPerPartition() as // number of instances per replica-group for backward-compatibility replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, - replicaGroupStrategyConfig.getNumInstancesPerPartition(), 0, 0, minimizeDataMovement); + replicaGroupStrategyConfig.getNumInstancesPerPartition(), 0, 0, minimizeDataMovement, null); } return new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitions.java b/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitions.java index a296527e845..a67bb93ce08 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitions.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitions.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.spi.utils.JsonUtils; @@ -146,4 +147,22 @@ public String toJsonString() { public String toString() { return toJsonString(); } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof InstancePartitions)) { + return false; + } + InstancePartitions other = (InstancePartitions) obj; + return Objects.equals(_instancePartitionsName, other._instancePartitionsName) + && Objects.equals(_partitionToInstancesMap, other._partitionToInstancesMap); + } + + @Override + public int hashCode() { + return Objects.hash(_instancePartitionsName, _partitionToInstancesMap); + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitionsUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitionsUtils.java index a15554f3d3c..759d387af47 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitionsUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/assignment/InstancePartitionsUtils.java @@ -46,6 +46,7 @@ private InstancePartitionsUtils() { } public static final char TYPE_SUFFIX_SEPARATOR = '_'; + public static final String TIER_SUFFIX = "__TIER__"; /** * Returns the name of the instance partitions for the given table name (with or without type suffix) and instance @@ -93,6 +94,11 @@ public static InstancePartitions fetchInstancePartitions(HelixPropertyStore propert throw new ZkException("Failed to remove instance partitions: " + instancePartitionsName); } } + + public static void removeTierInstancePartitions(HelixPropertyStore propertyStore, + String tableNameWithType) { + List instancePartitions = ZKMetadataProvider.getAllInstancePartitions(propertyStore); + instancePartitions.stream().filter(instancePartition -> instancePartition.getInstancePartitionsName() + .startsWith(TableNameBuilder.extractRawTableName(tableNameWithType) + TIER_SUFFIX)) + .forEach(instancePartition -> { + removeInstancePartitions(propertyStore, instancePartition.getInstancePartitionsName()); + }); + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java index 27f0b5f7320..f0492dbe80c 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.common.datablock; -import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.math.BigDecimal; @@ -27,6 +26,7 @@ import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; +import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.pinot.common.CustomObject; import org.apache.pinot.common.datatable.DataTableImplV3; import org.apache.pinot.common.datatable.DataTableUtils; @@ -186,15 +186,12 @@ public BaseDataBlock(ByteBuffer byteBuffer) } // Read variable size data. + _variableSizeDataBytes = new byte[variableSizeDataLength]; if (variableSizeDataLength != 0) { - _variableSizeDataBytes = new byte[variableSizeDataLength]; byteBuffer.position(variableSizeDataStart); byteBuffer.get(_variableSizeDataBytes); - _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); - } else { - _variableSizeDataBytes = null; - _variableSizeData = null; } + _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); // Read metadata. int metadataLength = byteBuffer.getInt(); @@ -390,7 +387,10 @@ public RoaringBitmap getNullRowIds(int colId) { */ protected byte[] serializeStringDictionary() throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + if (_stringDictionary.length == 0) { + return new byte[4]; + } + UnsynchronizedByteArrayOutputStream byteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(1024); DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); dataOutputStream.writeInt(_stringDictionary.length); @@ -436,7 +436,7 @@ public byte[] toBytes() throws IOException { ThreadResourceUsageProvider threadResourceUsageProvider = new ThreadResourceUsageProvider(); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + UnsynchronizedByteArrayOutputStream byteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(8192); DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); writeLeadingSections(dataOutputStream); @@ -536,7 +536,10 @@ private Map deserializeMetadata(ByteBuffer buffer) private byte[] serializeExceptions() throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + if (_errCodeToExceptionMap.isEmpty()) { + return new byte[4]; + } + UnsynchronizedByteArrayOutputStream byteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(1024); DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); dataOutputStream.writeInt(_errCodeToExceptionMap.size()); @@ -569,41 +572,13 @@ private Map deserializeExceptions(ByteBuffer buffer) public String toString() { if (_dataSchema == null) { return _metadata.toString(); + } else { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("resultSchema:").append('\n'); + stringBuilder.append(_dataSchema).append('\n'); + stringBuilder.append("numRows: ").append(_numRows).append('\n'); + stringBuilder.append("metadata: ").append(_metadata.toString()).append('\n'); + return stringBuilder.toString(); } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(_dataSchema).append('\n'); - stringBuilder.append("numRows: ").append(_numRows).append('\n'); - - DataSchema.ColumnDataType[] storedColumnDataTypes = _dataSchema.getStoredColumnDataTypes(); - _fixedSizeData.position(0); - for (int rowId = 0; rowId < _numRows; rowId++) { - for (int colId = 0; colId < _numColumns; colId++) { - switch (storedColumnDataTypes[colId]) { - case INT: - stringBuilder.append(_fixedSizeData.getInt()); - break; - case LONG: - stringBuilder.append(_fixedSizeData.getLong()); - break; - case FLOAT: - stringBuilder.append(_fixedSizeData.getFloat()); - break; - case DOUBLE: - stringBuilder.append(_fixedSizeData.getDouble()); - break; - case STRING: - stringBuilder.append(_fixedSizeData.getInt()); - break; - // Object and array. - default: - stringBuilder.append(String.format("(%s:%s)", _fixedSizeData.getInt(), _fixedSizeData.getInt())); - break; - } - stringBuilder.append("\t"); - } - stringBuilder.append("\n"); - } - return stringBuilder.toString(); } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java index cd9a729c8b5..99c3e4df46a 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java @@ -19,6 +19,7 @@ package org.apache.pinot.common.datablock; import java.io.IOException; +import java.math.BigDecimal; import java.nio.ByteBuffer; import java.sql.Timestamp; import java.util.ArrayList; @@ -49,7 +50,7 @@ public static MetadataBlock getErrorDataBlock(Exception e) { } private static String extractErrorMsg(Throwable t) { - while (t.getMessage() == null) { + while (t.getCause() != null && t.getMessage() == null) { t = t.getCause(); } return t.getMessage() + "\n" + QueryException.getTruncatedStackTrace(t); @@ -68,6 +69,11 @@ public static MetadataBlock getEndOfStreamDataBlock() { return new MetadataBlock(MetadataBlock.MetadataBlockType.EOS); } + public static MetadataBlock getEndOfStreamDataBlock(Map stats) { + // TODO: add query statistics metadata for the block. + return new MetadataBlock(MetadataBlock.MetadataBlockType.EOS, stats); + } + public static MetadataBlock getNoOpBlock() { return new MetadataBlock(MetadataBlock.MetadataBlockType.NOOP); } @@ -263,4 +269,295 @@ private static Object[] extractRowFromDataBlock(DataBlock dataBlock, int rowId, } return row; } + + /** + * Given a datablock and the column index, extracts the integer values for the column. Prefer using this function over + * extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to the + * desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return int array of values in the column + */ + public static int[] extractIntValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + int[] rows = new int[numRows]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + + switch (columnDataTypes[columnIndex]) { + case INT: + case BOOLEAN: + rows[rowId] = dataBlock.getInt(rowId, columnIndex); + break; + case LONG: + rows[rowId] = (int) dataBlock.getLong(rowId, columnIndex); + break; + case FLOAT: + rows[rowId] = (int) dataBlock.getFloat(rowId, columnIndex); + break; + case DOUBLE: + rows[rowId] = (int) dataBlock.getDouble(rowId, columnIndex); + break; + case BIG_DECIMAL: + rows[rowId] = dataBlock.getBigDecimal(rowId, columnIndex).intValue(); + break; + default: + throw new IllegalStateException( + String.format("Unsupported data type: %s for column: %s", columnDataTypes[columnIndex], columnIndex)); + } + } + return rows; + } + + /** + * Given a datablock and the column index, extracts the long values for the column. Prefer using this function over + * extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to the + * desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return long array of values in the column + */ + public static long[] extractLongValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + long[] rows = new long[numRows]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + + switch (columnDataTypes[columnIndex]) { + case INT: + case BOOLEAN: + rows[rowId] = dataBlock.getInt(rowId, columnIndex); + break; + case LONG: + rows[rowId] = dataBlock.getLong(rowId, columnIndex); + break; + case FLOAT: + rows[rowId] = (long) dataBlock.getFloat(rowId, columnIndex); + break; + case DOUBLE: + rows[rowId] = (long) dataBlock.getDouble(rowId, columnIndex); + break; + case BIG_DECIMAL: + rows[rowId] = dataBlock.getBigDecimal(rowId, columnIndex).longValue(); + break; + default: + throw new IllegalStateException( + String.format("Unsupported data type: %s for column: %s", columnDataTypes[columnIndex], columnIndex)); + } + } + return rows; + } + + /** + * Given a datablock and the column index, extracts the float values for the column. Prefer using this function over + * extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to the + * desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return float array of values in the column + */ + public static float[] extractFloatValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + float[] rows = new float[numRows]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + + switch (columnDataTypes[columnIndex]) { + case INT: + case BOOLEAN: + rows[rowId] = dataBlock.getInt(rowId, columnIndex); + break; + case LONG: + rows[rowId] = dataBlock.getLong(rowId, columnIndex); + break; + case FLOAT: + rows[rowId] = dataBlock.getFloat(rowId, columnIndex); + break; + case DOUBLE: + rows[rowId] = (float) dataBlock.getDouble(rowId, columnIndex); + break; + case BIG_DECIMAL: + rows[rowId] = dataBlock.getBigDecimal(rowId, columnIndex).floatValue(); + break; + default: + throw new IllegalStateException( + String.format("Unsupported data type: %s for column: %s", columnDataTypes[columnIndex], columnIndex)); + } + } + + return rows; + } + + /** + * Given a datablock and the column index, extracts the double values for the column. Prefer using this function over + * extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to the + * desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return double array of values in the column + */ + public static double[] extractDoubleValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + double[] rows = new double[numRows]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + switch (columnDataTypes[columnIndex]) { + case INT: + case BOOLEAN: + rows[rowId] = dataBlock.getInt(rowId, columnIndex); + break; + case LONG: + rows[rowId] = dataBlock.getLong(rowId, columnIndex); + break; + case FLOAT: + rows[rowId] = dataBlock.getFloat(rowId, columnIndex); + break; + case DOUBLE: + rows[rowId] = dataBlock.getDouble(rowId, columnIndex); + break; + case BIG_DECIMAL: + rows[rowId] = dataBlock.getBigDecimal(rowId, columnIndex).doubleValue(); + break; + default: + throw new IllegalStateException( + String.format("Unsupported data type: %s for column: %s", columnDataTypes[columnIndex], columnIndex)); + } + } + + return rows; + } + + /** + * Given a datablock and the column index, extracts the BigDecimal values for the column. Prefer using this function + * over extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to + * the desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return BigDecimal array of values in the column + */ + public static BigDecimal[] extractBigDecimalValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + BigDecimal[] rows = new BigDecimal[numRows]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + + switch (columnDataTypes[columnIndex]) { + case INT: + case BOOLEAN: + rows[rowId] = BigDecimal.valueOf(dataBlock.getInt(rowId, columnIndex)); + break; + case LONG: + rows[rowId] = BigDecimal.valueOf(dataBlock.getLong(rowId, columnIndex)); + break; + case FLOAT: + rows[rowId] = BigDecimal.valueOf(dataBlock.getFloat(rowId, columnIndex)); + break; + case DOUBLE: + rows[rowId] = BigDecimal.valueOf(dataBlock.getDouble(rowId, columnIndex)); + break; + case BIG_DECIMAL: + rows[rowId] = BigDecimal.valueOf(dataBlock.getBigDecimal(rowId, columnIndex).doubleValue()); + break; + default: + throw new IllegalStateException( + String.format("Unsupported data type: %s for column: %s", columnDataTypes[columnIndex], columnIndex)); + } + } + + return rows; + } + + /** + * Given a datablock and the column index, extracts the String values for the column. Prefer using this function over + * extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to the + * desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return String array of values in the column + */ + public static String[] extractStringValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + String[] rows = new String[numRows]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + rows[rowId] = dataBlock.getString(rowId, columnIndex); + } + + return rows; + } + + /** + * Given a datablock and the column index, extracts the byte values for the column. Prefer using this function over + * extractRowFromDatablock if the desired datatype is known to prevent autoboxing to Object and later unboxing to the + * desired type. + * This only works on ROW format. + * TODO: Add support for COLUMNAR format. + * @return byte array of values in the column + */ + public static byte[][] extractBytesValuesForColumn(DataBlock dataBlock, int columnIndex) { + DataSchema dataSchema = dataBlock.getDataSchema(); + DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + // Get null bitmap for the column. + RoaringBitmap nullBitmap = extractNullBitmaps(dataBlock)[columnIndex]; + int numRows = dataBlock.getNumberOfRows(); + + byte[][] rows = new byte[numRows][]; + for (int rowId = 0; rowId < numRows; rowId++) { + if (nullBitmap != null && nullBitmap.contains(rowId)) { + continue; + } + rows[rowId] = dataBlock.getBytes(rowId, columnIndex).getBytes(); + } + + return rows; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java index 33f7ee965b3..2e19258d6cc 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java @@ -26,6 +26,8 @@ import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; /** @@ -76,15 +78,17 @@ public enum MetadataBlockType { static class Contents { private String _type; + private Map _stats; @JsonCreator - public Contents(@JsonProperty("type") String type) { + public Contents(@JsonProperty("type") String type, @JsonProperty("stats") Map stats) { _type = type; + _stats = stats; } @JsonCreator public Contents() { - _type = null; + this(null, new HashMap<>()); } public String getType() { @@ -94,13 +98,25 @@ public String getType() { public void setType(String type) { _type = type; } + + public Map getStats() { + return _stats; + } + + public void setStats(Map stats) { + _stats = stats; + } } private final Contents _contents; public MetadataBlock(MetadataBlockType type) { - super(0, null, new String[0], new byte[]{0}, toContents(new Contents(type.name()))); - _contents = new Contents(type.name()); + this(type, new HashMap<>()); + } + + public MetadataBlock(MetadataBlockType type, Map stats) { + super(0, null, new String[0], new byte[]{0}, toContents(new Contents(type.name(), stats))); + _contents = new Contents(type.name(), stats); } private static byte[] toContents(Contents type) { @@ -114,7 +130,7 @@ private static byte[] toContents(Contents type) { public MetadataBlock(ByteBuffer byteBuffer) throws IOException { super(byteBuffer); - if (_variableSizeDataBytes != null) { + if (_variableSizeDataBytes != null && _variableSizeDataBytes.length > 0) { _contents = JSON.readValue(_variableSizeDataBytes, Contents.class); } else { _contents = new Contents(); @@ -132,6 +148,10 @@ public MetadataBlockType getType() { : MetadataBlockType.valueOf(type); } + public Map getStats() { + return _contents.getStats() != null ? _contents.getStats() : new HashMap<>(); + } + @Override public int getDataBlockVersionType() { return VERSION + (Type.METADATA.ordinal() << DataBlockUtils.VERSION_TYPE_SHIFT); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java index 9bac9706a7c..928834359f9 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java @@ -102,7 +102,7 @@ enum MetadataValueType { */ enum MetadataKey { UNKNOWN(0, "unknown", MetadataValueType.STRING), - TABLE(1, "table", MetadataValueType.STRING), // NOTE: this key is only used in PrioritySchedulerTest + TABLE(1, "table", MetadataValueType.STRING), NUM_DOCS_SCANNED(2, "numDocsScanned", MetadataValueType.LONG), NUM_ENTRIES_SCANNED_IN_FILTER(3, "numEntriesScannedInFilter", MetadataValueType.LONG), NUM_ENTRIES_SCANNED_POST_FILTER(4, "numEntriesScannedPostFilter", MetadataValueType.LONG), @@ -110,8 +110,6 @@ enum MetadataKey { NUM_SEGMENTS_PROCESSED(6, "numSegmentsProcessed", MetadataValueType.INT), NUM_SEGMENTS_MATCHED(7, "numSegmentsMatched", MetadataValueType.INT), NUM_CONSUMING_SEGMENTS_QUERIED(8, "numConsumingSegmentsQueried", MetadataValueType.INT), - NUM_CONSUMING_SEGMENTS_PROCESSED(26, "numConsumingSegmentsProcessed", MetadataValueType.INT), - NUM_CONSUMING_SEGMENTS_MATCHED(27, "numConsumingSegmentsMatched", MetadataValueType.INT), MIN_CONSUMING_FRESHNESS_TIME_MS(9, "minConsumingFreshnessTimeMs", MetadataValueType.LONG), TOTAL_DOCS(10, "totalDocs", MetadataValueType.LONG), NUM_GROUPS_LIMIT_REACHED(11, "numGroupsLimitReached", MetadataValueType.STRING), @@ -128,11 +126,19 @@ enum MetadataKey { NUM_SEGMENTS_PRUNED_BY_LIMIT(22, "numSegmentsPrunedByLimit", MetadataValueType.INT), NUM_SEGMENTS_PRUNED_BY_VALUE(23, "numSegmentsPrunedByValue", MetadataValueType.INT), EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS(24, "explainPlanNumEmptyFilterSegments", MetadataValueType.INT), - EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS(25, "explainPlanNumMatchAllFilterSegments", MetadataValueType.INT); + EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS(25, "explainPlanNumMatchAllFilterSegments", MetadataValueType.INT), + NUM_CONSUMING_SEGMENTS_PROCESSED(26, "numConsumingSegmentsProcessed", MetadataValueType.INT), + NUM_CONSUMING_SEGMENTS_MATCHED(27, "numConsumingSegmentsMatched", MetadataValueType.INT), + NUM_BLOCKS(28, "numBlocks", MetadataValueType.INT), + NUM_ROWS(29, "numRows", MetadataValueType.INT), + OPERATOR_EXECUTION_TIME_MS(30, "operatorExecutionTimeMs", MetadataValueType.LONG), + OPERATOR_ID(31, "operatorId", MetadataValueType.STRING), + OPERATOR_EXEC_START_TIME_MS(32, "operatorExecStartTimeMs", MetadataValueType.LONG), + OPERATOR_EXEC_END_TIME_MS(33, "operatorExecEndTimeMs", MetadataValueType.LONG); // We keep this constant to track the max id added so far for backward compatibility. // Increase it when adding new keys, but NEVER DECREASE IT!!! - private static final int MAX_ID = 27; + private static final int MAX_ID = 33; private static final MetadataKey[] ID_TO_ENUM_KEY_MAP = new MetadataKey[MAX_ID + 1]; private static final Map NAME_TO_ENUM_KEY_MAP = new HashMap<>(); @@ -186,6 +192,7 @@ public MetadataValueType getValueType() { int id = key.getId(); Preconditions.checkArgument(id >= 0 && id <= MAX_ID, "Invalid id: %s for MetadataKey: %s, must be in the range of [0, MAX_ID(%s)]", id, key, MAX_ID); + Preconditions.checkArgument(ID_TO_ENUM_KEY_MAP[id] == null, "Duplicate id: %s defined for MetadataKey: %s and %s", id, ID_TO_ENUM_KEY_MAP[id], key); ID_TO_ENUM_KEY_MAP[id] = key; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV3.java b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV3.java index ececf03bd00..c855185a020 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV3.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV3.java @@ -147,15 +147,12 @@ public DataTableImplV3(ByteBuffer byteBuffer) } // Read variable size data. + _variableSizeDataBytes = new byte[variableSizeDataLength]; if (variableSizeDataLength != 0) { - _variableSizeDataBytes = new byte[variableSizeDataLength]; byteBuffer.position(variableSizeDataStart); byteBuffer.get(_variableSizeDataBytes); - _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); - } else { - _variableSizeDataBytes = null; - _variableSizeData = null; } + _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); // Read metadata. int metadataLength = byteBuffer.getInt(); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV4.java b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV4.java index d4d27634f94..a4d69ee714e 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV4.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTableImplV4.java @@ -150,15 +150,12 @@ public DataTableImplV4(ByteBuffer byteBuffer) } // Read variable size data. + _variableSizeDataBytes = new byte[variableSizeDataLength]; if (variableSizeDataLength != 0) { - _variableSizeDataBytes = new byte[variableSizeDataLength]; byteBuffer.position(variableSizeDataStart); byteBuffer.get(_variableSizeDataBytes); - _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); - } else { - _variableSizeDataBytes = null; - _variableSizeData = null; } + _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); // Read metadata. int metadataLength = byteBuffer.getInt(); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java b/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java index 5b2910a3004..a6a1a2bfa4b 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java @@ -54,7 +54,7 @@ public static void setMaxLinesOfStackTrace(int maxLinesOfStackTracePerFrame) { public static final int ACCESS_DENIED_ERROR_CODE = 180; public static final int TABLE_DOES_NOT_EXIST_ERROR_CODE = 190; public static final int QUERY_EXECUTION_ERROR_CODE = 200; - public static final int QUERY_CANCELLATION_ERROR_CODE = 205; + public static final int QUERY_CANCELLATION_ERROR_CODE = 503; // TODO: Handle these errors in broker public static final int SERVER_SHUTTING_DOWN_ERROR_CODE = 210; public static final int SERVER_OUT_OF_CAPACITY_ERROR_CODE = 211; @@ -130,6 +130,8 @@ public static void setMaxLinesOfStackTrace(int maxLinesOfStackTracePerFrame) { public static final ProcessingException UNKNOWN_COLUMN_ERROR = new ProcessingException(UNKNOWN_COLUMN_ERROR_CODE); public static final ProcessingException UNKNOWN_ERROR = new ProcessingException(UNKNOWN_ERROR_CODE); public static final ProcessingException QUOTA_EXCEEDED_ERROR = new ProcessingException(TOO_MANY_REQUESTS_ERROR_CODE); + public static final ProcessingException BROKER_REQUEST_SEND_ERROR = + new ProcessingException(BROKER_REQUEST_SEND_ERROR_CODE); static { JSON_PARSING_ERROR.setMessage("JsonParsingError"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java index 98fbbf0635f..59a91ca76e1 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java @@ -32,12 +32,17 @@ */ public class FunctionInvoker { private final Method _method; + // If true, the function should return null if any of its argument is null + // Otherwise, the function should deal with null in its own implementation. + private final boolean _isNullIntolerant; + private final Class[] _parameterClasses; private final PinotDataType[] _parameterTypes; private final Object _instance; public FunctionInvoker(FunctionInfo functionInfo) { _method = functionInfo.getMethod(); + _isNullIntolerant = !functionInfo.hasNullableParameters(); Class[] parameterClasses = _method.getParameterTypes(); int numParameters = parameterClasses.length; _parameterClasses = new Class[numParameters]; @@ -123,6 +128,13 @@ public Class getResultClass() { * {@link #convertTypes(Object[])} to convert the argument types if needed before calling this method. */ public Object invoke(Object[] arguments) { + if (_isNullIntolerant) { + for (Object arg : arguments) { + if (arg == null) { + return null; + } + } + } try { return _method.invoke(_instance, arguments); } catch (Exception e) { diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java index 4a5f2bb60d8..2c2ba9a3244 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java @@ -187,7 +187,7 @@ public static Object jsonExtractScalar(String jsonFieldName, String jsonPath, St } @ScalarFunction(names = {"jsonExtractKey", "json_extract_key"}, isPlaceholder = true) - public static String jsonExtractKey(String jsonFieldName, String jsonPath) { + public static Object jsonExtractKey(String jsonFieldName, String jsonPath) { throw new UnsupportedOperationException("Placeholder scalar function, should not reach here"); } @@ -205,5 +205,16 @@ public static boolean textMatch(String text, String pattern) { public static boolean jsonMatch(String text, String pattern) { throw new UnsupportedOperationException("Placeholder scalar function, should not reach here"); } + + @ScalarFunction(names = {"clpDecode", "clp_decode"}, isPlaceholder = true) + public static Object clpDecode(String logtypeFieldName, String dictVarsFieldName, String encodedVarsFieldName) { + throw new UnsupportedOperationException("Placeholder scalar function, should not reach here"); + } + + @ScalarFunction(names = {"clpDecode", "clp_decode"}, isPlaceholder = true) + public static Object clpDecode(String logtypeFieldName, String dictVarsFieldName, String encodedVarsFieldName, + String defaultValue) { + throw new UnsupportedOperationException("Placeholder scalar function, should not reach here"); + } } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java b/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java index 6c61fc89f10..2ae2f806037 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java @@ -18,10 +18,25 @@ */ package org.apache.pinot.common.function; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.sql.SqlFunctionCategory; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlOperatorBinding; +import org.apache.calcite.sql.type.OperandTypes; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlOperandTypeChecker; +import org.apache.calcite.sql.type.SqlReturnTypeInference; +import org.apache.calcite.sql.type.SqlTypeFamily; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.type.SqlTypeTransforms; +import org.apache.pinot.spi.data.DateTimeFieldSpec; +import org.apache.pinot.spi.data.DateTimeFormatSpec; public enum TransformFunctionType { @@ -76,13 +91,34 @@ public enum TransformFunctionType { CAST("cast"), // string functions - JSONEXTRACTSCALAR("jsonExtractScalar"), - JSONEXTRACTKEY("jsonExtractKey"), + JSONEXTRACTSCALAR("jsonExtractScalar", + ReturnTypes.cascade(opBinding -> positionalReturnTypeInferenceFromStringLiteral(opBinding, 2, + SqlTypeName.VARCHAR), SqlTypeTransforms.FORCE_NULLABLE), + OperandTypes.family(ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, + SqlTypeFamily.CHARACTER), ordinal -> ordinal > 2), "json_extract_scalar"), + JSONEXTRACTKEY("jsonExtractKey", ReturnTypes.TO_ARRAY, + OperandTypes.family(ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER)), "json_extract_key"), // date time functions - TIMECONVERT("timeConvert"), - DATETIMECONVERT("dateTimeConvert"), - DATETRUNC("dateTrunc"), + TIMECONVERT("timeConvert", + ReturnTypes.BIGINT_FORCE_NULLABLE, + OperandTypes.family(ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER)), + "time_convert"), + + DATETIMECONVERT("dateTimeConvert", + ReturnTypes.cascade( + opBinding -> dateTimeConverterReturnTypeInference(opBinding), + SqlTypeTransforms.FORCE_NULLABLE), + OperandTypes.family(ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, + SqlTypeFamily.CHARACTER)), "date_time_convert"), + + DATETRUNC("dateTrunc", + ReturnTypes.BIGINT_FORCE_NULLABLE, + OperandTypes.family( + ImmutableList.of(SqlTypeFamily.CHARACTER, SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, + SqlTypeFamily.CHARACTER), + ordinal -> ordinal > 1)), + YEAR("year"), YEAR_OF_WEEK("yearOfWeek", "yow"), QUARTER("quarter"), @@ -107,15 +143,26 @@ public enum TransformFunctionType { ARRAYMAX("arrayMax"), ARRAYSUM("arraySum"), VALUEIN("valueIn"), - MAPVALUE("mapValue"), + MAPVALUE("mapValue", ReturnTypes.cascade(opBinding -> + opBinding.getOperandType(2).getComponentType(), SqlTypeTransforms.FORCE_NULLABLE), + OperandTypes.family(ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.ANY, SqlTypeFamily.ANY)), + "map_value"), // special functions INIDSET("inIdSet"), LOOKUP("lookUp"), GROOVY("groovy"), + CLPDECODE("clpDecode"), // Regexp functions - REGEXP_EXTRACT("regexpExtract"), + REGEXP_EXTRACT("regexpExtract", "regexp_extract"), + REGEXPREPLACE("regexpReplace", + ReturnTypes.VARCHAR_2000_NULLABLE, + OperandTypes.family( + ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, + SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER, SqlTypeFamily.CHARACTER), + ordinal -> ordinal > 2), + "regexp_replace"), // Special type for annotation based scalar functions SCALAR("scalar"), @@ -161,22 +208,108 @@ public enum TransformFunctionType { RADIANS("radians"); private final String _name; - private final List _aliases; + private final List _alternativeNames; + private final SqlKind _sqlKind; + private final SqlReturnTypeInference _returnTypeInference; + private final SqlOperandTypeChecker _operandTypeChecker; + private final SqlFunctionCategory _sqlFunctionCategory; + + TransformFunctionType(String name, String... alternativeNames) { + this(name, null, null, null, null, alternativeNames); + } + + TransformFunctionType(String name, SqlReturnTypeInference returnTypeInference, + SqlOperandTypeChecker operandTypeChecker, String... alternativeNames) { + this(name, SqlKind.OTHER_FUNCTION, returnTypeInference, operandTypeChecker, + SqlFunctionCategory.USER_DEFINED_FUNCTION, alternativeNames); + } - TransformFunctionType(String name, String... aliases) { + /** + * Constructor to use for transform functions which are supported in both v1 and multistage engines + */ + TransformFunctionType(String name, SqlKind sqlKind, SqlReturnTypeInference returnTypeInference, + SqlOperandTypeChecker operandTypeChecker, SqlFunctionCategory sqlFunctionCategory, String... alternativeNames) { _name = name; - List all = new ArrayList<>(aliases.length + 2); + List all = new ArrayList<>(alternativeNames.length + 2); all.add(name); all.add(name()); - all.addAll(Arrays.asList(aliases)); - _aliases = Collections.unmodifiableList(all); + all.addAll(Arrays.asList(alternativeNames)); + _alternativeNames = Collections.unmodifiableList(all); + _sqlKind = sqlKind; + _returnTypeInference = returnTypeInference; + _operandTypeChecker = operandTypeChecker; + _sqlFunctionCategory = sqlFunctionCategory; } public String getName() { return _name; } - public List getAliases() { - return _aliases; + public List getAlternativeNames() { + return _alternativeNames; + } + + public SqlKind getSqlKind() { + return _sqlKind; + } + + public SqlReturnTypeInference getReturnTypeInference() { + return _returnTypeInference; + } + + public SqlOperandTypeChecker getOperandTypeChecker() { + return _operandTypeChecker; + } + + public SqlFunctionCategory getSqlFunctionCategory() { + return _sqlFunctionCategory; + } + + /** Returns the optional explicit returning type specification. */ + private static RelDataType positionalReturnTypeInferenceFromStringLiteral(SqlOperatorBinding opBinding, int pos) { + return positionalReturnTypeInferenceFromStringLiteral(opBinding, pos, SqlTypeName.ANY); + } + + private static RelDataType positionalReturnTypeInferenceFromStringLiteral(SqlOperatorBinding opBinding, int pos, + SqlTypeName defaultSqlType) { + if (opBinding.getOperandCount() > pos + && opBinding.isOperandLiteral(pos, false)) { + String operandType = opBinding.getOperandLiteralValue(pos, String.class).toUpperCase(); + return inferTypeFromStringLiteral(operandType, opBinding.getTypeFactory()); + } + return opBinding.getTypeFactory().createSqlType(defaultSqlType); + } + + private static RelDataType dateTimeConverterReturnTypeInference(SqlOperatorBinding opBinding) { + int outputFormatPos = 2; + if (opBinding.getOperandCount() > outputFormatPos + && opBinding.isOperandLiteral(outputFormatPos, false)) { + String outputFormatStr = opBinding.getOperandLiteralValue(outputFormatPos, String.class).toUpperCase(); + DateTimeFormatSpec dateTimeFormatSpec = new DateTimeFormatSpec(outputFormatStr); + if ((dateTimeFormatSpec.getTimeFormat() == DateTimeFieldSpec.TimeFormat.EPOCH) || ( + dateTimeFormatSpec.getTimeFormat() == DateTimeFieldSpec.TimeFormat.TIMESTAMP)) { + return opBinding.getTypeFactory().createSqlType(SqlTypeName.BIGINT); + } + } + return opBinding.getTypeFactory().createSqlType(SqlTypeName.VARCHAR); + } + + private static RelDataType inferTypeFromStringLiteral(String operandTypeStr, RelDataTypeFactory typeFactory) { + switch (operandTypeStr) { + case "INT": + return typeFactory.createSqlType(SqlTypeName.INTEGER); + case "LONG": + return typeFactory.createSqlType(SqlTypeName.BIGINT); + case "STRING": + return typeFactory.createSqlType(SqlTypeName.VARCHAR); + case "BYTES": + return typeFactory.createSqlType(SqlTypeName.VARBINARY); + default: + SqlTypeName sqlTypeName = SqlTypeName.get(operandTypeStr); + if (sqlTypeName == null) { + throw new IllegalArgumentException("Invalid type: " + operandTypeStr); + } + return typeFactory.createSqlType(sqlTypeName); + } } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java index cbd3d2e39ce..77db5d7fcdf 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java @@ -79,6 +79,15 @@ public static long toEpochSeconds(long millis) { return TimeUnit.MILLISECONDS.toSeconds(millis); } + @ScalarFunction + public static long[] toEpochSecondsMV(long[] millis) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochSeconds(millis[i]); + } + return results; + } + /** * Convert epoch millis to epoch minutes */ @@ -87,6 +96,15 @@ public static long toEpochMinutes(long millis) { return TimeUnit.MILLISECONDS.toMinutes(millis); } + @ScalarFunction + public static long[] toEpochMinutesMV(long[] millis) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochMinutes(millis[i]); + } + return results; + } + /** * Convert epoch millis to epoch hours */ @@ -95,6 +113,15 @@ public static long toEpochHours(long millis) { return TimeUnit.MILLISECONDS.toHours(millis); } + @ScalarFunction + public static long[] toEpochHoursMV(long[] millis) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochHours(millis[i]); + } + return results; + } + /** * Convert epoch millis to epoch days */ @@ -103,6 +130,15 @@ public static long toEpochDays(long millis) { return TimeUnit.MILLISECONDS.toDays(millis); } + @ScalarFunction + public static long[] toEpochDaysMV(long[] millis) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochDays(millis[i]); + } + return results; + } + /** * Convert epoch millis to epoch seconds, round to nearest rounding bucket */ @@ -111,6 +147,16 @@ public static long toEpochSecondsRounded(long millis, long roundToNearest) { return (TimeUnit.MILLISECONDS.toSeconds(millis) / roundToNearest) * roundToNearest; } + @ScalarFunction + public static long[] toEpochSecondsRoundedMV(long[] millis, long roundToNearest) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochSecondsRounded(millis[i], roundToNearest); + } + return results; + } + + /** * Convert epoch millis to epoch minutes, round to nearest rounding bucket */ @@ -119,6 +165,16 @@ public static long toEpochMinutesRounded(long millis, long roundToNearest) { return (TimeUnit.MILLISECONDS.toMinutes(millis) / roundToNearest) * roundToNearest; } + @ScalarFunction + public static long[] toEpochMinutesRoundedMV(long[] millis, long roundToNearest) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochMinutesRounded(millis[i], roundToNearest); + } + return results; + } + + /** * Convert epoch millis to epoch hours, round to nearest rounding bucket */ @@ -127,6 +183,16 @@ public static long toEpochHoursRounded(long millis, long roundToNearest) { return (TimeUnit.MILLISECONDS.toHours(millis) / roundToNearest) * roundToNearest; } + @ScalarFunction + public static long[] toEpochHoursRoundedMV(long[] millis, long roundToNearest) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochHoursRounded(millis[i], roundToNearest); + } + return results; + } + + /** * Convert epoch millis to epoch days, round to nearest rounding bucket */ @@ -135,6 +201,16 @@ public static long toEpochDaysRounded(long millis, long roundToNearest) { return (TimeUnit.MILLISECONDS.toDays(millis) / roundToNearest) * roundToNearest; } + @ScalarFunction + public static long[] toEpochDaysRoundedMV(long[] millis, long roundToNearest) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochDaysRounded(millis[i], roundToNearest); + } + return results; + } + + /** * Convert epoch millis to epoch seconds, divided by given bucket, to get nSecondsSinceEpoch */ @@ -143,6 +219,16 @@ public static long toEpochSecondsBucket(long millis, long bucket) { return TimeUnit.MILLISECONDS.toSeconds(millis) / bucket; } + @ScalarFunction + public static long[] toEpochSecondsBucketMV(long[] millis, long bucket) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochSecondsBucket(millis[i], bucket); + } + return results; + } + + /** * Convert epoch millis to epoch minutes, divided by given bucket, to get nMinutesSinceEpoch */ @@ -151,6 +237,16 @@ public static long toEpochMinutesBucket(long millis, long bucket) { return TimeUnit.MILLISECONDS.toMinutes(millis) / bucket; } + @ScalarFunction + public static long[] toEpochMinutesBucketMV(long[] millis, long bucket) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochMinutesBucket(millis[i], bucket); + } + return results; + } + + /** * Convert epoch millis to epoch hours, divided by given bucket, to get nHoursSinceEpoch */ @@ -159,6 +255,16 @@ public static long toEpochHoursBucket(long millis, long bucket) { return TimeUnit.MILLISECONDS.toHours(millis) / bucket; } + @ScalarFunction + public static long[] toEpochHoursBucketMV(long[] millis, long bucket) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochHoursBucket(millis[i], bucket); + } + return results; + } + + /** * Convert epoch millis to epoch days, divided by given bucket, to get nDaysSinceEpoch */ @@ -167,6 +273,16 @@ public static long toEpochDaysBucket(long millis, long bucket) { return TimeUnit.MILLISECONDS.toDays(millis) / bucket; } + @ScalarFunction + public static long[] toEpochDaysBucketMV(long[] millis, long bucket) { + long[] results = new long[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toEpochDaysBucket(millis[i], bucket); + } + return results; + } + + /** * Converts epoch seconds to epoch millis */ @@ -175,6 +291,15 @@ public static long fromEpochSeconds(long seconds) { return TimeUnit.SECONDS.toMillis(seconds); } + @ScalarFunction + public static long[] fromEpochSecondsMV(long[] seconds) { + long[] results = new long[seconds.length]; + for (int i = 0; i < seconds.length; i++) { + results[i] = fromEpochSeconds(seconds[i]); + } + return results; + } + /** * Converts epoch minutes to epoch millis */ @@ -183,6 +308,15 @@ public static long fromEpochMinutes(long minutes) { return TimeUnit.MINUTES.toMillis(minutes); } + @ScalarFunction + public static long[] fromEpochMinutesMV(long[] minutes) { + long[] results = new long[minutes.length]; + for (int i = 0; i < minutes.length; i++) { + results[i] = fromEpochMinutes(minutes[i]); + } + return results; + } + /** * Converts epoch hours to epoch millis */ @@ -191,6 +325,15 @@ public static long fromEpochHours(long hours) { return TimeUnit.HOURS.toMillis(hours); } + @ScalarFunction + public static long[] fromEpochHoursMV(long[] hours) { + long[] results = new long[hours.length]; + for (int i = 0; i < hours.length; i++) { + results[i] = fromEpochHours(hours[i]); + } + return results; + } + /** * Converts epoch days to epoch millis */ @@ -199,6 +342,15 @@ public static long fromEpochDays(long days) { return TimeUnit.DAYS.toMillis(days); } + @ScalarFunction + public static long[] fromEpochDaysMV(long[] days) { + long[] results = new long[days.length]; + for (int i = 0; i < days.length; i++) { + results[i] = fromEpochDays(days[i]); + } + return results; + } + /** * Converts nSecondsSinceEpoch (seconds that have been divided by a bucket), to epoch millis */ @@ -207,6 +359,15 @@ public static long fromEpochSecondsBucket(long seconds, long bucket) { return TimeUnit.SECONDS.toMillis(seconds * bucket); } + @ScalarFunction + public static long[] fromEpochSecondsBucketMV(long[] seconds, long bucket) { + long[] results = new long[seconds.length]; + for (int i = 0; i < seconds.length; i++) { + results[i] = fromEpochSecondsBucket(seconds[i], bucket); + } + return results; + } + /** * Converts nMinutesSinceEpoch (minutes that have been divided by a bucket), to epoch millis */ @@ -215,6 +376,15 @@ public static long fromEpochMinutesBucket(long minutes, long bucket) { return TimeUnit.MINUTES.toMillis(minutes * bucket); } + @ScalarFunction + public static long[] fromEpochMinutesBucketMV(long[] minutes, long bucket) { + long[] results = new long[minutes.length]; + for (int i = 0; i < minutes.length; i++) { + results[i] = fromEpochMinutesBucket(minutes[i], bucket); + } + return results; + } + /** * Converts nHoursSinceEpoch (hours that have been divided by a bucket), to epoch millis */ @@ -223,6 +393,15 @@ public static long fromEpochHoursBucket(long hours, long bucket) { return TimeUnit.HOURS.toMillis(hours * bucket); } + @ScalarFunction + public static long[] fromEpochHoursBucketMV(long[] hours, long bucket) { + long[] results = new long[hours.length]; + for (int i = 0; i < hours.length; i++) { + results[i] = fromEpochHoursBucket(hours[i], bucket); + } + return results; + } + /** * Converts nDaysSinceEpoch (days that have been divided by a bucket), to epoch millis */ @@ -231,6 +410,15 @@ public static long fromEpochDaysBucket(long days, long bucket) { return TimeUnit.DAYS.toMillis(days * bucket); } + @ScalarFunction + public static long[] fromEpochDaysBucketMV(long[] days, long bucket) { + long[] results = new long[days.length]; + for (int i = 0; i < days.length; i++) { + results[i] = fromEpochDaysBucket(days[i], bucket); + } + return results; + } + /** * Converts epoch millis to Timestamp */ @@ -239,6 +427,15 @@ public static Timestamp toTimestamp(long millis) { return new Timestamp(millis); } + @ScalarFunction + public static Timestamp[] toTimestampMV(long[] millis) { + Timestamp[] results = new Timestamp[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toTimestamp(millis[i]); + } + return results; + } + /** * Converts Timestamp to epoch millis */ @@ -247,6 +444,15 @@ public static long fromTimestamp(Timestamp timestamp) { return timestamp.getTime(); } + @ScalarFunction + public static long[] fromTimestampMV(Timestamp[] timestamp) { + long[] results = new long[timestamp.length]; + for (int i = 0; i < timestamp.length; i++) { + results[i] = fromTimestamp(timestamp[i]); + } + return results; + } + /** * Converts epoch millis to DateTime string represented by pattern */ @@ -255,6 +461,15 @@ public static String toDateTime(long millis, String pattern) { return DateTimePatternHandler.parseEpochMillisToDateTimeString(millis, pattern); } + @ScalarFunction + public static String[] toDateTimeMV(long[] millis, String pattern) { + String[] results = new String[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toDateTime(millis[i], pattern); + } + return results; + } + /** * Converts epoch millis to DateTime string represented by pattern and the time zone id. */ @@ -263,6 +478,15 @@ public static String toDateTime(long millis, String pattern, String timezoneId) return DateTimePatternHandler.parseEpochMillisToDateTimeString(millis, pattern, timezoneId); } + @ScalarFunction + public static String[] toDateTimeMV(long[] millis, String pattern, String timezoneId) { + String[] results = new String[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = toDateTime(millis[i], pattern, timezoneId); + } + return results; + } + /** * Converts DateTime string represented by pattern to epoch millis */ @@ -271,6 +495,15 @@ public static long fromDateTime(String dateTimeString, String pattern) { return DateTimePatternHandler.parseDateTimeStringToEpochMillis(dateTimeString, pattern); } + @ScalarFunction + public static long[] fromDateTimeMV(String[] dateTimeString, String pattern) { + long[] results = new long[dateTimeString.length]; + for (int i = 0; i < dateTimeString.length; i++) { + results[i] = fromDateTime(dateTimeString[i], pattern); + } + return results; + } + /** * Converts DateTime string represented by pattern to epoch millis */ @@ -279,6 +512,15 @@ public static long fromDateTime(String dateTimeString, String pattern, String ti return DateTimePatternHandler.parseDateTimeStringToEpochMillis(dateTimeString, pattern, timeZoneId); } + @ScalarFunction + public static long[] fromDateTimeMV(String[] dateTimeString, String pattern, String timeZoneId) { + long[] results = new long[dateTimeString.length]; + for (int i = 0; i < dateTimeString.length; i++) { + results[i] = fromDateTime(dateTimeString[i], pattern, timeZoneId); + } + return results; + } + /** * Round the given time value to nearest multiple * @return the original value but rounded to the nearest multiple of @param roundToNearest @@ -288,6 +530,15 @@ public static long round(long timeValue, long roundToNearest) { return (timeValue / roundToNearest) * roundToNearest; } + @ScalarFunction + public static long[] roundMV(long[] timeValue, long roundToNearest) { + long[] results = new long[timeValue.length]; + for (int i = 0; i < timeValue.length; i++) { + results[i] = round(timeValue[i], roundToNearest); + } + return results; + } + /** * Return current time as epoch millis * TODO: Consider changing the return type to Timestamp @@ -315,6 +566,15 @@ public static long ago(String periodString) { return System.currentTimeMillis() - period.toMillis(); } + @ScalarFunction + public static long[] agoMV(String[] periodString) { + long[] results = new long[periodString.length]; + for (int i = 0; i < periodString.length; i++) { + results[i] = ago(periodString[i]); + } + return results; + } + /** * The {@code timezoneId} for the following methods must be of a Joda-Time format: * https://www.joda.org/joda-time/timezones.html @@ -328,6 +588,14 @@ public static int timezoneHour(String timezoneId) { return timezoneHour(timezoneId, 0); } + @ScalarFunction + public static int[] timezoneHourMV(String[] timezoneId) { + int[] results = new int[timezoneId.length]; + for (int i = 0; i < timezoneId.length; i++) { + results[i] = timezoneHour(timezoneId[i], 0); + } + return results; + } /** * Returns the hour of the time zone offset, for the UTC timestamp at {@code millis}. This will * properly handle daylight savings time. @@ -337,6 +605,15 @@ public static int timezoneHour(String timezoneId, long millis) { return (int) TimeUnit.MILLISECONDS.toHours(DateTimeZone.forID(timezoneId).getOffset(millis)); } + @ScalarFunction + public static int[] timezoneHourMV(String timezoneId, long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = timezoneHour(timezoneId, millis[i]); + } + return results; + } + /** * Returns the minute of the time zone offset. */ @@ -345,6 +622,15 @@ public static int timezoneMinute(String timezoneId) { return timezoneMinute(timezoneId, 0); } + @ScalarFunction + public static int[] timezoneMinuteMV(String[] timezoneId) { + int[] results = new int[timezoneId.length]; + for (int i = 0; i < timezoneId.length; i++) { + results[i] = timezoneMinute(timezoneId[i], 0); + } + return results; + } + /** * Returns the minute of the time zone offset, for the UTC timestamp at {@code millis}. This will * properly handle daylight savings time @@ -354,6 +640,15 @@ public static int timezoneMinute(String timezoneId, long millis) { return (int) TimeUnit.MILLISECONDS.toMinutes(DateTimeZone.forID(timezoneId).getOffset(millis)) % 60; } + @ScalarFunction + public static int[] timezoneMinuteMV(String timezoneId, long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = timezoneMinute(timezoneId, millis[i]); + } + return results; + } + /** * Returns the year from the given epoch millis in UTC timezone. */ @@ -362,6 +657,15 @@ public static int year(long millis) { return new DateTime(millis, DateTimeZone.UTC).getYear(); } + @ScalarFunction + public static int[] yearMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = year(millis[i]); + } + return results; + } + /** * Returns the year from the given epoch millis and timezone id. */ @@ -370,6 +674,15 @@ public static int year(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getYear(); } + @ScalarFunction + public static int[] yearMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = year(millis[i], timezoneId); + } + return results; + } + /** * Returns the year of the ISO week from the given epoch millis in UTC timezone. */ @@ -378,6 +691,15 @@ public static int yearOfWeek(long millis) { return new DateTime(millis, DateTimeZone.UTC).getWeekyear(); } + @ScalarFunction(names = {"yearOfWeekMV", "year_of_week_mv", "yowmv"}) + public static int[] yearOfWeekMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = yearOfWeek(millis[i]); + } + return results; + } + /** * Returns the year of the ISO week from the given epoch millis and timezone id. */ @@ -386,6 +708,15 @@ public static int yearOfWeek(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getWeekyear(); } + @ScalarFunction(names = {"yearOfWeekMV", "year_of_week_mv", "yowmv"}) + public static int[] yearOfWeekMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = yearOfWeek(millis[i], timezoneId); + } + return results; + } + /** * Returns the quarter of the year from the given epoch millis in UTC timezone. The value ranges from 1 to 4. */ @@ -394,6 +725,15 @@ public static int quarter(long millis) { return (monthOfYear(millis) - 1) / 3 + 1; } + @ScalarFunction + public static int[] quarterMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = quarter(millis[i]); + } + return results; + } + /** * Returns the quarter of the year from the given epoch millis and timezone id. The value ranges from 1 to 4. */ @@ -402,6 +742,15 @@ public static int quarter(long millis, String timezoneId) { return (monthOfYear(millis, timezoneId) - 1) / 3 + 1; } + @ScalarFunction + public static int[] quarterMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = quarter(millis[i], timezoneId); + } + return results; + } + /** * Returns the month of the year from the given epoch millis in UTC timezone. The value ranges from 1 to 12. */ @@ -410,6 +759,15 @@ public static int monthOfYear(long millis) { return new DateTime(millis, DateTimeZone.UTC).getMonthOfYear(); } + @ScalarFunction(names = {"monthMV", "month_of_year_mv", "monthOfYearMV"}) + public static int[] monthOfYearMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = monthOfYear(millis[i]); + } + return results; + } + /** * Returns the month of the year from the given epoch millis and timezone id. The value ranges from 1 to 12. */ @@ -418,6 +776,15 @@ public static int monthOfYear(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getMonthOfYear(); } + @ScalarFunction(names = {"monthMV", "month_of_year_mv", "monthOfYearMV"}) + public static int[] monthOfYearMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = monthOfYear(millis[i], timezoneId); + } + return results; + } + /** * Returns the ISO week of the year from the given epoch millis in UTC timezone.The value ranges from 1 to 53. */ @@ -426,6 +793,15 @@ public static int weekOfYear(long millis) { return new DateTime(millis, DateTimeZone.UTC).getWeekOfWeekyear(); } + @ScalarFunction(names = {"weekOfYearMV", "week_of_year_mv", "weekMV"}) + public static int[] weekOfYearMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = weekOfYear(millis[i]); + } + return results; + } + /** * Returns the ISO week of the year from the given epoch millis and timezone id. The value ranges from 1 to 53. */ @@ -434,6 +810,15 @@ public static int weekOfYear(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getWeekOfWeekyear(); } + @ScalarFunction(names = {"weekOfYearMV", "week_of_year_mv", "weekMV"}) + public static int[] weekOfYearMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = weekOfYear(millis[i], timezoneId); + } + return results; + } + /** * Returns the day of the year from the given epoch millis in UTC timezone. The value ranges from 1 to 366. */ @@ -442,6 +827,15 @@ public static int dayOfYear(long millis) { return new DateTime(millis, DateTimeZone.UTC).getDayOfYear(); } + @ScalarFunction(names = {"dayOfYearMV", "day_of_year_mv", "doyMV"}) + public static int[] dayOfYear(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = dayOfYear(millis[i]); + } + return results; + } + /** * Returns the day of the year from the given epoch millis and timezone id. The value ranges from 1 to 366. */ @@ -450,6 +844,15 @@ public static int dayOfYear(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getDayOfYear(); } + @ScalarFunction(names = {"dayOfYearMV", "day_of_year_mv", "doyMV"}) + public static int[] dayOfYear(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = dayOfYear(millis[i], timezoneId); + } + return results; + } + /** * Returns the day of the month from the given epoch millis in UTC timezone. The value ranges from 1 to 31. */ @@ -458,6 +861,15 @@ public static int dayOfMonth(long millis) { return new DateTime(millis, DateTimeZone.UTC).getDayOfMonth(); } + @ScalarFunction(names = {"dayMV", "dayOfMonthMV", "day_of_month_mv"}) + public static int[] dayOfMonthMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = dayOfMonth(millis[i]); + } + return results; + } + /** * Returns the day of the month from the given epoch millis and timezone id. The value ranges from 1 to 31. */ @@ -466,6 +878,15 @@ public static int dayOfMonth(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getDayOfMonth(); } + @ScalarFunction(names = {"dayMV", "dayOfMonthMV", "day_of_month_mv"}) + public static int[] dayOfMonthMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = dayOfMonth(millis[i], timezoneId); + } + return results; + } + /** * Returns the day of the week from the given epoch millis in UTC timezone. The value ranges from 1 (Monday) to 7 * (Sunday). @@ -475,6 +896,15 @@ public static int dayOfWeek(long millis) { return new DateTime(millis, DateTimeZone.UTC).getDayOfWeek(); } + @ScalarFunction(names = {"dayOfWeekMV", "day_of_week_mv", "dowMV"}) + public static int[] dayOfWeekMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = dayOfWeek(millis[i]); + } + return results; + } + /** * Returns the day of the week from the given epoch millis and timezone id. The value ranges from 1 (Monday) to 7 * (Sunday). @@ -484,6 +914,15 @@ public static int dayOfWeek(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getDayOfWeek(); } + @ScalarFunction(names = {"dayOfWeekMV", "day_of_week_mv", "dowMV"}) + public static int[] dayOfWeekMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = dayOfWeek(millis[i], timezoneId); + } + return results; + } + /** * Returns the hour of the day from the given epoch millis in UTC timezone. The value ranges from 0 to 23. */ @@ -492,6 +931,15 @@ public static int hour(long millis) { return new DateTime(millis, DateTimeZone.UTC).getHourOfDay(); } + @ScalarFunction + public static int[] hourMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = hour(millis[i]); + } + return results; + } + /** * Returns the hour of the day from the given epoch millis and timezone id. The value ranges from 0 to 23. */ @@ -500,6 +948,15 @@ public static int hour(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getHourOfDay(); } + @ScalarFunction + public static int[] hourMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = hour(millis[i], timezoneId); + } + return results; + } + /** * Returns the minute of the hour from the given epoch millis in UTC timezone. The value ranges from 0 to 59. */ @@ -508,6 +965,15 @@ public static int minute(long millis) { return new DateTime(millis, DateTimeZone.UTC).getMinuteOfHour(); } + @ScalarFunction + public static int[] minuteMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = minute(millis[i]); + } + return results; + } + /** * Returns the minute of the hour from the given epoch millis and timezone id. The value ranges from 0 to 59. */ @@ -516,6 +982,15 @@ public static int minute(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getMinuteOfHour(); } + @ScalarFunction + public static int[] minuteMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = minute(millis[i], timezoneId); + } + return results; + } + /** * Returns the second of the minute from the given epoch millis in UTC timezone. The value ranges from 0 to 59. */ @@ -524,6 +999,15 @@ public static int second(long millis) { return new DateTime(millis, DateTimeZone.UTC).getSecondOfMinute(); } + @ScalarFunction + public static int[] secondMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = second(millis[i]); + } + return results; + } + /** * Returns the second of the minute from the given epoch millis and timezone id. The value ranges from 0 to 59. */ @@ -532,6 +1016,15 @@ public static int second(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getSecondOfMinute(); } + @ScalarFunction + public static int[] secondMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = second(millis[i], timezoneId); + } + return results; + } + /** * Returns the millisecond of the second from the given epoch millis in UTC timezone. The value ranges from 0 to 999. */ @@ -540,6 +1033,15 @@ public static int millisecond(long millis) { return new DateTime(millis, DateTimeZone.UTC).getMillisOfSecond(); } + @ScalarFunction + public static int[] millisecondMV(long[] millis) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = millisecond(millis[i]); + } + return results; + } + /** * Returns the millisecond of the second from the given epoch millis and timezone id. The value ranges from 0 to 999. */ @@ -548,6 +1050,15 @@ public static int millisecond(long millis, String timezoneId) { return new DateTime(millis, DateTimeZone.forID(timezoneId)).getMillisOfSecond(); } + @ScalarFunction + public static int[] millisecondMV(long[] millis, String timezoneId) { + int[] results = new int[millis.length]; + for (int i = 0; i < millis.length; i++) { + results[i] = millisecond(millis[i], timezoneId); + } + return results; + } + /** * The sql compatible date_trunc function for epoch time. * @@ -557,7 +1068,17 @@ public static int millisecond(long millis, String timezoneId) { */ @ScalarFunction public static long dateTrunc(String unit, long timeValue) { - return dateTrunc(unit, timeValue, TimeUnit.MILLISECONDS, ISOChronology.getInstanceUTC(), TimeUnit.MILLISECONDS); + return dateTrunc(unit, timeValue, TimeUnit.MILLISECONDS.name(), ISOChronology.getInstanceUTC(), + TimeUnit.MILLISECONDS.name()); + } + + @ScalarFunction + public static long[] dateTruncMV(String unit, long[] timeValue) { + long[] results = new long[timeValue.length]; + for (int i = 0; i < timeValue.length; i++) { + results[i] = dateTrunc(unit, timeValue[i]); + } + return results; } /** @@ -565,53 +1086,80 @@ public static long dateTrunc(String unit, long timeValue) { * * @param unit truncate to unit (millisecond, second, minute, hour, day, week, month, quarter, year) * @param timeValue value to truncate - * @param inputTimeUnitStr TimeUnit of value, expressed in Java's joda TimeUnit + * @param inputTimeUnit TimeUnit of value, expressed in Java's joda TimeUnit * @return truncated timeValue in same TimeUnit as the input */ @ScalarFunction - public static long dateTrunc(String unit, long timeValue, String inputTimeUnitStr) { - TimeUnit inputTimeUnit = TimeUnit.valueOf(inputTimeUnitStr); + public static long dateTrunc(String unit, long timeValue, String inputTimeUnit) { return dateTrunc(unit, timeValue, inputTimeUnit, ISOChronology.getInstanceUTC(), inputTimeUnit); } + @ScalarFunction + public static long[] dateTruncMV(String unit, long[] timeValue, String inputTimeUnit) { + long[] results = new long[timeValue.length]; + for (int i = 0; i < timeValue.length; i++) { + results[i] = dateTrunc(unit, timeValue[i], inputTimeUnit); + } + return results; + } + /** * The sql compatible date_trunc function for epoch time. * * @param unit truncate to unit (millisecond, second, minute, hour, day, week, month, quarter, year) * @param timeValue value to truncate - * @param inputTimeUnitStr TimeUnit of value, expressed in Java's joda TimeUnit + * @param inputTimeUnit TimeUnit of value, expressed in Java's joda TimeUnit * @param timeZone timezone of the input * @return truncated timeValue in same TimeUnit as the input */ @ScalarFunction - public static long dateTrunc(String unit, long timeValue, String inputTimeUnitStr, String timeZone) { - TimeUnit inputTimeUnit = TimeUnit.valueOf(inputTimeUnitStr); + public static long dateTrunc(String unit, long timeValue, String inputTimeUnit, String timeZone) { return dateTrunc(unit, timeValue, inputTimeUnit, DateTimeUtils.getChronology(TimeZoneKey.getTimeZoneKey(timeZone)), inputTimeUnit); } + @ScalarFunction + public static long[] dateTruncMV(String unit, long[] timeValue, String inputTimeUnit, String timeZone) { + long[] results = new long[timeValue.length]; + for (int i = 0; i < timeValue.length; i++) { + results[i] = dateTrunc(unit, timeValue[i], inputTimeUnit, timeZone); + } + return results; + } + /** * The sql compatible date_trunc function for epoch time. * * @param unit truncate to unit (millisecond, second, minute, hour, day, week, month, quarter, year) * @param timeValue value to truncate - * @param inputTimeUnitStr TimeUnit of value, expressed in Java's joda TimeUnit + * @param inputTimeUnit TimeUnit of value, expressed in Java's joda TimeUnit * @param timeZone timezone of the input - * @param outputTimeUnitStr TimeUnit to convert the output to + * @param outputTimeUnit TimeUnit to convert the output to * @return truncated timeValue * */ @ScalarFunction - public static long dateTrunc(String unit, long timeValue, String inputTimeUnitStr, String timeZone, - String outputTimeUnitStr) { - return dateTrunc(unit, timeValue, TimeUnit.valueOf(inputTimeUnitStr), - DateTimeUtils.getChronology(TimeZoneKey.getTimeZoneKey(timeZone)), TimeUnit.valueOf(outputTimeUnitStr)); + public static long dateTrunc(String unit, long timeValue, String inputTimeUnit, String timeZone, + String outputTimeUnit) { + return dateTrunc(unit, timeValue, inputTimeUnit, + DateTimeUtils.getChronology(TimeZoneKey.getTimeZoneKey(timeZone)), outputTimeUnit); + } + + @ScalarFunction + public static long[] dateTruncMV(String unit, long[] timeValue, String inputTimeUnit, String timeZone, + String outputTimeUnit) { + long[] results = new long[timeValue.length]; + for (int i = 0; i < timeValue.length; i++) { + results[i] = dateTrunc(unit, timeValue[i], inputTimeUnit, timeZone, outputTimeUnit); + } + return results; } - private static long dateTrunc(String unit, long timeValue, TimeUnit inputTimeUnit, ISOChronology chronology, - TimeUnit outputTimeUnit) { - return outputTimeUnit.convert(DateTimeUtils.getTimestampField(chronology, unit) - .roundFloor(TimeUnit.MILLISECONDS.convert(timeValue, inputTimeUnit)), TimeUnit.MILLISECONDS); + private static long dateTrunc(String unit, long timeValue, String inputTimeUnit, ISOChronology chronology, + String outputTimeUnit) { + return TimeUnit.valueOf(outputTimeUnit.toUpperCase()).convert(DateTimeUtils.getTimestampField(chronology, unit) + .roundFloor(TimeUnit.MILLISECONDS.convert(timeValue, TimeUnit.valueOf(inputTimeUnit.toUpperCase()))), + TimeUnit.MILLISECONDS); } /** @@ -629,6 +1177,15 @@ public static long timestampAdd(String unit, long interval, long timestamp) { return millis; } + @ScalarFunction(names = {"timestampAddMV", "dateAddMV"}) + public static long[] timestampAddMV(String unit, long interval, long[] timestamp) { + long[] results = new long[timestamp.length]; + for (int i = 0; i < timestamp.length; i++) { + results[i] = timestampAdd(unit, interval, timestamp[i]); + } + return results; + } + /** * Get difference between two timestamps and return the result in the specified timeunit. * e.g. timestampDiff('days', ago('10D'), ago('2D')) will return 8 i.e. 8 days @@ -642,4 +1199,22 @@ public static long timestampDiff(String unit, long timestamp1, long timestamp2) ISOChronology chronology = ISOChronology.getInstanceUTC(); return DateTimeUtils.getTimestampField(chronology, unit).getDifferenceAsLong(timestamp2, timestamp1); } + + @ScalarFunction(names = {"timestampDiffMV", "dateDiffMV"}) + public static long[] timestampDiffMV(String unit, long[] timestamp1, long timestamp2) { + long[] results = new long[timestamp1.length]; + for (int i = 0; i < timestamp1.length; i++) { + results[i] = timestampDiff(unit, timestamp1[i], timestamp2); + } + return results; + } + + @ScalarFunction(names = {"timestampDiffMVReverse", "dateDiffMVReverse"}) + public static long[] timestampDiffMVReverse(String unit, long timestamp1, long[] timestamp2) { + long[] results = new long[timestamp2.length]; + for (int i = 0; i < timestamp2.length; i++) { + results[i] = timestampDiff(unit, timestamp1, timestamp2[i]); + } + return results; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java index a81ec373b44..fa50cc6c486 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java @@ -128,6 +128,7 @@ private static Object[] convertObjectToArray(Object arrayObject) { /** * Extract from Json with path to String */ + @Nullable @ScalarFunction public static String jsonPathString(Object object, String jsonPath) throws JsonProcessingException { @@ -135,7 +136,7 @@ public static String jsonPathString(Object object, String jsonPath) if (jsonValue instanceof String) { return (String) jsonValue; } - return JsonUtils.objectToString(jsonValue); + return jsonValue == null ? null : JsonUtils.objectToString(jsonValue); } /** diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ObjectFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ObjectFunctions.java index 143edd740f5..5cdc0b34d2f 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ObjectFunctions.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ObjectFunctions.java @@ -94,33 +94,41 @@ private static Object coalesceVar(Object... objects) { return null; } - @ScalarFunction - public static Object caseWhen(boolean c1, Object o1, Object oe) { + @Nullable + @ScalarFunction(nullableParameters = true, names = {"case", "caseWhen", "case_when"}) + public static Object caseWhen(boolean c1, @Nullable Object o1, @Nullable Object oe) { return caseWhenVar(c1, o1, oe); } - @ScalarFunction - public static Object caseWhen(boolean c1, Object o1, boolean c2, Object o2, Object oe) { + @Nullable + @ScalarFunction(nullableParameters = true, names = {"case", "caseWhen", "case_when"}) + public static Object caseWhen(boolean c1, @Nullable Object o1, boolean c2, @Nullable Object o2, @Nullable Object oe) { return caseWhenVar(c1, o1, c2, o2, oe); } - @ScalarFunction - public static Object caseWhen(boolean c1, Object o1, boolean c2, Object o2, boolean c3, Object o3, Object oe) { + @Nullable + @ScalarFunction(nullableParameters = true, names = {"case", "caseWhen", "case_when"}) + public static Object caseWhen(boolean c1, @Nullable Object o1, boolean c2, @Nullable Object o2, boolean c3, + @Nullable Object o3, @Nullable Object oe) { return caseWhenVar(c1, o1, c2, o2, c3, o3, oe); } - @ScalarFunction - public static Object caseWhen(boolean c1, Object o1, boolean c2, Object o2, boolean c3, Object o3, boolean c4, - Object o4, Object oe) { + @Nullable + @ScalarFunction(nullableParameters = true, names = {"case", "caseWhen", "case_when"}) + public static Object caseWhen(boolean c1, @Nullable Object o1, boolean c2, @Nullable Object o2, boolean c3, + @Nullable Object o3, boolean c4, @Nullable Object o4, @Nullable Object oe) { return caseWhenVar(c1, o1, c2, o2, c3, o3, c4, o4, oe); } - @ScalarFunction - public static Object caseWhen(boolean c1, Object o1, boolean c2, Object o2, boolean c3, Object o3, boolean c4, - Object o4, boolean c5, Object o5, Object oe) { + @Nullable + @ScalarFunction(nullableParameters = true, names = {"case", "caseWhen", "case_when"}) + public static Object caseWhen(boolean c1, @Nullable Object o1, boolean c2, @Nullable Object o2, boolean c3, + @Nullable Object o3, boolean c4, @Nullable Object o4, boolean c5, @Nullable Object o5, + @Nullable Object oe) { return caseWhenVar(c1, o1, c2, o2, c3, o3, c4, o4, c5, o5, oe); } + @Nullable private static Object caseWhenVar(Object... objs) { for (int i = 0; i < objs.length - 1; i += 2) { if (BooleanUtils.toBoolean(objs[i])) { diff --git a/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java b/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java index eb445c973e9..b3776ddc9f2 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.spi.utils.JsonUtils; @@ -46,18 +47,22 @@ */ public class SegmentLineage { private static final String COMMA_SEPARATOR = ","; + private static final String CUSTOM_MAP_KEY = "custom.map"; private final String _tableNameWithType; private final Map _lineageEntries; + private Map _customMap = null; public SegmentLineage(String tableNameWithType) { _tableNameWithType = tableNameWithType; _lineageEntries = new HashMap<>(); } - public SegmentLineage(String tableNameWithType, Map lineageEntries) { + public SegmentLineage(String tableNameWithType, Map lineageEntries, + @Nullable Map customMap) { _tableNameWithType = tableNameWithType; _lineageEntries = lineageEntries; + _customMap = customMap; } public String getTableNameWithType() { @@ -86,6 +91,10 @@ public void updateLineageEntry(String lineageEntryId, LineageEntry lineageEntry) _lineageEntries.put(lineageEntryId, lineageEntry); } + public Map getLineageEntries() { + return _lineageEntries; + } + /** * Retrieve lineage entry * @param lineageEntryId the id for the lineage entry @@ -111,6 +120,22 @@ public void deleteLineageEntry(String lineageEntryId) { _lineageEntries.remove(lineageEntryId); } + /** + * Retrieve custom map + * @return custom map + */ + public Map getCustomMap() { + return _customMap; + } + + /** + * Set custom map + * @param customMap + */ + public void setCustomMap(Map customMap) { + _customMap = customMap; + } + /** * Convert ZNRecord to segment lineage * @param record ZNRecord representation of the segment lineage @@ -120,6 +145,7 @@ public static SegmentLineage fromZNRecord(ZNRecord record) { String tableNameWithType = record.getId(); Map lineageEntries = new HashMap<>(); Map> listFields = record.getListFields(); + Map customMap = record.getMapField(CUSTOM_MAP_KEY); for (Map.Entry> listField : listFields.entrySet()) { String lineageId = listField.getKey(); List value = listField.getValue(); @@ -130,7 +156,7 @@ public static SegmentLineage fromZNRecord(ZNRecord record) { long timestamp = Long.parseLong(value.get(3)); lineageEntries.put(lineageId, new LineageEntry(segmentsFrom, segmentsTo, state, timestamp)); } - return new SegmentLineage(tableNameWithType, lineageEntries); + return new SegmentLineage(tableNameWithType, lineageEntries, customMap); } /** @@ -148,6 +174,9 @@ public ZNRecord toZNRecord() { List listEntry = Arrays.asList(segmentsFrom, segmentsTo, state, timestamp); znRecord.setListField(entry.getKey(), listEntry); } + if (_customMap != null) { + znRecord.setMapField(CUSTOM_MAP_KEY, _customMap); + } return znRecord; } @@ -163,6 +192,9 @@ public ObjectNode toJsonObject() { .sorted(Map.Entry.comparingByValue(Comparator.comparingLong(LineageEntry::getTimestamp))) .forEachOrdered(x -> sortedLineageEntries.put(x.getKey(), x.getValue())); jsonObject.set("lineageEntries", JsonUtils.objectToJsonNode(sortedLineageEntries)); + if (_customMap != null) { + jsonObject.set("customMap", JsonUtils.objectToJsonNode(_customMap)); + } return jsonObject; } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineageUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineageUtils.java index 458b2477ca8..073ff679b59 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineageUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineageUtils.java @@ -26,7 +26,6 @@ * Util class for Segment Lineage */ public class SegmentLineageUtils { - private SegmentLineageUtils() { } @@ -45,12 +44,11 @@ public static String generateLineageEntryId() { */ public static void filterSegmentsBasedOnLineageInPlace(Set segments, SegmentLineage segmentLineage) { if (segmentLineage != null) { - for (String lineageEntryId : segmentLineage.getLineageEntryIds()) { - LineageEntry lineageEntry = segmentLineage.getLineageEntry(lineageEntryId); + for (LineageEntry lineageEntry : segmentLineage.getLineageEntries().values()) { if (lineageEntry.getState() == LineageEntryState.COMPLETED) { - segments.removeAll(lineageEntry.getSegmentsFrom()); + lineageEntry.getSegmentsFrom().forEach(segments::remove); } else { - segments.removeAll(lineageEntry.getSegmentsTo()); + lineageEntry.getSegmentsTo().forEach(segments::remove); } } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java b/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java index 623a8564542..c6425ca0e02 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java @@ -26,9 +26,12 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.helix.AccessOption; +import org.apache.helix.store.HelixPropertyStore; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.helix.zookeeper.zkclient.exception.ZkBadVersionException; +import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.metadata.controllerjob.ControllerJobType; import org.apache.pinot.common.metadata.instance.InstanceZKMetadata; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.utils.SchemaUtils; @@ -110,14 +113,14 @@ public static String constructPropertyStorePathForInstancePartitions(String inst return StringUtil.join("/", PROPERTYSTORE_INSTANCE_PARTITIONS_PREFIX, instancePartitionsName); } - public static String constructPropertyStorePathForControllerJob() { - return StringUtil.join("/", PROPERTYSTORE_CONTROLLER_JOBS_PREFIX); - } - public static String constructPropertyStorePathForResource(String resourceName) { return StringUtil.join("/", PROPERTYSTORE_SEGMENTS_PREFIX, resourceName); } + public static String constructPropertyStorePathForControllerJob(ControllerJobType jobType) { + return StringUtil.join("/", PROPERTYSTORE_CONTROLLER_JOBS_PREFIX, jobType.name()); + } + public static String constructPropertyStorePathForResourceConfig(String resourceName) { return StringUtil.join("/", PROPERTYSTORE_TABLE_CONFIGS_PREFIX, resourceName); } @@ -261,6 +264,20 @@ public static UserConfig getUserConfig(ZkHelixPropertyStore propertySt } } + @Nullable + public static List getAllInstancePartitions(HelixPropertyStore propertyStore) { + List znRecordss = + propertyStore.getChildren(PROPERTYSTORE_INSTANCE_PARTITIONS_PREFIX, null, AccessOption.PERSISTENT); + + try { + return Optional.ofNullable(znRecordss).orElseGet(ArrayList::new).stream().map(InstancePartitions::fromZNRecord) + .collect(Collectors.toList()); + } catch (Exception e) { + LOGGER.error("Caught exception while getting instance partitions", e); + return null; + } + } + @Nullable public static List getAllUserConfig(ZkHelixPropertyStore propertyStore) { List znRecordss = diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metadata/controllerjob/ControllerJobType.java b/pinot-common/src/main/java/org/apache/pinot/common/metadata/controllerjob/ControllerJobType.java index b62144fd378..aaad454787b 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metadata/controllerjob/ControllerJobType.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metadata/controllerjob/ControllerJobType.java @@ -19,7 +19,5 @@ package org.apache.pinot.common.metadata.controllerjob; public enum ControllerJobType { - RELOAD_SEGMENT, - RELOAD_ALL_SEGMENTS, - FORCE_COMMIT + RELOAD_SEGMENT, FORCE_COMMIT, TABLE_REBALANCE } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/AbstractMetrics.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/AbstractMetrics.java index f1044abd777..ff41b12abfc 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/AbstractMetrics.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/AbstractMetrics.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.common.metrics; -import com.google.common.annotations.VisibleForTesting; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -44,8 +43,6 @@ /** * Common code for metrics implementations. - * TODO: 1. With gauge updatable, we can remove _gaugeValues 2. Remove methods with callback in name since the callback - * function can not be updated. */ public abstract class AbstractMetrics { @@ -58,6 +55,9 @@ public abstract class AbstractMetrics _gaugeValues = new ConcurrentHashMap(); private final boolean _isTableLevelMetricsEnabled; @@ -93,6 +93,10 @@ public PinotMetricsRegistry getMetricsRegistry() { public interface QueryPhase { String getQueryPhaseName(); + + default String getDescription() { + return ""; + } } public interface Meter { @@ -101,6 +105,10 @@ public interface Meter { String getUnit(); boolean isGlobal(); + + default String getDescription() { + return ""; + } } public interface Gauge { @@ -109,12 +117,18 @@ public interface Gauge { String getUnit(); boolean isGlobal(); + default String getDescription() { + return ""; + } } public interface Timer { String getTimerName(); boolean isGlobal(); + default String getDescription() { + return ""; + } } public void addPhaseTiming(String tableName, QP phase, long duration, TimeUnit timeUnit) { @@ -326,12 +340,16 @@ public PinotMeter getMeteredTableValue(final String tableName, final M meter) { } /** + * @deprecated Please use addMeteredTableValue(final String tableName, final M meter, final long unitCount), which is + * designed for tracking count and rates. + * * Logs a value to a table gauge. * * @param tableName The table name * @param gauge The gauge to use * @param unitCount The number of units to add to the gauge */ + @Deprecated public void addValueToTableGauge(final String tableName, final G gauge, final long unitCount) { final String fullGaugeName = composeTableGaugeName(tableName, gauge); @@ -425,11 +443,15 @@ private void setValueOfGauge(long value, String gaugeName) { } /** + * @deprecated Please use addMeteredGlobalValue(final M meter, final long unitCount), which is designed for tracking + * count and rates. + * * Adds a value to a table gauge. * * @param gauge The gauge to use * @param unitCount The number of units to add to the gauge */ + @Deprecated public void addValueToGlobalGauge(final G gauge, final long unitCount) { String gaugeName = gauge.getGaugeName(); @@ -448,19 +470,6 @@ public void addValueToGlobalGauge(final G gauge, final long unitCount) { } } - /** - * Gets the value of a table gauge. - * - * @param tableName The table name - * @param gauge The gauge to use - */ - public long getValueOfTableGauge(final String tableName, final G gauge) { - final String fullGaugeName = composeTableGaugeName(tableName, gauge); - - AtomicLong gaugeValue = _gaugeValues.get(fullGaugeName); - return gaugeValue == null ? 0 : gaugeValue.get(); - } - /** * Initializes all global meters (such as exceptions count) to zero. */ @@ -732,14 +741,4 @@ private void removeGaugeFromMetricRegistry(String metricName) { protected String getTableName(String tableName) { return _isTableLevelMetricsEnabled || _allowedTables.contains(tableName) ? tableName : "allTables"; } - - /** - * Check if the metric name appears in the gauge value map. - * @param metricName metric name - * @return True if the metric name appears on the gauge value map. False otherwise. - */ - @VisibleForTesting - public boolean containsGauge(String metricName) { - return _gaugeValues.containsKey(metricName); - } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerGauge.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerGauge.java index 44a9161bb4d..93bf03edbe9 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerGauge.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerGauge.java @@ -34,7 +34,8 @@ public enum BrokerGauge implements AbstractMetrics.Gauge { REQUEST_SIZE("requestSize", false), RESIZE_TIME_MS("milliseconds", false), UNHEALTHY_SERVERS("servers", true), - TIME_BOUNDARY_DIFFERENCE("milliseconds", false); + TIME_BOUNDARY_DIFFERENCE("milliseconds", false), + JVM_HEAP_USED_BYTES("bytes", true); private final String _brokerGaugeName; private final String _unit; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMeter.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMeter.java index 17f9ae53542..606eeb05a69 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMeter.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMeter.java @@ -32,6 +32,8 @@ public enum BrokerMeter implements AbstractMetrics.Meter { QUERIES("queries", false), // These metrics track the exceptions caught during query execution in broker side. + // Query rejected by Jersey thread pool executor + QUERY_REJECTED_EXCEPTIONS("exceptions", true), // Query compile phase. REQUEST_COMPILATION_EXCEPTIONS("exceptions", true), // Get resource phase. @@ -40,6 +42,8 @@ public enum BrokerMeter implements AbstractMetrics.Meter { QUERY_VALIDATION_EXCEPTIONS("exceptions", false), // Query validation phase. UNKNOWN_COLUMN_EXCEPTIONS("exceptions", false), + // Queries preempted by accountant + QUERIES_KILLED("query", true), // Scatter phase. NO_SERVER_FOUND_EXCEPTIONS("exceptions", false), REQUEST_TIMEOUT_BEFORE_SCATTERED_EXCEPTIONS("exceptions", false), @@ -51,6 +55,8 @@ public enum BrokerMeter implements AbstractMetrics.Meter { DATA_TABLE_DESERIALIZATION_EXCEPTIONS("exceptions", false), // Reduce responses phase. RESPONSE_MERGE_EXCEPTIONS("exceptions", false), + HEAP_CRITICAL_LEVEL_EXCEEDED("count", true), + HEAP_PANIC_LEVEL_EXCEEDED("count", true), // These metrics track the number of bad broker responses. // This metric track the number of broker responses with processing exceptions inside. diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMetrics.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMetrics.java index 0a1c995dbb8..cf6a310eb21 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMetrics.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMetrics.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; import org.apache.pinot.spi.metrics.PinotMetricsRegistry; import static org.apache.pinot.spi.utils.CommonConstants.Broker.DEFAULT_ENABLE_TABLE_LEVEL_METRICS; @@ -32,6 +33,24 @@ */ public class BrokerMetrics extends AbstractMetrics { + private static final AtomicReference BROKER_METRICS_INSTANCE = new AtomicReference<>(); + + /** + * register the brokerMetrics onto this class, so that we don't need to pass it down as a parameter + */ + public static boolean register(BrokerMetrics brokerMetrics) { + return BROKER_METRICS_INSTANCE.compareAndSet(null, brokerMetrics); + } + + /** + * should always call after registration + */ + public static BrokerMetrics get() { + BrokerMetrics ret = BROKER_METRICS_INSTANCE.get(); + assert ret != null; + return ret; + } + /** * Constructs the broker metrics. * diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ControllerMeter.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ControllerMeter.java index edd70ee6425..8de33d0e41a 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ControllerMeter.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ControllerMeter.java @@ -56,7 +56,9 @@ public enum ControllerMeter implements AbstractMetrics.Meter { NUMBER_REVERT_REPLACE_FAILURE("NumRevertReplaceFailure", false), CRON_SCHEDULER_JOB_TRIGGERED("cronSchedulerJobTriggered", false), CRON_SCHEDULER_JOB_SKIPPED("cronSchedulerJobSkipped", false), + LLC_SEGMENTS_DEEP_STORE_UPLOAD_RETRY_SUCCESS("LLCSegmentDeepStoreUploadRetrySuccess", false), LLC_SEGMENTS_DEEP_STORE_UPLOAD_RETRY_ERROR("LLCSegmentDeepStoreUploadRetryError", false), + SEGMENT_MISSING_DEEP_STORE_LINK("RealtimeSegmentMissingDeepStoreLink", false), NUMBER_ADHOC_TASKS_SUBMITTED("adhocTasks", false); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/MinionMeter.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/MinionMeter.java index db25ca0f173..376f86e55e5 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/MinionMeter.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/MinionMeter.java @@ -24,12 +24,14 @@ public enum MinionMeter implements AbstractMetrics.Meter { HEALTH_CHECK_GOOD_CALLS("healthChecks", true), HEALTH_CHECK_BAD_CALLS("healthChecks", true), - + NUMBER_TASKS("tasks", false), NUMBER_TASKS_EXECUTED("tasks", false), NUMBER_TASKS_COMPLETED("tasks", false), NUMBER_TASKS_CANCELLED("tasks", false), NUMBER_TASKS_FAILED("tasks", false), - NUMBER_TASKS_FATAL_FAILED("tasks", false); + NUMBER_TASKS_FATAL_FAILED("tasks", false), + SEGMENT_UPLOAD_FAIL_COUNT("segments", false), + SEGMENT_DOWNLOAD_FAIL_COUNT("segments", false); private final String _meterName; private final String _unit; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerGauge.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerGauge.java index 7dd52e90eae..210d95fc2f5 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerGauge.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerGauge.java @@ -46,17 +46,25 @@ public enum ServerGauge implements AbstractMetrics.Gauge { DEDUP_PRIMARY_KEYS_COUNT("dedupPrimaryKeysCount", false), CONSUMPTION_QUOTA_UTILIZATION("ratio", false), JVM_HEAP_USED_BYTES("bytes", true), - // Ingestion delay metric - REALTIME_INGESTION_DELAY_MS("milliseconds", false); + // Ingestion delay metrics + REALTIME_INGESTION_DELAY_MS("milliseconds", false), + END_TO_END_REALTIME_INGESTION_DELAY_MS("milliseconds", false); private final String _gaugeName; private final String _unit; private final boolean _global; + private final String _description; + ServerGauge(String unit, boolean global) { + this(unit, global, ""); + } + + ServerGauge(String unit, boolean global, String description) { _unit = unit; _global = global; _gaugeName = Utils.toCamelCase(name().toLowerCase()); + _description = description; } @Override @@ -78,4 +86,9 @@ public String getUnit() { public boolean isGlobal() { return _global; } + + @Override + public String getDescription() { + return _description; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java index cce863623ec..4681b41659d 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java @@ -35,6 +35,7 @@ public enum ServerMeter implements AbstractMetrics.Meter { DELETED_SEGMENT_COUNT("segments", false), DELETE_TABLE_FAILURES("tables", false), REALTIME_ROWS_CONSUMED("rows", true), + REALTIME_ROWS_FILTERED("rows", false), INVALID_REALTIME_ROWS_DROPPED("rows", false), INCOMPLETE_REALTIME_ROWS_CONSUMED("rows", false), REALTIME_CONSUMPTION_EXCEPTIONS("exceptions", true), @@ -45,6 +46,7 @@ public enum ServerMeter implements AbstractMetrics.Meter { UPSERT_KEYS_IN_WRONG_SEGMENT("rows", false), PARTIAL_UPSERT_OUT_OF_ORDER("rows", false), PARTIAL_UPSERT_KEYS_NOT_REPLACED("rows", false), + UPSERT_OUT_OF_ORDER("rows", false), ROWS_WITH_ERRORS("rows", false), LLC_CONTROLLER_RESPONSE_NOT_SENT("messages", true), LLC_CONTROLLER_RESPONSE_COMMIT("messages", true), @@ -68,24 +70,36 @@ public enum ServerMeter implements AbstractMetrics.Meter { RELOAD_FAILURES("segments", false), REFRESH_FAILURES("segments", false), UNTAR_FAILURES("segments", false), - SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES("segments", false), + SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES("segments", false, "Counts the number of segment " + + "fetch failures"), SEGMENT_DIR_MOVEMENT_FAILURES("segments", false), SEGMENT_DOWNLOAD_FAILURES("segments", false), SEGMENT_DOWNLOAD_FROM_REMOTE_FAILURES("segments", false), SEGMENT_DOWNLOAD_FROM_PEERS_FAILURES("segments", false), + SEGMENT_UPLOAD_FAILURE("segments", false), + SEGMENT_UPLOAD_SUCCESS("segments", false), + // Emitted only by Server to Deep-store segment uploader. + SEGMENT_UPLOAD_TIMEOUT("segments", false), NUM_RESIZES("numResizes", false), NO_TABLE_ACCESS("tables", true), INDEXING_FAILURES("attributeValues", true), READINESS_CHECK_OK_CALLS("readinessCheck", true), READINESS_CHECK_BAD_CALLS("readinessCheck", true), - QUERIES_PREEMPTED("query", true), + QUERIES_KILLED("query", true), + HEAP_CRITICAL_LEVEL_EXCEEDED("count", true), + HEAP_PANIC_LEVEL_EXCEEDED("count", true), // Netty connection metrics NETTY_CONNECTION_BYTES_RECEIVED("nettyConnection", true), NETTY_CONNECTION_RESPONSES_SENT("nettyConnection", true), NETTY_CONNECTION_BYTES_SENT("nettyConnection", true), + // GRPC related metrics + GRPC_QUERIES("grpcQueries", true), + GRPC_BYTES_RECEIVED("grpcBytesReceived", true), + GRPC_BYTES_SENT("grpcBytesSent", true), + NUM_SEGMENTS_PRUNED_INVALID("numSegmentsPrunedInvalid", false), NUM_SEGMENTS_PRUNED_BY_LIMIT("numSegmentsPrunedByLimit", false), NUM_SEGMENTS_PRUNED_BY_VALUE("numSegmentsPrunedByValue", false),; @@ -93,11 +107,17 @@ public enum ServerMeter implements AbstractMetrics.Meter { private final String _meterName; private final String _unit; private final boolean _global; + private final String _description; ServerMeter(String unit, boolean global) { + this(unit, global, ""); + } + + ServerMeter(String unit, boolean global, String description) { _unit = unit; _global = global; _meterName = Utils.toCamelCase(name().toLowerCase()); + _description = description; } @Override @@ -119,4 +139,9 @@ public String getUnit() { public boolean isGlobal() { return _global; } + + @Override + public String getDescription() { + return _description; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerTimer.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerTimer.java index b6d83bcf6c7..aa0952730bd 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerTimer.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerTimer.java @@ -26,31 +26,41 @@ * */ public enum ServerTimer implements AbstractMetrics.Timer { - // metric tracking the freshness lag for consuming segments - FRESHNESS_LAG_MS("freshnessLagMs", false), + FRESHNESS_LAG_MS("freshnessLagMs", false, "Tracks the freshness lag for consuming segments. " + + "Computed as the time-period between when the data was last updated in the table and the current time."), - // The latency of sending the response from server to broker - NETTY_CONNECTION_SEND_RESPONSE_LATENCY("nettyConnection", false), + NETTY_CONNECTION_SEND_RESPONSE_LATENCY("nettyConnection", false, + "Latency of sending the response from server to broker. Computed as the time spent in sending " + + "response to brokers after the results are available."), - // Query cost (execution thread cpu time) for query processing on server - EXECUTION_THREAD_CPU_TIME_NS("nanoseconds", false), + EXECUTION_THREAD_CPU_TIME_NS("nanoseconds", false, "Query cost (execution thread cpu time) " + + "for query processing on server. Computed as time spent by all threads processing query and results " + + "(doesn't includes time spent in system activities)"), - // Query cost (system activities cpu time) for query processing on server - SYSTEM_ACTIVITIES_CPU_TIME_NS("nanoseconds", false), + SYSTEM_ACTIVITIES_CPU_TIME_NS("nanoseconds", false, "Query cost (system activities cpu time) " + + "for query processing on server. Computed as the time spent in processing query on the servers " + + "(only counts system acitivities such as GC, OS paging etc.)"), - // Query cost (response serialization cpu time) for query processing on server - RESPONSE_SER_CPU_TIME_NS("nanoseconds", false), + RESPONSE_SER_CPU_TIME_NS("nanoseconds", false, "Query cost (response serialization cpu time) " + + "for query processing on server. Computed as the time spent in serializing query response on servers"), - // Total query cost (thread cpu time + system activities cpu time + response serialization cpu time) for query - // processing on server - TOTAL_CPU_TIME_NS("nanoseconds", false); + SEGMENT_UPLOAD_TIME_MS("milliseconds", false), + + TOTAL_CPU_TIME_NS("nanoseconds", false, "Total query cost (thread cpu time + system " + + "activities cpu time + response serialization cpu time) for query processing on server."); private final String _timerName; private final boolean _global; + private final String _description; ServerTimer(String unit, boolean global) { + this(unit, global, ""); + } + + ServerTimer(String unit, boolean global, String description) { _global = global; _timerName = Utils.toCamelCase(name().toLowerCase()); + _description = description; } @Override @@ -67,4 +77,9 @@ public String getTimerName() { public boolean isGlobal() { return _global; } + + @Override + public String getDescription() { + return _description; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java b/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java index 83c271ee166..c6f4864ccb4 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java @@ -25,25 +25,34 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") -public class DataSource implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { - private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("DataSource"); - - private static final org.apache.thrift.protocol.TField TABLE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("tableName", org.apache.thrift.protocol.TType.STRING, (short)1); - private static final org.apache.thrift.protocol.TField SUBQUERY_FIELD_DESC = new org.apache.thrift.protocol.TField("subquery", org.apache.thrift.protocol.TType.STRUCT, (short)2); - - private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new DataSourceStandardSchemeFactory(); +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-03-14") +public class DataSource + implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, + Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = + new org.apache.thrift.protocol.TStruct("DataSource"); + + private static final org.apache.thrift.protocol.TField TABLE_NAME_FIELD_DESC = + new org.apache.thrift.protocol.TField("tableName", org.apache.thrift.protocol.TType.STRING, (short) 1); + private static final org.apache.thrift.protocol.TField SUBQUERY_FIELD_DESC = + new org.apache.thrift.protocol.TField("subquery", org.apache.thrift.protocol.TType.STRUCT, (short) 2); + private static final org.apache.thrift.protocol.TField JOIN_FIELD_DESC = + new org.apache.thrift.protocol.TField("join", org.apache.thrift.protocol.TType.STRUCT, (short) 3); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = + new DataSourceStandardSchemeFactory(); private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new DataSourceTupleSchemeFactory(); public @org.apache.thrift.annotation.Nullable java.lang.String tableName; // optional public @org.apache.thrift.annotation.Nullable PinotQuery subquery; // optional + public @org.apache.thrift.annotation.Nullable Join join; // optional /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ public enum _Fields implements org.apache.thrift.TFieldIdEnum { - TABLE_NAME((short)1, "tableName"), - SUBQUERY((short)2, "subquery"); + TABLE_NAME((short) 1, "tableName"), SUBQUERY((short) 2, "subquery"), JOIN((short) 3, "join"); - private static final java.util.Map byName = new java.util.HashMap(); + private static final java.util.Map byName = + new java.util.HashMap(); static { for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { @@ -56,11 +65,13 @@ public enum _Fields implements org.apache.thrift.TFieldIdEnum { */ @org.apache.thrift.annotation.Nullable public static _Fields findByThriftId(int fieldId) { - switch(fieldId) { + switch (fieldId) { case 1: // TABLE_NAME return TABLE_NAME; case 2: // SUBQUERY return SUBQUERY; + case 3: // JOIN + return JOIN; default: return null; } @@ -72,7 +83,9 @@ public static _Fields findByThriftId(int fieldId) { */ public static _Fields findByThriftIdOrThrow(int fieldId) { _Fields fields = findByThriftId(fieldId); - if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + if (fields == null) { + throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + } return fields; } @@ -92,24 +105,33 @@ public static _Fields findByName(java.lang.String name) { _fieldName = fieldName; } + @Override public short getThriftFieldId() { return _thriftId; } + @Override public java.lang.String getFieldName() { return _fieldName; } } // isset id assignments - private static final _Fields optionals[] = {_Fields.TABLE_NAME,_Fields.SUBQUERY}; + private static final _Fields optionals[] = {_Fields.TABLE_NAME, _Fields.SUBQUERY, _Fields.JOIN}; public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { - java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); - tmpMap.put(_Fields.TABLE_NAME, new org.apache.thrift.meta_data.FieldMetaData("tableName", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); - tmpMap.put(_Fields.SUBQUERY, new org.apache.thrift.meta_data.FieldMetaData("subquery", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "PinotQuery"))); + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = + new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.TABLE_NAME, + new org.apache.thrift.meta_data.FieldMetaData("tableName", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + tmpMap.put(_Fields.SUBQUERY, + new org.apache.thrift.meta_data.FieldMetaData("subquery", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, PinotQuery.class))); + tmpMap.put(_Fields.JOIN, + new org.apache.thrift.meta_data.FieldMetaData("join", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT, "Join"))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(DataSource.class, metaDataMap); } @@ -127,8 +149,12 @@ public DataSource(DataSource other) { if (other.isSetSubquery()) { this.subquery = new PinotQuery(other.subquery); } + if (other.isSetJoin()) { + this.join = new Join(other.join); + } } + @Override public DataSource deepCopy() { return new DataSource(this); } @@ -137,6 +163,7 @@ public DataSource deepCopy() { public void clear() { this.tableName = null; this.subquery = null; + this.join = null; } @org.apache.thrift.annotation.Nullable @@ -189,84 +216,141 @@ public void setSubqueryIsSet(boolean value) { } } + @org.apache.thrift.annotation.Nullable + public Join getJoin() { + return this.join; + } + + public DataSource setJoin(@org.apache.thrift.annotation.Nullable Join join) { + this.join = join; + return this; + } + + public void unsetJoin() { + this.join = null; + } + + /** Returns true if field join is set (has been assigned a value) and false otherwise */ + public boolean isSetJoin() { + return this.join != null; + } + + public void setJoinIsSet(boolean value) { + if (!value) { + this.join = null; + } + } + + @Override public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { switch (field) { - case TABLE_NAME: - if (value == null) { - unsetTableName(); - } else { - setTableName((java.lang.String)value); - } - break; + case TABLE_NAME: + if (value == null) { + unsetTableName(); + } else { + setTableName((java.lang.String) value); + } + break; - case SUBQUERY: - if (value == null) { - unsetSubquery(); - } else { - setSubquery((PinotQuery)value); - } - break; + case SUBQUERY: + if (value == null) { + unsetSubquery(); + } else { + setSubquery((PinotQuery) value); + } + break; + case JOIN: + if (value == null) { + unsetJoin(); + } else { + setJoin((Join) value); + } + break; } } @org.apache.thrift.annotation.Nullable + @Override public java.lang.Object getFieldValue(_Fields field) { switch (field) { - case TABLE_NAME: - return getTableName(); + case TABLE_NAME: + return getTableName(); - case SUBQUERY: - return getSubquery(); + case SUBQUERY: + return getSubquery(); + case JOIN: + return getJoin(); } throw new java.lang.IllegalStateException(); } /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override public boolean isSet(_Fields field) { if (field == null) { throw new java.lang.IllegalArgumentException(); } switch (field) { - case TABLE_NAME: - return isSetTableName(); - case SUBQUERY: - return isSetSubquery(); + case TABLE_NAME: + return isSetTableName(); + case SUBQUERY: + return isSetSubquery(); + case JOIN: + return isSetJoin(); } throw new java.lang.IllegalStateException(); } @Override public boolean equals(java.lang.Object that) { - if (that instanceof DataSource) - return this.equals((DataSource)that); + if (that instanceof DataSource) { + return this.equals((DataSource) that); + } return false; } public boolean equals(DataSource that) { - if (that == null) + if (that == null) { return false; - if (this == that) + } + if (this == that) { return true; + } boolean this_present_tableName = true && this.isSetTableName(); boolean that_present_tableName = true && that.isSetTableName(); if (this_present_tableName || that_present_tableName) { - if (!(this_present_tableName && that_present_tableName)) + if (!(this_present_tableName && that_present_tableName)) { return false; - if (!this.tableName.equals(that.tableName)) + } + if (!this.tableName.equals(that.tableName)) { return false; + } } boolean this_present_subquery = true && this.isSetSubquery(); boolean that_present_subquery = true && that.isSetSubquery(); if (this_present_subquery || that_present_subquery) { - if (!(this_present_subquery && that_present_subquery)) + if (!(this_present_subquery && that_present_subquery)) { + return false; + } + if (!this.subquery.equals(that.subquery)) { + return false; + } + } + + boolean this_present_join = true && this.isSetJoin(); + boolean that_present_join = true && that.isSetJoin(); + if (this_present_join || that_present_join) { + if (!(this_present_join && that_present_join)) { return false; - if (!this.subquery.equals(that.subquery)) + } + if (!this.join.equals(that.join)) { return false; + } } return true; @@ -277,12 +361,19 @@ public int hashCode() { int hashCode = 1; hashCode = hashCode * 8191 + ((isSetTableName()) ? 131071 : 524287); - if (isSetTableName()) + if (isSetTableName()) { hashCode = hashCode * 8191 + tableName.hashCode(); + } hashCode = hashCode * 8191 + ((isSetSubquery()) ? 131071 : 524287); - if (isSetSubquery()) + if (isSetSubquery()) { hashCode = hashCode * 8191 + subquery.hashCode(); + } + + hashCode = hashCode * 8191 + ((isSetJoin()) ? 131071 : 524287); + if (isSetJoin()) { + hashCode = hashCode * 8191 + join.hashCode(); + } return hashCode; } @@ -315,19 +406,34 @@ public int compareTo(DataSource other) { return lastComparison; } } + lastComparison = java.lang.Boolean.compare(isSetJoin(), other.isSetJoin()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetJoin()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.join, other.join); + if (lastComparison != 0) { + return lastComparison; + } + } return 0; } @org.apache.thrift.annotation.Nullable + @Override public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } - public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot) + throws org.apache.thrift.TException { scheme(iprot).read(iprot, this); } - public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot) + throws org.apache.thrift.TException { scheme(oprot).write(oprot, this); } @@ -346,7 +452,9 @@ public java.lang.String toString() { first = false; } if (isSetSubquery()) { - if (!first) sb.append(", "); + if (!first) { + sb.append(", "); + } sb.append("subquery:"); if (this.subquery == null) { sb.append("null"); @@ -355,16 +463,33 @@ public java.lang.String toString() { } first = false; } + if (isSetJoin()) { + if (!first) { + sb.append(", "); + } + sb.append("join:"); + if (this.join == null) { + sb.append("null"); + } else { + sb.append(this.join); + } + first = false; + } sb.append(")"); return sb.toString(); } - public void validate() throws org.apache.thrift.TException { + public void validate() + throws org.apache.thrift.TException { // check for required fields // check for sub-struct validity + if (subquery != null) { + subquery.validate(); + } } - private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + private void writeObject(java.io.ObjectOutputStream out) + throws java.io.IOException { try { write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); } catch (org.apache.thrift.TException te) { @@ -372,7 +497,8 @@ private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOExcept } } - private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, java.lang.ClassNotFoundException { try { read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); } catch (org.apache.thrift.TException te) { @@ -381,6 +507,7 @@ private void readObject(java.io.ObjectInputStream in) throws java.io.IOException } private static class DataSourceStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public DataSourceStandardScheme getScheme() { return new DataSourceStandardScheme(); } @@ -388,13 +515,14 @@ public DataSourceStandardScheme getScheme() { private static class DataSourceStandardScheme extends org.apache.thrift.scheme.StandardScheme { - public void read(org.apache.thrift.protocol.TProtocol iprot, DataSource struct) throws org.apache.thrift.TException { + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot, DataSource struct) + throws org.apache.thrift.TException { org.apache.thrift.protocol.TField schemeField; iprot.readStructBegin(); - while (true) - { + while (true) { schemeField = iprot.readFieldBegin(); - if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { break; } switch (schemeField.id) { @@ -402,7 +530,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, DataSource struct) if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { struct.tableName = iprot.readString(); struct.setTableNameIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -411,7 +539,16 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, DataSource struct) struct.subquery = new PinotQuery(); struct.subquery.read(iprot); struct.setSubqueryIsSet(true); - } else { + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 3: // JOIN + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.join = new Join(); + struct.join.read(iprot); + struct.setJoinIsSet(true); + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -426,7 +563,9 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, DataSource struct) struct.validate(); } - public void write(org.apache.thrift.protocol.TProtocol oprot, DataSource struct) throws org.apache.thrift.TException { + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot, DataSource struct) + throws org.apache.thrift.TException { struct.validate(); oprot.writeStructBegin(STRUCT_DESC); @@ -444,13 +583,20 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, DataSource struct) oprot.writeFieldEnd(); } } + if (struct.join != null) { + if (struct.isSetJoin()) { + oprot.writeFieldBegin(JOIN_FIELD_DESC); + struct.join.write(oprot); + oprot.writeFieldEnd(); + } + } oprot.writeFieldStop(); oprot.writeStructEnd(); } - } private static class DataSourceTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public DataSourceTupleScheme getScheme() { return new DataSourceTupleScheme(); } @@ -459,7 +605,8 @@ public DataSourceTupleScheme getScheme() { private static class DataSourceTupleScheme extends org.apache.thrift.scheme.TupleScheme { @Override - public void write(org.apache.thrift.protocol.TProtocol prot, DataSource struct) throws org.apache.thrift.TException { + public void write(org.apache.thrift.protocol.TProtocol prot, DataSource struct) + throws org.apache.thrift.TException { org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; java.util.BitSet optionals = new java.util.BitSet(); if (struct.isSetTableName()) { @@ -468,19 +615,26 @@ public void write(org.apache.thrift.protocol.TProtocol prot, DataSource struct) if (struct.isSetSubquery()) { optionals.set(1); } - oprot.writeBitSet(optionals, 2); + if (struct.isSetJoin()) { + optionals.set(2); + } + oprot.writeBitSet(optionals, 3); if (struct.isSetTableName()) { oprot.writeString(struct.tableName); } if (struct.isSetSubquery()) { struct.subquery.write(oprot); } + if (struct.isSetJoin()) { + struct.join.write(oprot); + } } @Override - public void read(org.apache.thrift.protocol.TProtocol prot, DataSource struct) throws org.apache.thrift.TException { + public void read(org.apache.thrift.protocol.TProtocol prot, DataSource struct) + throws org.apache.thrift.TException { org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; - java.util.BitSet incoming = iprot.readBitSet(2); + java.util.BitSet incoming = iprot.readBitSet(3); if (incoming.get(0)) { struct.tableName = iprot.readString(); struct.setTableNameIsSet(true); @@ -490,11 +644,17 @@ public void read(org.apache.thrift.protocol.TProtocol prot, DataSource struct) t struct.subquery.read(iprot); struct.setSubqueryIsSet(true); } + if (incoming.get(2)) { + struct.join = new Join(); + struct.join.read(iprot); + struct.setJoinIsSet(true); + } } } private static S scheme(org.apache.thrift.protocol.TProtocol proto) { - return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY + : TUPLE_SCHEME_FACTORY).getScheme(); } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java index e1f5dea0c88..219dbc5b87a 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Autogenerated by Thrift Compiler (0.15.0) + * Autogenerated by Thrift Compiler (0.17.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.17.0)", date = "2023-02-08") public class Expression implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Expression"); @@ -38,7 +38,7 @@ public class Expression implements org.apache.thrift.TBase metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); - tmpMap.put(_Fields.TYPE, new org.apache.thrift.meta_data.FieldMetaData("type", org.apache.thrift.TFieldRequirementType.REQUIRED, + tmpMap.put(_Fields.TYPE, new org.apache.thrift.meta_data.FieldMetaData("type", org.apache.thrift.TFieldRequirementType.REQUIRED, new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, ExpressionType.class))); - tmpMap.put(_Fields.FUNCTION_CALL, new org.apache.thrift.meta_data.FieldMetaData("functionCall", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Function"))); - tmpMap.put(_Fields.LITERAL, new org.apache.thrift.meta_data.FieldMetaData("literal", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Literal"))); - tmpMap.put(_Fields.IDENTIFIER, new org.apache.thrift.meta_data.FieldMetaData("identifier", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Identifier"))); + tmpMap.put(_Fields.FUNCTION_CALL, new org.apache.thrift.meta_data.FieldMetaData("functionCall", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Function.class))); + tmpMap.put(_Fields.LITERAL, new org.apache.thrift.meta_data.FieldMetaData("literal", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Literal.class))); + tmpMap.put(_Fields.IDENTIFIER, new org.apache.thrift.meta_data.FieldMetaData("identifier", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Identifier.class))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Expression.class, metaDataMap); } @@ -140,7 +142,7 @@ public Expression() { } public Expression( - ExpressionType type) + ExpressionType type) { this(); this.type = type; @@ -164,6 +166,7 @@ public Expression(Expression other) { } } + @Override public Expression deepCopy() { return new Expression(this); } @@ -177,7 +180,7 @@ public void clear() { } /** - * + * * @see ExpressionType */ @org.apache.thrift.annotation.Nullable @@ -186,7 +189,7 @@ public ExpressionType getType() { } /** - * + * * @see ExpressionType */ public Expression setType(@org.apache.thrift.annotation.Nullable ExpressionType type) { @@ -284,77 +287,80 @@ public void setIdentifierIsSet(boolean value) { } } + @Override public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { switch (field) { - case TYPE: - if (value == null) { - unsetType(); - } else { - setType((ExpressionType)value); - } - break; + case TYPE: + if (value == null) { + unsetType(); + } else { + setType((ExpressionType)value); + } + break; - case FUNCTION_CALL: - if (value == null) { - unsetFunctionCall(); - } else { - setFunctionCall((Function)value); - } - break; + case FUNCTION_CALL: + if (value == null) { + unsetFunctionCall(); + } else { + setFunctionCall((Function)value); + } + break; - case LITERAL: - if (value == null) { - unsetLiteral(); - } else { - setLiteral((Literal)value); - } - break; + case LITERAL: + if (value == null) { + unsetLiteral(); + } else { + setLiteral((Literal)value); + } + break; - case IDENTIFIER: - if (value == null) { - unsetIdentifier(); - } else { - setIdentifier((Identifier)value); - } - break; + case IDENTIFIER: + if (value == null) { + unsetIdentifier(); + } else { + setIdentifier((Identifier)value); + } + break; } } @org.apache.thrift.annotation.Nullable + @Override public java.lang.Object getFieldValue(_Fields field) { switch (field) { - case TYPE: - return getType(); + case TYPE: + return getType(); - case FUNCTION_CALL: - return getFunctionCall(); + case FUNCTION_CALL: + return getFunctionCall(); - case LITERAL: - return getLiteral(); + case LITERAL: + return getLiteral(); - case IDENTIFIER: - return getIdentifier(); + case IDENTIFIER: + return getIdentifier(); } throw new java.lang.IllegalStateException(); } /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override public boolean isSet(_Fields field) { if (field == null) { throw new java.lang.IllegalArgumentException(); } switch (field) { - case TYPE: - return isSetType(); - case FUNCTION_CALL: - return isSetFunctionCall(); - case LITERAL: - return isSetLiteral(); - case IDENTIFIER: - return isSetIdentifier(); + case TYPE: + return isSetType(); + case FUNCTION_CALL: + return isSetFunctionCall(); + case LITERAL: + return isSetLiteral(); + case IDENTIFIER: + return isSetIdentifier(); } throw new java.lang.IllegalStateException(); } @@ -486,14 +492,17 @@ public int compareTo(Expression other) { } @org.apache.thrift.annotation.Nullable + @Override public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } + @Override public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { scheme(iprot).read(iprot, this); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { scheme(oprot).write(oprot, this); } @@ -550,6 +559,12 @@ public void validate() throws org.apache.thrift.TException { throw new org.apache.thrift.protocol.TProtocolException("Required field 'type' was not present! Struct: " + toString()); } // check for sub-struct validity + if (functionCall != null) { + functionCall.validate(); + } + if (identifier != null) { + identifier.validate(); + } } private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { @@ -569,6 +584,7 @@ private void readObject(java.io.ObjectInputStream in) throws java.io.IOException } private static class ExpressionStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public ExpressionStandardScheme getScheme() { return new ExpressionStandardScheme(); } @@ -576,13 +592,14 @@ public ExpressionStandardScheme getScheme() { private static class ExpressionStandardScheme extends org.apache.thrift.scheme.StandardScheme { + @Override public void read(org.apache.thrift.protocol.TProtocol iprot, Expression struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TField schemeField; iprot.readStructBegin(); while (true) { schemeField = iprot.readFieldBegin(); - if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { break; } switch (schemeField.id) { @@ -590,7 +607,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Expression struct) if (schemeField.type == org.apache.thrift.protocol.TType.I32) { struct.type = org.apache.pinot.common.request.ExpressionType.findByValue(iprot.readI32()); struct.setTypeIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -599,7 +616,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Expression struct) struct.functionCall = new Function(); struct.functionCall.read(iprot); struct.setFunctionCallIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -608,7 +625,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Expression struct) struct.literal = new Literal(); struct.literal.read(iprot); struct.setLiteralIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -617,7 +634,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Expression struct) struct.identifier = new Identifier(); struct.identifier.read(iprot); struct.setIdentifierIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -632,6 +649,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Expression struct) struct.validate(); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot, Expression struct) throws org.apache.thrift.TException { struct.validate(); @@ -669,6 +687,7 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, Expression struct) } private static class ExpressionTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public ExpressionTupleScheme getScheme() { return new ExpressionTupleScheme(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java b/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java index 9e1d9823903..8822fbcbff5 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Autogenerated by Thrift Compiler (0.15.0) + * Autogenerated by Thrift Compiler (0.17.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.17.0)", date = "2023-02-08") public enum ExpressionType implements org.apache.thrift.TEnum { LITERAL(0), IDENTIFIER(1), @@ -40,6 +40,7 @@ private ExpressionType(int value) { /** * Get the integer value of this enum value, as defined in the Thrift IDL. */ + @Override public int getValue() { return value; } @@ -49,7 +50,7 @@ public int getValue() { * @return null if the value is not found. */ @org.apache.thrift.annotation.Nullable - public static ExpressionType findByValue(int value) { + public static ExpressionType findByValue(int value) { switch (value) { case 0: return LITERAL; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java index eaec734de15..12a8b8b295a 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Autogenerated by Thrift Compiler (0.15.0) + * Autogenerated by Thrift Compiler (0.17.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.17.0)", date = "2023-02-08") public class Function implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Function"); @@ -92,10 +92,12 @@ public static _Fields findByName(java.lang.String name) { _fieldName = fieldName; } + @Override public short getThriftFieldId() { return _thriftId; } + @Override public java.lang.String getFieldName() { return _fieldName; } @@ -106,10 +108,10 @@ public java.lang.String getFieldName() { public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); - tmpMap.put(_Fields.OPERATOR, new org.apache.thrift.meta_data.FieldMetaData("operator", org.apache.thrift.TFieldRequirementType.REQUIRED, + tmpMap.put(_Fields.OPERATOR, new org.apache.thrift.meta_data.FieldMetaData("operator", org.apache.thrift.TFieldRequirementType.REQUIRED, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); - tmpMap.put(_Fields.OPERANDS, new org.apache.thrift.meta_data.FieldMetaData("operands", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, + tmpMap.put(_Fields.OPERANDS, new org.apache.thrift.meta_data.FieldMetaData("operands", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class)))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Function.class, metaDataMap); @@ -119,7 +121,7 @@ public Function() { } public Function( - java.lang.String operator) + java.lang.String operator) { this(); this.operator = operator; @@ -141,6 +143,7 @@ public Function(Function other) { } } + @Override public Function deepCopy() { return new Function(this); } @@ -217,51 +220,54 @@ public void setOperandsIsSet(boolean value) { } } + @Override public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { switch (field) { - case OPERATOR: - if (value == null) { - unsetOperator(); - } else { - setOperator((java.lang.String)value); - } - break; + case OPERATOR: + if (value == null) { + unsetOperator(); + } else { + setOperator((java.lang.String)value); + } + break; - case OPERANDS: - if (value == null) { - unsetOperands(); - } else { - setOperands((java.util.List)value); - } - break; + case OPERANDS: + if (value == null) { + unsetOperands(); + } else { + setOperands((java.util.List)value); + } + break; } } @org.apache.thrift.annotation.Nullable + @Override public java.lang.Object getFieldValue(_Fields field) { switch (field) { - case OPERATOR: - return getOperator(); + case OPERATOR: + return getOperator(); - case OPERANDS: - return getOperands(); + case OPERANDS: + return getOperands(); } throw new java.lang.IllegalStateException(); } /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override public boolean isSet(_Fields field) { if (field == null) { throw new java.lang.IllegalArgumentException(); } switch (field) { - case OPERATOR: - return isSetOperator(); - case OPERANDS: - return isSetOperands(); + case OPERATOR: + return isSetOperator(); + case OPERANDS: + return isSetOperands(); } throw new java.lang.IllegalStateException(); } @@ -347,14 +353,17 @@ public int compareTo(Function other) { } @org.apache.thrift.annotation.Nullable + @Override public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } + @Override public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { scheme(iprot).read(iprot, this); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { scheme(oprot).write(oprot, this); } @@ -410,6 +419,7 @@ private void readObject(java.io.ObjectInputStream in) throws java.io.IOException } private static class FunctionStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public FunctionStandardScheme getScheme() { return new FunctionStandardScheme(); } @@ -417,13 +427,14 @@ public FunctionStandardScheme getScheme() { private static class FunctionStandardScheme extends org.apache.thrift.scheme.StandardScheme { + @Override public void read(org.apache.thrift.protocol.TProtocol iprot, Function struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TField schemeField; iprot.readStructBegin(); while (true) { schemeField = iprot.readFieldBegin(); - if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { break; } switch (schemeField.id) { @@ -431,7 +442,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Function struct) th if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { struct.operator = iprot.readString(); struct.setOperatorIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -450,7 +461,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Function struct) th iprot.readListEnd(); } struct.setOperandsIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -465,6 +476,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Function struct) th struct.validate(); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot, Function struct) throws org.apache.thrift.TException { struct.validate(); @@ -495,6 +507,7 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, Function struct) t } private static class FunctionTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public FunctionTupleScheme getScheme() { return new FunctionTupleScheme(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java index 3387d144871..307e127f20e 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Autogenerated by Thrift Compiler (0.15.0) + * Autogenerated by Thrift Compiler (0.17.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.17.0)", date = "2023-02-08") public class Identifier implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Identifier"); @@ -87,10 +87,12 @@ public static _Fields findByName(java.lang.String name) { _fieldName = fieldName; } + @Override public short getThriftFieldId() { return _thriftId; } + @Override public java.lang.String getFieldName() { return _fieldName; } @@ -100,7 +102,7 @@ public java.lang.String getFieldName() { public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); - tmpMap.put(_Fields.NAME, new org.apache.thrift.meta_data.FieldMetaData("name", org.apache.thrift.TFieldRequirementType.REQUIRED, + tmpMap.put(_Fields.NAME, new org.apache.thrift.meta_data.FieldMetaData("name", org.apache.thrift.TFieldRequirementType.REQUIRED, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Identifier.class, metaDataMap); @@ -110,7 +112,7 @@ public Identifier() { } public Identifier( - java.lang.String name) + java.lang.String name) { this(); this.name = name; @@ -125,6 +127,7 @@ public Identifier(Identifier other) { } } + @Override public Identifier deepCopy() { return new Identifier(this); } @@ -159,38 +162,41 @@ public void setNameIsSet(boolean value) { } } + @Override public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { switch (field) { - case NAME: - if (value == null) { - unsetName(); - } else { - setName((java.lang.String)value); - } - break; + case NAME: + if (value == null) { + unsetName(); + } else { + setName((java.lang.String)value); + } + break; } } @org.apache.thrift.annotation.Nullable + @Override public java.lang.Object getFieldValue(_Fields field) { switch (field) { - case NAME: - return getName(); + case NAME: + return getName(); } throw new java.lang.IllegalStateException(); } /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override public boolean isSet(_Fields field) { if (field == null) { throw new java.lang.IllegalArgumentException(); } switch (field) { - case NAME: - return isSetName(); + case NAME: + return isSetName(); } throw new java.lang.IllegalStateException(); } @@ -253,14 +259,17 @@ public int compareTo(Identifier other) { } @org.apache.thrift.annotation.Nullable + @Override public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } + @Override public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { scheme(iprot).read(iprot, this); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { scheme(oprot).write(oprot, this); } @@ -306,6 +315,7 @@ private void readObject(java.io.ObjectInputStream in) throws java.io.IOException } private static class IdentifierStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public IdentifierStandardScheme getScheme() { return new IdentifierStandardScheme(); } @@ -313,13 +323,14 @@ public IdentifierStandardScheme getScheme() { private static class IdentifierStandardScheme extends org.apache.thrift.scheme.StandardScheme { + @Override public void read(org.apache.thrift.protocol.TProtocol iprot, Identifier struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TField schemeField; iprot.readStructBegin(); while (true) { schemeField = iprot.readFieldBegin(); - if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { break; } switch (schemeField.id) { @@ -327,7 +338,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Identifier struct) if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { struct.name = iprot.readString(); struct.setNameIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -342,6 +353,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Identifier struct) struct.validate(); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot, Identifier struct) throws org.apache.thrift.TException { struct.validate(); @@ -358,6 +370,7 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, Identifier struct) } private static class IdentifierTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public IdentifierTupleScheme getScheme() { return new IdentifierTupleScheme(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java new file mode 100644 index 00000000000..b618ad40477 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java @@ -0,0 +1,761 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Autogenerated by Thrift Compiler (0.15.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.pinot.common.request; + +@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-03-14") +public class Join + implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Join"); + + private static final org.apache.thrift.protocol.TField TYPE_FIELD_DESC = + new org.apache.thrift.protocol.TField("type", org.apache.thrift.protocol.TType.I32, (short) 1); + private static final org.apache.thrift.protocol.TField LEFT_FIELD_DESC = + new org.apache.thrift.protocol.TField("left", org.apache.thrift.protocol.TType.STRUCT, (short) 2); + private static final org.apache.thrift.protocol.TField RIGHT_FIELD_DESC = + new org.apache.thrift.protocol.TField("right", org.apache.thrift.protocol.TType.STRUCT, (short) 3); + private static final org.apache.thrift.protocol.TField CONDITION_FIELD_DESC = + new org.apache.thrift.protocol.TField("condition", org.apache.thrift.protocol.TType.STRUCT, (short) 4); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new JoinStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new JoinTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable JoinType type; // required + public @org.apache.thrift.annotation.Nullable DataSource left; // required + public @org.apache.thrift.annotation.Nullable DataSource right; // required + public @org.apache.thrift.annotation.Nullable Expression condition; // optional + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + TYPE((short) 1, "type"), LEFT((short) 2, "left"), RIGHT((short) 3, "right"), CONDITION((short) 4, "condition"); + + private static final java.util.Map byName = + new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch (fieldId) { + case 1: // TYPE + return TYPE; + case 2: // LEFT + return LEFT; + case 3: // RIGHT + return RIGHT; + case 4: // CONDITION + return CONDITION; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) { + throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + } + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + @Override + public short getThriftFieldId() { + return _thriftId; + } + + @Override + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + private static final _Fields optionals[] = {_Fields.CONDITION}; + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = + new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.TYPE, + new org.apache.thrift.meta_data.FieldMetaData("type", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.ENUM, "JoinType"))); + tmpMap.put(_Fields.LEFT, + new org.apache.thrift.meta_data.FieldMetaData("left", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, DataSource.class))); + tmpMap.put(_Fields.RIGHT, + new org.apache.thrift.meta_data.FieldMetaData("right", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, DataSource.class))); + tmpMap.put(_Fields.CONDITION, + new org.apache.thrift.meta_data.FieldMetaData("condition", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT, "Expression"))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Join.class, metaDataMap); + } + + public Join() { + } + + public Join(JoinType type, DataSource left, DataSource right) { + this(); + this.type = type; + this.left = left; + this.right = right; + } + + /** + * Performs a deep copy on other. + */ + public Join(Join other) { + if (other.isSetType()) { + this.type = other.type; + } + if (other.isSetLeft()) { + this.left = new DataSource(other.left); + } + if (other.isSetRight()) { + this.right = new DataSource(other.right); + } + if (other.isSetCondition()) { + this.condition = new Expression(other.condition); + } + } + + @Override + public Join deepCopy() { + return new Join(this); + } + + @Override + public void clear() { + this.type = null; + this.left = null; + this.right = null; + this.condition = null; + } + + @org.apache.thrift.annotation.Nullable + public JoinType getType() { + return this.type; + } + + public Join setType(@org.apache.thrift.annotation.Nullable JoinType type) { + this.type = type; + return this; + } + + public void unsetType() { + this.type = null; + } + + /** Returns true if field type is set (has been assigned a value) and false otherwise */ + public boolean isSetType() { + return this.type != null; + } + + public void setTypeIsSet(boolean value) { + if (!value) { + this.type = null; + } + } + + @org.apache.thrift.annotation.Nullable + public DataSource getLeft() { + return this.left; + } + + public Join setLeft(@org.apache.thrift.annotation.Nullable DataSource left) { + this.left = left; + return this; + } + + public void unsetLeft() { + this.left = null; + } + + /** Returns true if field left is set (has been assigned a value) and false otherwise */ + public boolean isSetLeft() { + return this.left != null; + } + + public void setLeftIsSet(boolean value) { + if (!value) { + this.left = null; + } + } + + @org.apache.thrift.annotation.Nullable + public DataSource getRight() { + return this.right; + } + + public Join setRight(@org.apache.thrift.annotation.Nullable DataSource right) { + this.right = right; + return this; + } + + public void unsetRight() { + this.right = null; + } + + /** Returns true if field right is set (has been assigned a value) and false otherwise */ + public boolean isSetRight() { + return this.right != null; + } + + public void setRightIsSet(boolean value) { + if (!value) { + this.right = null; + } + } + + @org.apache.thrift.annotation.Nullable + public Expression getCondition() { + return this.condition; + } + + public Join setCondition(@org.apache.thrift.annotation.Nullable Expression condition) { + this.condition = condition; + return this; + } + + public void unsetCondition() { + this.condition = null; + } + + /** Returns true if field condition is set (has been assigned a value) and false otherwise */ + public boolean isSetCondition() { + return this.condition != null; + } + + public void setConditionIsSet(boolean value) { + if (!value) { + this.condition = null; + } + } + + @Override + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case TYPE: + if (value == null) { + unsetType(); + } else { + setType((JoinType) value); + } + break; + + case LEFT: + if (value == null) { + unsetLeft(); + } else { + setLeft((DataSource) value); + } + break; + + case RIGHT: + if (value == null) { + unsetRight(); + } else { + setRight((DataSource) value); + } + break; + + case CONDITION: + if (value == null) { + unsetCondition(); + } else { + setCondition((Expression) value); + } + break; + } + } + + @org.apache.thrift.annotation.Nullable + @Override + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case TYPE: + return getType(); + + case LEFT: + return getLeft(); + + case RIGHT: + return getRight(); + + case CONDITION: + return getCondition(); + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case TYPE: + return isSetType(); + case LEFT: + return isSetLeft(); + case RIGHT: + return isSetRight(); + case CONDITION: + return isSetCondition(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof Join) { + return this.equals((Join) that); + } + return false; + } + + public boolean equals(Join that) { + if (that == null) { + return false; + } + if (this == that) { + return true; + } + + boolean this_present_type = true && this.isSetType(); + boolean that_present_type = true && that.isSetType(); + if (this_present_type || that_present_type) { + if (!(this_present_type && that_present_type)) { + return false; + } + if (!this.type.equals(that.type)) { + return false; + } + } + + boolean this_present_left = true && this.isSetLeft(); + boolean that_present_left = true && that.isSetLeft(); + if (this_present_left || that_present_left) { + if (!(this_present_left && that_present_left)) { + return false; + } + if (!this.left.equals(that.left)) { + return false; + } + } + + boolean this_present_right = true && this.isSetRight(); + boolean that_present_right = true && that.isSetRight(); + if (this_present_right || that_present_right) { + if (!(this_present_right && that_present_right)) { + return false; + } + if (!this.right.equals(that.right)) { + return false; + } + } + + boolean this_present_condition = true && this.isSetCondition(); + boolean that_present_condition = true && that.isSetCondition(); + if (this_present_condition || that_present_condition) { + if (!(this_present_condition && that_present_condition)) { + return false; + } + if (!this.condition.equals(that.condition)) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetType()) ? 131071 : 524287); + if (isSetType()) { + hashCode = hashCode * 8191 + type.getValue(); + } + + hashCode = hashCode * 8191 + ((isSetLeft()) ? 131071 : 524287); + if (isSetLeft()) { + hashCode = hashCode * 8191 + left.hashCode(); + } + + hashCode = hashCode * 8191 + ((isSetRight()) ? 131071 : 524287); + if (isSetRight()) { + hashCode = hashCode * 8191 + right.hashCode(); + } + + hashCode = hashCode * 8191 + ((isSetCondition()) ? 131071 : 524287); + if (isSetCondition()) { + hashCode = hashCode * 8191 + condition.hashCode(); + } + + return hashCode; + } + + @Override + public int compareTo(Join other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetType(), other.isSetType()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetType()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.type, other.type); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetLeft(), other.isSetLeft()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetLeft()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.left, other.left); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetRight(), other.isSetRight()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetRight()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.right, other.right); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetCondition(), other.isSetCondition()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetCondition()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.condition, other.condition); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + @Override + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot) + throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot) + throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("Join("); + boolean first = true; + + sb.append("type:"); + if (this.type == null) { + sb.append("null"); + } else { + sb.append(this.type); + } + first = false; + if (!first) { + sb.append(", "); + } + sb.append("left:"); + if (this.left == null) { + sb.append("null"); + } else { + sb.append(this.left); + } + first = false; + if (!first) { + sb.append(", "); + } + sb.append("right:"); + if (this.right == null) { + sb.append("null"); + } else { + sb.append(this.right); + } + first = false; + if (isSetCondition()) { + if (!first) { + sb.append(", "); + } + sb.append("condition:"); + if (this.condition == null) { + sb.append("null"); + } else { + sb.append(this.condition); + } + first = false; + } + sb.append(")"); + return sb.toString(); + } + + public void validate() + throws org.apache.thrift.TException { + // check for required fields + if (type == null) { + throw new org.apache.thrift.protocol.TProtocolException( + "Required field 'type' was not present! Struct: " + toString()); + } + if (left == null) { + throw new org.apache.thrift.protocol.TProtocolException( + "Required field 'left' was not present! Struct: " + toString()); + } + if (right == null) { + throw new org.apache.thrift.protocol.TProtocolException( + "Required field 'right' was not present! Struct: " + toString()); + } + // check for sub-struct validity + if (left != null) { + left.validate(); + } + if (right != null) { + right.validate(); + } + } + + private void writeObject(java.io.ObjectOutputStream out) + throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class JoinStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public JoinStandardScheme getScheme() { + return new JoinStandardScheme(); + } + } + + private static class JoinStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot, Join struct) + throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // TYPE + if (schemeField.type == org.apache.thrift.protocol.TType.I32) { + struct.type = org.apache.pinot.common.request.JoinType.findByValue(iprot.readI32()); + struct.setTypeIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // LEFT + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.left = new DataSource(); + struct.left.read(iprot); + struct.setLeftIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 3: // RIGHT + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.right = new DataSource(); + struct.right.read(iprot); + struct.setRightIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 4: // CONDITION + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.condition = new Expression(); + struct.condition.read(iprot); + struct.setConditionIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot, Join struct) + throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.type != null) { + oprot.writeFieldBegin(TYPE_FIELD_DESC); + oprot.writeI32(struct.type.getValue()); + oprot.writeFieldEnd(); + } + if (struct.left != null) { + oprot.writeFieldBegin(LEFT_FIELD_DESC); + struct.left.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.right != null) { + oprot.writeFieldBegin(RIGHT_FIELD_DESC); + struct.right.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.condition != null) { + if (struct.isSetCondition()) { + oprot.writeFieldBegin(CONDITION_FIELD_DESC); + struct.condition.write(oprot); + oprot.writeFieldEnd(); + } + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + } + + private static class JoinTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public JoinTupleScheme getScheme() { + return new JoinTupleScheme(); + } + } + + private static class JoinTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, Join struct) + throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + oprot.writeI32(struct.type.getValue()); + struct.left.write(oprot); + struct.right.write(oprot); + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetCondition()) { + optionals.set(0); + } + oprot.writeBitSet(optionals, 1); + if (struct.isSetCondition()) { + struct.condition.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, Join struct) + throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.type = org.apache.pinot.common.request.JoinType.findByValue(iprot.readI32()); + struct.setTypeIsSet(true); + struct.left = new DataSource(); + struct.left.read(iprot); + struct.setLeftIsSet(true); + struct.right = new DataSource(); + struct.right.read(iprot); + struct.setRightIsSet(true); + java.util.BitSet incoming = iprot.readBitSet(1); + if (incoming.get(0)) { + struct.condition = new Expression(); + struct.condition.read(iprot); + struct.setConditionIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY + : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java b/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java new file mode 100644 index 00000000000..08efcf21bbc --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Autogenerated by Thrift Compiler (0.15.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.pinot.common.request; + + +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-03-14") +public enum JoinType implements org.apache.thrift.TEnum { + INNER(0), + LEFT(1), + RIGHT(2), + FULL(3); + + private final int value; + + private JoinType(int value) { + this.value = value; + } + + /** + * Get the integer value of this enum value, as defined in the Thrift IDL. + */ + public int getValue() { + return value; + } + + /** + * Find a the enum type by its integer value, as defined in the Thrift IDL. + * @return null if the value is not found. + */ + @org.apache.thrift.annotation.Nullable + public static JoinType findByValue(int value) { + switch (value) { + case 0: + return INNER; + case 1: + return LEFT; + case 2: + return RIGHT; + case 3: + return FULL; + default: + return null; + } + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java index 6378917b700..fcf523187ab 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Autogenerated by Thrift Compiler (0.15.0) + * Autogenerated by Thrift Compiler (0.17.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.17.0)", date = "2023-02-08") public class Literal extends org.apache.thrift.TUnion { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Literal"); private static final org.apache.thrift.protocol.TField BOOL_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("boolValue", org.apache.thrift.protocol.TType.BOOL, (short)1); @@ -36,6 +36,7 @@ public class Literal extends org.apache.thrift.TUnion private static final org.apache.thrift.protocol.TField DOUBLE_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("doubleValue", org.apache.thrift.protocol.TType.DOUBLE, (short)6); private static final org.apache.thrift.protocol.TField STRING_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("stringValue", org.apache.thrift.protocol.TType.STRING, (short)7); private static final org.apache.thrift.protocol.TField BINARY_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("binaryValue", org.apache.thrift.protocol.TType.STRING, (short)8); + private static final org.apache.thrift.protocol.TField NULL_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("nullValue", org.apache.thrift.protocol.TType.BOOL, (short)9); /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ public enum _Fields implements org.apache.thrift.TFieldIdEnum { @@ -46,7 +47,8 @@ public enum _Fields implements org.apache.thrift.TFieldIdEnum { LONG_VALUE((short)5, "longValue"), DOUBLE_VALUE((short)6, "doubleValue"), STRING_VALUE((short)7, "stringValue"), - BINARY_VALUE((short)8, "binaryValue"); + BINARY_VALUE((short)8, "binaryValue"), + NULL_VALUE((short)9, "nullValue"); private static final java.util.Map byName = new java.util.HashMap(); @@ -78,6 +80,8 @@ public static _Fields findByThriftId(int fieldId) { return STRING_VALUE; case 8: // BINARY_VALUE return BINARY_VALUE; + case 9: // NULL_VALUE + return NULL_VALUE; default: return null; } @@ -109,10 +113,12 @@ public static _Fields findByName(java.lang.String name) { _fieldName = fieldName; } + @Override public short getThriftFieldId() { return _thriftId; } + @Override public java.lang.String getFieldName() { return _fieldName; } @@ -121,22 +127,24 @@ public java.lang.String getFieldName() { public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); - tmpMap.put(_Fields.BOOL_VALUE, new org.apache.thrift.meta_data.FieldMetaData("boolValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.BOOL_VALUE, new org.apache.thrift.meta_data.FieldMetaData("boolValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL))); - tmpMap.put(_Fields.BYTE_VALUE, new org.apache.thrift.meta_data.FieldMetaData("byteValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.BYTE_VALUE, new org.apache.thrift.meta_data.FieldMetaData("byteValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BYTE))); - tmpMap.put(_Fields.SHORT_VALUE, new org.apache.thrift.meta_data.FieldMetaData("shortValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.SHORT_VALUE, new org.apache.thrift.meta_data.FieldMetaData("shortValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I16))); - tmpMap.put(_Fields.INT_VALUE, new org.apache.thrift.meta_data.FieldMetaData("intValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.INT_VALUE, new org.apache.thrift.meta_data.FieldMetaData("intValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); - tmpMap.put(_Fields.LONG_VALUE, new org.apache.thrift.meta_data.FieldMetaData("longValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.LONG_VALUE, new org.apache.thrift.meta_data.FieldMetaData("longValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64))); - tmpMap.put(_Fields.DOUBLE_VALUE, new org.apache.thrift.meta_data.FieldMetaData("doubleValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.DOUBLE_VALUE, new org.apache.thrift.meta_data.FieldMetaData("doubleValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.DOUBLE))); - tmpMap.put(_Fields.STRING_VALUE, new org.apache.thrift.meta_data.FieldMetaData("stringValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.STRING_VALUE, new org.apache.thrift.meta_data.FieldMetaData("stringValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); - tmpMap.put(_Fields.BINARY_VALUE, new org.apache.thrift.meta_data.FieldMetaData("binaryValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.BINARY_VALUE, new org.apache.thrift.meta_data.FieldMetaData("binaryValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING , true))); + tmpMap.put(_Fields.NULL_VALUE, new org.apache.thrift.meta_data.FieldMetaData("nullValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Literal.class, metaDataMap); } @@ -152,6 +160,7 @@ public Literal(_Fields setField, java.lang.Object value) { public Literal(Literal other) { super(other); } + @Override public Literal deepCopy() { return new Literal(this); } @@ -210,6 +219,12 @@ public static Literal binaryValue(byte[] value) { return x; } + public static Literal nullValue(boolean value) { + Literal x = new Literal(); + x.setNullValue(value); + return x; + } + @Override protected void checkType(_Fields setField, java.lang.Object value) throws java.lang.ClassCastException { @@ -254,6 +269,11 @@ protected void checkType(_Fields setField, java.lang.Object value) throws java.l break; } throw new java.lang.ClassCastException("Was expecting value of type java.nio.ByteBuffer for field 'binaryValue', but got " + value.getClass().getSimpleName()); + case NULL_VALUE: + if (value instanceof java.lang.Boolean) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type java.lang.Boolean for field 'nullValue', but got " + value.getClass().getSimpleName()); default: throw new java.lang.IllegalArgumentException("Unknown field id " + setField); } @@ -336,6 +356,15 @@ protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TP org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); return null; } + case NULL_VALUE: + if (field.type == NULL_VALUE_FIELD_DESC.type) { + java.lang.Boolean nullValue; + nullValue = iprot.readBool(); + return nullValue; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } default: throw new java.lang.IllegalStateException("setField wasn't null, but didn't match any of the case statements!"); } @@ -380,6 +409,10 @@ protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol opr java.nio.ByteBuffer binaryValue = (java.nio.ByteBuffer)value_; oprot.writeBinary(binaryValue); return; + case NULL_VALUE: + java.lang.Boolean nullValue = (java.lang.Boolean)value_; + oprot.writeBool(nullValue); + return; default: throw new java.lang.IllegalStateException("Cannot write union with unknown field " + setField_); } @@ -422,6 +455,10 @@ protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProt java.nio.ByteBuffer binaryValue; binaryValue = iprot.readBinary(); return binaryValue; + case NULL_VALUE: + java.lang.Boolean nullValue; + nullValue = iprot.readBool(); + return nullValue; default: throw new java.lang.IllegalStateException("setField wasn't null, but didn't match any of the case statements!"); } @@ -465,6 +502,10 @@ protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) java.nio.ByteBuffer binaryValue = (java.nio.ByteBuffer)value_; oprot.writeBinary(binaryValue); return; + case NULL_VALUE: + java.lang.Boolean nullValue = (java.lang.Boolean)value_; + oprot.writeBool(nullValue); + return; default: throw new java.lang.IllegalStateException("Cannot write union with unknown field " + setField_); } @@ -489,6 +530,8 @@ protected org.apache.thrift.protocol.TField getFieldDesc(_Fields setField) { return STRING_VALUE_FIELD_DESC; case BINARY_VALUE: return BINARY_VALUE_FIELD_DESC; + case NULL_VALUE: + return NULL_VALUE_FIELD_DESC; default: throw new java.lang.IllegalArgumentException("Unknown field id " + setField); } @@ -505,6 +548,7 @@ protected _Fields enumForId(short id) { } @org.apache.thrift.annotation.Nullable + @Override public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } @@ -624,6 +668,19 @@ public void setBinaryValue(java.nio.ByteBuffer value) { value_ = java.util.Objects.requireNonNull(value,"_Fields.BINARY_VALUE"); } + public boolean getNullValue() { + if (getSetField() == _Fields.NULL_VALUE) { + return (java.lang.Boolean)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'nullValue' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + public void setNullValue(boolean value) { + setField_ = _Fields.NULL_VALUE; + value_ = value; + } + public boolean isSetBoolValue() { return setField_ == _Fields.BOOL_VALUE; } @@ -664,6 +721,11 @@ public boolean isSetBinaryValue() { } + public boolean isSetNullValue() { + return setField_ == _Fields.NULL_VALUE; + } + + public boolean equals(java.lang.Object other) { if (other instanceof Literal) { return equals((Literal)other); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java b/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java index 4dcdf2bebcc..51b75bf6b99 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Autogenerated by Thrift Compiler (0.15.0) + * Autogenerated by Thrift Compiler (0.17.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2022-05-02") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.17.0)", date = "2023-02-08") public class PinotQuery implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("PinotQuery"); @@ -147,10 +147,12 @@ public static _Fields findByName(java.lang.String name) { _fieldName = fieldName; } + @Override public short getThriftFieldId() { return _thriftId; } + @Override public java.lang.String getFieldName() { return _fieldName; } @@ -166,41 +168,41 @@ public java.lang.String getFieldName() { public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); - tmpMap.put(_Fields.VERSION, new org.apache.thrift.meta_data.FieldMetaData("version", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.VERSION, new org.apache.thrift.meta_data.FieldMetaData("version", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); - tmpMap.put(_Fields.DATA_SOURCE, new org.apache.thrift.meta_data.FieldMetaData("dataSource", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.DATA_SOURCE, new org.apache.thrift.meta_data.FieldMetaData("dataSource", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, DataSource.class))); - tmpMap.put(_Fields.SELECT_LIST, new org.apache.thrift.meta_data.FieldMetaData("selectList", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression")))); - tmpMap.put(_Fields.FILTER_EXPRESSION, new org.apache.thrift.meta_data.FieldMetaData("filterExpression", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression"))); - tmpMap.put(_Fields.GROUP_BY_LIST, new org.apache.thrift.meta_data.FieldMetaData("groupByList", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression")))); - tmpMap.put(_Fields.ORDER_BY_LIST, new org.apache.thrift.meta_data.FieldMetaData("orderByList", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression")))); - tmpMap.put(_Fields.HAVING_EXPRESSION, new org.apache.thrift.meta_data.FieldMetaData("havingExpression", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression"))); - tmpMap.put(_Fields.LIMIT, new org.apache.thrift.meta_data.FieldMetaData("limit", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.SELECT_LIST, new org.apache.thrift.meta_data.FieldMetaData("selectList", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class)))); + tmpMap.put(_Fields.FILTER_EXPRESSION, new org.apache.thrift.meta_data.FieldMetaData("filterExpression", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class))); + tmpMap.put(_Fields.GROUP_BY_LIST, new org.apache.thrift.meta_data.FieldMetaData("groupByList", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class)))); + tmpMap.put(_Fields.ORDER_BY_LIST, new org.apache.thrift.meta_data.FieldMetaData("orderByList", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class)))); + tmpMap.put(_Fields.HAVING_EXPRESSION, new org.apache.thrift.meta_data.FieldMetaData("havingExpression", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class))); + tmpMap.put(_Fields.LIMIT, new org.apache.thrift.meta_data.FieldMetaData("limit", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); - tmpMap.put(_Fields.OFFSET, new org.apache.thrift.meta_data.FieldMetaData("offset", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.OFFSET, new org.apache.thrift.meta_data.FieldMetaData("offset", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); - tmpMap.put(_Fields.DEBUG_OPTIONS, new org.apache.thrift.meta_data.FieldMetaData("debugOptions", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), + tmpMap.put(_Fields.DEBUG_OPTIONS, new org.apache.thrift.meta_data.FieldMetaData("debugOptions", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)))); - tmpMap.put(_Fields.QUERY_OPTIONS, new org.apache.thrift.meta_data.FieldMetaData("queryOptions", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), + tmpMap.put(_Fields.QUERY_OPTIONS, new org.apache.thrift.meta_data.FieldMetaData("queryOptions", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)))); - tmpMap.put(_Fields.EXPLAIN, new org.apache.thrift.meta_data.FieldMetaData("explain", org.apache.thrift.TFieldRequirementType.OPTIONAL, + tmpMap.put(_Fields.EXPLAIN, new org.apache.thrift.meta_data.FieldMetaData("explain", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL))); - tmpMap.put(_Fields.EXPRESSION_OVERRIDE_HINTS, new org.apache.thrift.meta_data.FieldMetaData("expressionOverrideHints", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression"), - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRUCT , "Expression")))); + tmpMap.put(_Fields.EXPRESSION_OVERRIDE_HINTS, new org.apache.thrift.meta_data.FieldMetaData("expressionOverrideHints", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class), + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Expression.class)))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(PinotQuery.class, metaDataMap); } @@ -276,6 +278,7 @@ public PinotQuery(PinotQuery other) { } } + @Override public PinotQuery deepCopy() { return new PinotQuery(this); } @@ -699,194 +702,197 @@ public void setExpressionOverrideHintsIsSet(boolean value) { } } + @Override public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { switch (field) { - case VERSION: - if (value == null) { - unsetVersion(); - } else { - setVersion((java.lang.Integer)value); - } - break; + case VERSION: + if (value == null) { + unsetVersion(); + } else { + setVersion((java.lang.Integer)value); + } + break; - case DATA_SOURCE: - if (value == null) { - unsetDataSource(); - } else { - setDataSource((DataSource)value); - } - break; + case DATA_SOURCE: + if (value == null) { + unsetDataSource(); + } else { + setDataSource((DataSource)value); + } + break; - case SELECT_LIST: - if (value == null) { - unsetSelectList(); - } else { - setSelectList((java.util.List)value); - } - break; + case SELECT_LIST: + if (value == null) { + unsetSelectList(); + } else { + setSelectList((java.util.List)value); + } + break; - case FILTER_EXPRESSION: - if (value == null) { - unsetFilterExpression(); - } else { - setFilterExpression((Expression)value); - } - break; + case FILTER_EXPRESSION: + if (value == null) { + unsetFilterExpression(); + } else { + setFilterExpression((Expression)value); + } + break; - case GROUP_BY_LIST: - if (value == null) { - unsetGroupByList(); - } else { - setGroupByList((java.util.List)value); - } - break; + case GROUP_BY_LIST: + if (value == null) { + unsetGroupByList(); + } else { + setGroupByList((java.util.List)value); + } + break; - case ORDER_BY_LIST: - if (value == null) { - unsetOrderByList(); - } else { - setOrderByList((java.util.List)value); - } - break; + case ORDER_BY_LIST: + if (value == null) { + unsetOrderByList(); + } else { + setOrderByList((java.util.List)value); + } + break; - case HAVING_EXPRESSION: - if (value == null) { - unsetHavingExpression(); - } else { - setHavingExpression((Expression)value); - } - break; + case HAVING_EXPRESSION: + if (value == null) { + unsetHavingExpression(); + } else { + setHavingExpression((Expression)value); + } + break; - case LIMIT: - if (value == null) { - unsetLimit(); - } else { - setLimit((java.lang.Integer)value); - } - break; + case LIMIT: + if (value == null) { + unsetLimit(); + } else { + setLimit((java.lang.Integer)value); + } + break; - case OFFSET: - if (value == null) { - unsetOffset(); - } else { - setOffset((java.lang.Integer)value); - } - break; + case OFFSET: + if (value == null) { + unsetOffset(); + } else { + setOffset((java.lang.Integer)value); + } + break; - case DEBUG_OPTIONS: - if (value == null) { - unsetDebugOptions(); - } else { - setDebugOptions((java.util.Map)value); - } - break; + case DEBUG_OPTIONS: + if (value == null) { + unsetDebugOptions(); + } else { + setDebugOptions((java.util.Map)value); + } + break; - case QUERY_OPTIONS: - if (value == null) { - unsetQueryOptions(); - } else { - setQueryOptions((java.util.Map)value); - } - break; + case QUERY_OPTIONS: + if (value == null) { + unsetQueryOptions(); + } else { + setQueryOptions((java.util.Map)value); + } + break; - case EXPLAIN: - if (value == null) { - unsetExplain(); - } else { - setExplain((java.lang.Boolean)value); - } - break; + case EXPLAIN: + if (value == null) { + unsetExplain(); + } else { + setExplain((java.lang.Boolean)value); + } + break; - case EXPRESSION_OVERRIDE_HINTS: - if (value == null) { - unsetExpressionOverrideHints(); - } else { - setExpressionOverrideHints((java.util.Map)value); - } - break; + case EXPRESSION_OVERRIDE_HINTS: + if (value == null) { + unsetExpressionOverrideHints(); + } else { + setExpressionOverrideHints((java.util.Map)value); + } + break; } } @org.apache.thrift.annotation.Nullable + @Override public java.lang.Object getFieldValue(_Fields field) { switch (field) { - case VERSION: - return getVersion(); + case VERSION: + return getVersion(); - case DATA_SOURCE: - return getDataSource(); + case DATA_SOURCE: + return getDataSource(); - case SELECT_LIST: - return getSelectList(); + case SELECT_LIST: + return getSelectList(); - case FILTER_EXPRESSION: - return getFilterExpression(); + case FILTER_EXPRESSION: + return getFilterExpression(); - case GROUP_BY_LIST: - return getGroupByList(); + case GROUP_BY_LIST: + return getGroupByList(); - case ORDER_BY_LIST: - return getOrderByList(); + case ORDER_BY_LIST: + return getOrderByList(); - case HAVING_EXPRESSION: - return getHavingExpression(); + case HAVING_EXPRESSION: + return getHavingExpression(); - case LIMIT: - return getLimit(); + case LIMIT: + return getLimit(); - case OFFSET: - return getOffset(); + case OFFSET: + return getOffset(); - case DEBUG_OPTIONS: - return getDebugOptions(); + case DEBUG_OPTIONS: + return getDebugOptions(); - case QUERY_OPTIONS: - return getQueryOptions(); + case QUERY_OPTIONS: + return getQueryOptions(); - case EXPLAIN: - return isExplain(); + case EXPLAIN: + return isExplain(); - case EXPRESSION_OVERRIDE_HINTS: - return getExpressionOverrideHints(); + case EXPRESSION_OVERRIDE_HINTS: + return getExpressionOverrideHints(); } throw new java.lang.IllegalStateException(); } /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override public boolean isSet(_Fields field) { if (field == null) { throw new java.lang.IllegalArgumentException(); } switch (field) { - case VERSION: - return isSetVersion(); - case DATA_SOURCE: - return isSetDataSource(); - case SELECT_LIST: - return isSetSelectList(); - case FILTER_EXPRESSION: - return isSetFilterExpression(); - case GROUP_BY_LIST: - return isSetGroupByList(); - case ORDER_BY_LIST: - return isSetOrderByList(); - case HAVING_EXPRESSION: - return isSetHavingExpression(); - case LIMIT: - return isSetLimit(); - case OFFSET: - return isSetOffset(); - case DEBUG_OPTIONS: - return isSetDebugOptions(); - case QUERY_OPTIONS: - return isSetQueryOptions(); - case EXPLAIN: - return isSetExplain(); - case EXPRESSION_OVERRIDE_HINTS: - return isSetExpressionOverrideHints(); + case VERSION: + return isSetVersion(); + case DATA_SOURCE: + return isSetDataSource(); + case SELECT_LIST: + return isSetSelectList(); + case FILTER_EXPRESSION: + return isSetFilterExpression(); + case GROUP_BY_LIST: + return isSetGroupByList(); + case ORDER_BY_LIST: + return isSetOrderByList(); + case HAVING_EXPRESSION: + return isSetHavingExpression(); + case LIMIT: + return isSetLimit(); + case OFFSET: + return isSetOffset(); + case DEBUG_OPTIONS: + return isSetDebugOptions(); + case QUERY_OPTIONS: + return isSetQueryOptions(); + case EXPLAIN: + return isSetExplain(); + case EXPRESSION_OVERRIDE_HINTS: + return isSetExpressionOverrideHints(); } throw new java.lang.IllegalStateException(); } @@ -1225,14 +1231,17 @@ public int compareTo(PinotQuery other) { } @org.apache.thrift.annotation.Nullable + @Override public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } + @Override public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { scheme(iprot).read(iprot, this); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { scheme(oprot).write(oprot, this); } @@ -1365,6 +1374,12 @@ public void validate() throws org.apache.thrift.TException { if (dataSource != null) { dataSource.validate(); } + if (filterExpression != null) { + filterExpression.validate(); + } + if (havingExpression != null) { + havingExpression.validate(); + } } private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { @@ -1386,6 +1401,7 @@ private void readObject(java.io.ObjectInputStream in) throws java.io.IOException } private static class PinotQueryStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public PinotQueryStandardScheme getScheme() { return new PinotQueryStandardScheme(); } @@ -1393,13 +1409,14 @@ public PinotQueryStandardScheme getScheme() { private static class PinotQueryStandardScheme extends org.apache.thrift.scheme.StandardScheme { + @Override public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TField schemeField; iprot.readStructBegin(); while (true) { schemeField = iprot.readFieldBegin(); - if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { break; } switch (schemeField.id) { @@ -1407,7 +1424,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) if (schemeField.type == org.apache.thrift.protocol.TType.I32) { struct.version = iprot.readI32(); struct.setVersionIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1416,7 +1433,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) struct.dataSource = new DataSource(); struct.dataSource.read(iprot); struct.setDataSourceIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1435,7 +1452,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) iprot.readListEnd(); } struct.setSelectListIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1444,7 +1461,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) struct.filterExpression = new Expression(); struct.filterExpression.read(iprot); struct.setFilterExpressionIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1463,7 +1480,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) iprot.readListEnd(); } struct.setGroupByListIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1482,7 +1499,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) iprot.readListEnd(); } struct.setOrderByListIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1491,7 +1508,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) struct.havingExpression = new Expression(); struct.havingExpression.read(iprot); struct.setHavingExpressionIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1499,7 +1516,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) if (schemeField.type == org.apache.thrift.protocol.TType.I32) { struct.limit = iprot.readI32(); struct.setLimitIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1507,7 +1524,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) if (schemeField.type == org.apache.thrift.protocol.TType.I32) { struct.offset = iprot.readI32(); struct.setOffsetIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1527,7 +1544,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) iprot.readMapEnd(); } struct.setDebugOptionsIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1547,7 +1564,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) iprot.readMapEnd(); } struct.setQueryOptionsIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1555,7 +1572,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) if (schemeField.type == org.apache.thrift.protocol.TType.BOOL) { struct.explain = iprot.readBool(); struct.setExplainIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1577,7 +1594,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) iprot.readMapEnd(); } struct.setExpressionOverrideHintsIsSet(true); - } else { + } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; @@ -1592,6 +1609,7 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) struct.validate(); } + @Override public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) throws org.apache.thrift.TException { struct.validate(); @@ -1731,6 +1749,7 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) } private static class PinotQueryTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override public PinotQueryTupleScheme getScheme() { return new PinotQueryTupleScheme(); } @@ -1937,7 +1956,7 @@ public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) t } if (incoming.get(9)) { { - org.apache.thrift.protocol.TMap _map42 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); + org.apache.thrift.protocol.TMap _map42 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); struct.debugOptions = new java.util.HashMap(2*_map42.size); @org.apache.thrift.annotation.Nullable java.lang.String _key43; @org.apache.thrift.annotation.Nullable java.lang.String _val44; @@ -1952,7 +1971,7 @@ public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) t } if (incoming.get(10)) { { - org.apache.thrift.protocol.TMap _map46 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); + org.apache.thrift.protocol.TMap _map46 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); struct.queryOptions = new java.util.HashMap(2*_map46.size); @org.apache.thrift.annotation.Nullable java.lang.String _key47; @org.apache.thrift.annotation.Nullable java.lang.String _val48; @@ -1971,7 +1990,7 @@ public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) t } if (incoming.get(12)) { { - org.apache.thrift.protocol.TMap _map50 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRUCT, org.apache.thrift.protocol.TType.STRUCT); + org.apache.thrift.protocol.TMap _map50 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRUCT, org.apache.thrift.protocol.TType.STRUCT); struct.expressionOverrideHints = new java.util.HashMap(2*_map50.size); @org.apache.thrift.annotation.Nullable Expression _key51; @org.apache.thrift.annotation.Nullable Expression _val52; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/context/ExpressionContext.java b/pinot-common/src/main/java/org/apache/pinot/common/request/context/ExpressionContext.java index daf586e4a7c..924c08f32a1 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/context/ExpressionContext.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/context/ExpressionContext.java @@ -65,15 +65,6 @@ private ExpressionContext(Type type, String identifier, FunctionContext function _literal = literal; } - // TODO: Refactor all of the usage for getLiteralString. - @Deprecated - public String getLiteralString() { - if (_literal == null || _literal.getValue() == null) { - return null; - } - return _literal.getValue().toString(); - } - public Type getType() { return _type; } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java b/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java index e8e00a53301..3436e826f3c 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java @@ -18,10 +18,18 @@ */ package org.apache.pinot.common.request.context; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; import java.util.Objects; import javax.annotation.Nullable; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.request.Literal; +import org.apache.pinot.common.utils.PinotDataType; import org.apache.pinot.spi.data.FieldSpec; @@ -34,71 +42,144 @@ */ public class LiteralContext { // TODO: Support all of the types for sql. - private FieldSpec.DataType _type; - private Object _value; + private final FieldSpec.DataType _type; + private final Object _value; + private final BigDecimal _bigDecimalValue; - // TODO: Support all data types. - private static FieldSpec.DataType convertThriftTypeToDataType(Literal._Fields fields) { - switch (fields) { - case LONG_VALUE: - return FieldSpec.DataType.LONG; - case BOOL_VALUE: - return FieldSpec.DataType.BOOLEAN; - case DOUBLE_VALUE: - return FieldSpec.DataType.DOUBLE; - case STRING_VALUE: - return FieldSpec.DataType.STRING; + private static BigDecimal getBigDecimalValue(FieldSpec.DataType type, Object value) { + switch (type) { + case BIG_DECIMAL: + return (BigDecimal) value; + case BOOLEAN: + return PinotDataType.BOOLEAN.toBigDecimal(value); + case TIMESTAMP: + return PinotDataType.TIMESTAMP.toBigDecimal(Timestamp.valueOf(value.toString())); default: - throw new UnsupportedOperationException("Unsupported literal type:" + fields); + if (type.isNumeric()) { + return new BigDecimal(value.toString()); + } + return BigDecimal.ZERO; } } - private static Class convertDataTypeToJavaType(FieldSpec.DataType dataType) { - switch (dataType) { - case INT: - return Integer.class; - case LONG: - return Long.class; - case BOOLEAN: - return Boolean.class; - case FLOAT: - return Float.class; - case DOUBLE: - return Double.class; - case STRING: - return String.class; - default: - throw new UnsupportedOperationException("Unsupported dataType:" + dataType); + @VisibleForTesting + static Pair inferLiteralDataTypeAndValue(String literal) { + // Try to interpret the literal as number + try { + Number number = NumberUtils.createNumber(literal); + if (number instanceof BigDecimal || number instanceof BigInteger) { + return ImmutablePair.of(FieldSpec.DataType.BIG_DECIMAL, new BigDecimal(literal)); + } else { + return ImmutablePair.of(FieldSpec.DataType.STRING, literal); + } + } catch (Exception e) { + // Ignored } + + // Try to interpret the literal as TIMESTAMP + try { + Timestamp timestamp = Timestamp.valueOf(literal); + return ImmutablePair.of(FieldSpec.DataType.TIMESTAMP, timestamp); + } catch (Exception e) { + // Ignored + } + return ImmutablePair.of(FieldSpec.DataType.STRING, literal); } public LiteralContext(Literal literal) { - _type = convertThriftTypeToDataType(literal.getSetField()); - _value = literal.getFieldValue(); + Preconditions.checkState(literal.getFieldValue() != null, + "Field value cannot be null for field:" + literal.getSetField()); + switch (literal.getSetField()) { + case BOOL_VALUE: + _type = FieldSpec.DataType.BOOLEAN; + _value = literal.getFieldValue(); + _bigDecimalValue = PinotDataType.BOOLEAN.toBigDecimal(_value); + break; + case LONG_VALUE: + long longValue = literal.getLongValue(); + if (longValue == (int) longValue) { + _type = FieldSpec.DataType.INT; + _value = (int) longValue; + } else { + _type = FieldSpec.DataType.LONG; + _value = longValue; + } + _bigDecimalValue = new BigDecimal(longValue); + break; + case DOUBLE_VALUE: + String stringValue = literal.getFieldValue().toString(); + Number floatingNumber = NumberUtils.createNumber(stringValue); + if (floatingNumber instanceof Float) { + _type = FieldSpec.DataType.FLOAT; + _value = floatingNumber; + } else { + _type = FieldSpec.DataType.DOUBLE; + _value = literal.getDoubleValue(); + } + _bigDecimalValue = new BigDecimal(stringValue); + break; + case STRING_VALUE: + Pair typeAndValue = + inferLiteralDataTypeAndValue(literal.getFieldValue().toString()); + _type = typeAndValue.getLeft(); + _value = typeAndValue.getRight(); + if (_type == FieldSpec.DataType.BIG_DECIMAL) { + _bigDecimalValue = (BigDecimal) _value; + } else if (_type == FieldSpec.DataType.TIMESTAMP) { + _bigDecimalValue = PinotDataType.TIMESTAMP.toBigDecimal(Timestamp.valueOf(_value.toString())); + } else { + _bigDecimalValue = BigDecimal.ZERO; + } + break; + case NULL_VALUE: + _type = FieldSpec.DataType.UNKNOWN; + _value = null; + _bigDecimalValue = BigDecimal.ZERO; + break; + default: + throw new UnsupportedOperationException("Unsupported data type:" + literal.getSetField()); + } } public FieldSpec.DataType getType() { return _type; } + public int getIntValue() { + return _bigDecimalValue.intValue(); + } + + public double getDoubleValue() { + return _bigDecimalValue.doubleValue(); + } + + public BigDecimal getBigDecimalValue() { + return _bigDecimalValue; + } + + public String getStringValue() { + return String.valueOf(_value); + } + @Nullable public Object getValue() { return _value; } + // This ctor is only used for special handling in subquery. public LiteralContext(FieldSpec.DataType type, Object value) { - Preconditions.checkArgument(convertDataTypeToJavaType(type) == value.getClass(), - "Unmatched data type: " + type + " java type: " + value.getClass()); _type = type; - _value = value; + if (type == FieldSpec.DataType.UNKNOWN) { + _value = null; + } else { + _value = value; + } + _bigDecimalValue = getBigDecimalValue(type, value); } @Override public int hashCode() { - if (_value == null) { - return 31 * _type.hashCode(); - } - return 31 * _value.hashCode(); + return Objects.hash(_value, _type); } @Override @@ -115,10 +196,7 @@ public boolean equals(Object o) { @Override public String toString() { - if (_value == null) { - return "null"; - } // TODO: print out the type. - return '\'' + _value.toString() + '\''; + return '\'' + String.valueOf(_value) + '\''; } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/context/OrderByExpressionContext.java b/pinot-common/src/main/java/org/apache/pinot/common/request/context/OrderByExpressionContext.java index fbe1a83882f..765c8e707f1 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/context/OrderByExpressionContext.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/context/OrderByExpressionContext.java @@ -29,10 +29,18 @@ public class OrderByExpressionContext { private final ExpressionContext _expression; private final boolean _isAsc; + private final Boolean _isNullsLast; public OrderByExpressionContext(ExpressionContext expression, boolean isAsc) { _expression = expression; _isAsc = isAsc; + _isNullsLast = null; + } + + public OrderByExpressionContext(ExpressionContext expression, boolean isAsc, boolean isNullsLast) { + _expression = expression; + _isAsc = isAsc; + _isNullsLast = isNullsLast; } public ExpressionContext getExpression() { @@ -47,6 +55,16 @@ public boolean isDesc() { return !_isAsc; } + public boolean isNullsLast() { + // By default, null values sort as if larger than any non-null value; that is, NULLS FIRST is the default for DESC + // order, and NULLS LAST otherwise. + if (_isNullsLast == null) { + return _isAsc; + } else { + return _isNullsLast; + } + } + /** * Adds the columns (IDENTIFIER expressions) in the order-by expression to the given set. */ @@ -63,16 +81,20 @@ public boolean equals(Object o) { return false; } OrderByExpressionContext that = (OrderByExpressionContext) o; - return _isAsc == that._isAsc && Objects.equals(_expression, that._expression); + return Objects.equals(_expression, that._expression) && _isAsc == that._isAsc && _isNullsLast == that._isNullsLast; } @Override public int hashCode() { - return Objects.hash(_expression, _isAsc); + return Objects.hash(_expression, _isAsc, _isNullsLast); } @Override public String toString() { - return _expression.toString() + (_isAsc ? " ASC" : " DESC"); + if (_isNullsLast != null) { + return _expression.toString() + (_isAsc ? " ASC" : " DESC") + (_isNullsLast ? " NULLS LAST" : " NULLS FIRST"); + } else { + return _expression.toString() + (_isAsc ? " ASC" : " DESC"); + } } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java index ad2340942f3..0014f5974e4 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java @@ -25,6 +25,7 @@ import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Literal; import org.apache.pinot.common.request.context.predicate.EqPredicate; import org.apache.pinot.common.request.context.predicate.InPredicate; import org.apache.pinot.common.request.context.predicate.IsNotNullPredicate; @@ -90,11 +91,6 @@ public static FunctionContext getFunction(Function thriftFunction) { for (Expression operand : operands) { arguments.add(getExpression(operand)); } - // TODO(walterddr): a work-around for multi-stage query engine which might pass COUNT without argument, and - // should be removed once that issue is fixed. - if (arguments.isEmpty() && functionName.equalsIgnoreCase(AggregationFunctionType.COUNT.getName())) { - arguments.add(ExpressionContext.forIdentifier("*")); - } return new FunctionContext(functionType, functionName, arguments); } else { return new FunctionContext(functionType, functionName, Collections.emptyList()); @@ -230,6 +226,9 @@ private static String getStringValue(Expression thriftExpression) { throw new BadQueryRequestException( "Pinot does not support column or function on the right-hand side of the predicate"); } + if (thriftExpression.getLiteral().getSetField() == Literal._Fields.NULL_VALUE) { + return "null"; + } return thriftExpression.getLiteral().getFieldValue().toString(); } @@ -356,6 +355,6 @@ private static String getStringValue(ExpressionContext expressionContext) { throw new BadQueryRequestException( "Pinot does not support column or function on the right-hand side of the predicate"); } - return expressionContext.getLiteralString(); + return expressionContext.getLiteral().getStringValue(); } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java b/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java index e50ea2b2ee3..3ad49460bc0 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java @@ -325,4 +325,14 @@ String toJsonString() * Set the total number of segments with a MatchAllFilterOperator when Explain Plan is called */ void setExplainPlanNumMatchAllFilterSegments(long explainPlanNumMatchAllFilterSegments); + + /** + * get request ID for the query + */ + String getRequestId(); + + /** + * set request ID generated by broker + */ + void setRequestId(String requestId); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java index 9c06d54b765..f2580b9cc33 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java @@ -41,7 +41,7 @@ * Supports serialization via JSON. */ @JsonPropertyOrder({ - "resultTable", "exceptions", "numServersQueried", "numServersResponded", "numSegmentsQueried", + "resultTable", "requestId", "exceptions", "numServersQueried", "numServersResponded", "numSegmentsQueried", "numSegmentsProcessed", "numSegmentsMatched", "numConsumingSegmentsQueried", "numConsumingSegmentsProcessed", "numConsumingSegmentsMatched", "numDocsScanned", "numEntriesScannedInFilter", "numEntriesScannedPostFilter", "numGroupsLimitReached", "totalDocs", "timeUsedMs", "offlineThreadCpuTimeNs", "realtimeThreadCpuTimeNs", @@ -57,6 +57,7 @@ public class BrokerResponseNative implements BrokerResponse { new BrokerResponseNative(QueryException.TABLE_DOES_NOT_EXIST_ERROR); public static final BrokerResponseNative BROKER_ONLY_EXPLAIN_PLAN_OUTPUT = getBrokerResponseExplainPlanOutput(); + private String _requestId; private int _numServersQueried = 0; private int _numServersResponded = 0; private long _numDocsScanned = 0L; @@ -321,7 +322,10 @@ public ResultTable getResultTable() { @Override public void setResultTable(ResultTable resultTable) { _resultTable = resultTable; - _numRowsResultSet = resultTable.getRows().size(); + // If query level parameter is set to not return the results, then resultTable will be null. + if (resultTable != null) { + _numRowsResultSet = resultTable.getRows().size(); + } } @JsonProperty("exceptions") @@ -554,4 +558,16 @@ public void addToExceptions(QueryProcessingException processingException) { public int getExceptionsSize() { return _processingExceptions.size(); } + + @JsonProperty("requestId") + @Override + public String getRequestId() { + return _requestId; + } + + @JsonProperty("requestId") + @Override + public void setRequestId(String requestId) { + _requestId = requestId; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java new file mode 100644 index 00000000000..95943881dc7 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.response.broker; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.response.ProcessingException; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.spi.utils.JsonUtils; + + +/** + * This class implements pinot-broker's response format for any given query. + * All fields either primitive data types, or native objects (as opposed to JSONObjects). + * + * Supports serialization via JSON. + */ +@JsonPropertyOrder({ + "resultTable", "requestId", "stageStats", "exceptions", "numServersQueried", "numServersResponded", + "numSegmentsQueried", "numSegmentsProcessed", "numSegmentsMatched", "numConsumingSegmentsQueried", + "numConsumingSegmentsProcessed", "numConsumingSegmentsMatched", "numDocsScanned", "numEntriesScannedInFilter", + "numEntriesScannedPostFilter", "numGroupsLimitReached", "totalDocs", "timeUsedMs", "offlineThreadCpuTimeNs", + "realtimeThreadCpuTimeNs", "offlineSystemActivitiesCpuTimeNs", "realtimeSystemActivitiesCpuTimeNs", + "offlineResponseSerializationCpuTimeNs", "realtimeResponseSerializationCpuTimeNs", "offlineTotalCpuTimeNs", + "realtimeTotalCpuTimeNs", "segmentStatistics", "traceInfo" +}) +public class BrokerResponseNativeV2 extends BrokerResponseNative { + + private final Map _stageIdStats = new HashMap<>(); + + public BrokerResponseNativeV2() { + } + + public BrokerResponseNativeV2(ProcessingException exception) { + super(exception); + } + + public BrokerResponseNativeV2(List exceptions) { + super(exceptions); + } + + /** Generate EXPLAIN PLAN output when queries are evaluated by Broker without going to the Server. */ + private static BrokerResponseNativeV2 getBrokerResponseExplainPlanOutput() { + BrokerResponseNativeV2 brokerResponse = BrokerResponseNativeV2.empty(); + List rows = new ArrayList<>(); + rows.add(new Object[]{"BROKER_EVALUATE", 0, -1}); + brokerResponse.setResultTable(new ResultTable(DataSchema.EXPLAIN_RESULT_SCHEMA, rows)); + return brokerResponse; + } + + /** + * Get a new empty {@link BrokerResponseNativeV2}. + */ + public static BrokerResponseNativeV2 empty() { + return new BrokerResponseNativeV2(); + } + + public static BrokerResponseNativeV2 fromJsonString(String jsonString) + throws IOException { + return JsonUtils.stringToObject(jsonString, BrokerResponseNativeV2.class); + } + + public void addStageStat(Integer stageId, BrokerResponseStats brokerResponseStats) { + // StageExecutionWallTime will always be there, other stats are optional such as OperatorStats + if (brokerResponseStats.getStageExecWallTimeMs() != -1) { + _stageIdStats.put(stageId, brokerResponseStats); + } + } + + @JsonProperty("stageStats") + public Map getStageIdStats() { + return _stageIdStats; + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java new file mode 100644 index 00000000000..f2361a7908e --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java @@ -0,0 +1,134 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.response.broker; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.pinot.spi.utils.JsonUtils; + + +// TODO: Decouple the execution stats aggregator logic and make it into a util that can aggregate 2 values with the +// same metadataKey +// TODO: Replace member fields with a simple map of +// TODO: Add a subStat field, stage level subStats will contain each operator stats +@JsonPropertyOrder({"requestId", "exceptions", "numBlocks", "numRows", "stageExecutionTimeMs", "stageExecutionUnit", + "stageExecWallTimeMs", "stageExecEndTimeMs", "numServersQueried", "numServersResponded", "numSegmentsQueried", + "numSegmentsProcessed", "numSegmentsMatched", "numConsumingSegmentsQueried", "numConsumingSegmentsProcessed", + "numConsumingSegmentsMatched", "numDocsScanned", "numEntriesScannedInFilter", "numEntriesScannedPostFilter", + "numGroupsLimitReached", "totalDocs", "timeUsedMs", "offlineThreadCpuTimeNs", "realtimeThreadCpuTimeNs", + "offlineSystemActivitiesCpuTimeNs", "realtimeSystemActivitiesCpuTimeNs", "offlineResponseSerializationCpuTimeNs", + "realtimeResponseSerializationCpuTimeNs", "offlineTotalCpuTimeNs", "realtimeTotalCpuTimeNs", + "traceInfo", "operatorStats", "tableNames"}) +@JsonInclude(JsonInclude.Include.NON_DEFAULT) +public class BrokerResponseStats extends BrokerResponseNative { + + private int _numBlocks = 0; + private int _numRows = 0; + private long _stageExecutionTimeMs = 0; + private int _stageExecutionUnit = 0; + private long _stageExecWallTimeMs = -1; + private Map> _operatorStats = new HashMap<>(); + private List _tableNames = new ArrayList<>(); + + @Override + public ResultTable getResultTable() { + return null; + } + + @JsonProperty("numBlocks") + public int getNumBlocks() { + return _numBlocks; + } + + @JsonProperty("numBlocks") + public void setNumBlocks(int numBlocks) { + _numBlocks = numBlocks; + } + + @JsonProperty("numRows") + public int getNumRows() { + return _numRows; + } + + @JsonProperty("numRows") + public void setNumRows(int numRows) { + _numRows = numRows; + } + + @JsonProperty("stageExecutionTimeMs") + public long getStageExecutionTimeMs() { + return _stageExecutionTimeMs; + } + + @JsonProperty("stageExecutionTimeMs") + public void setStageExecutionTimeMs(long stageExecutionTimeMs) { + _stageExecutionTimeMs = stageExecutionTimeMs; + } + + @JsonProperty("stageExecWallTimeMs") + public long getStageExecWallTimeMs() { + return _stageExecWallTimeMs; + } + + @JsonProperty("stageExecWallTimeMs") + public void setStageExecWallTimeMs(long stageExecWallTimeMs) { + _stageExecWallTimeMs = stageExecWallTimeMs; + } + + @JsonProperty("stageExecutionUnit") + public long getStageExecutionUnit() { + return _stageExecutionUnit; + } + + @JsonProperty("stageExecutionUnit") + public void setStageExecutionUnit(int stageExecutionUnit) { + _stageExecutionUnit = stageExecutionUnit; + } + + public String toJsonString() + throws IOException { + return JsonUtils.objectToString(this); + } + + @JsonProperty("operatorStats") + public Map> getOperatorStats() { + return _operatorStats; + } + + @JsonProperty("operatorStats") + public void setOperatorStats(Map> operatorStats) { + _operatorStats = operatorStats; + } + + @JsonProperty("tableNames") + public List getTableNames() { + return _tableNames; + } + + @JsonProperty("tableNames") + public void setTableNames(List tableNames) { + _tableNames = tableNames; + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/EndReplaceSegmentsRequest.java b/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/EndReplaceSegmentsRequest.java new file mode 100644 index 00000000000..7bcb4baf65f --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/EndReplaceSegmentsRequest.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.restlet.resources; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Request object for endReplaceSegments API. + * + * 1. segmentsTo: The new segments that actually get created. Sometimes not all segments that are passed into + * startReplaceSegments can get created. If only a subset of the original list eventually gets created, + * we need to be able to supply that list to the replacement protocol, so that the remaining + * segments that did not get created can be ignored. + * 2. customMap : custom map. + */ +public class EndReplaceSegmentsRequest { + private final List _segmentsTo; + private final Map _customMap; + + public EndReplaceSegmentsRequest(@JsonProperty("segmentsTo") @Nullable List segmentsTo) { + this(segmentsTo, null); + } + + @JsonCreator + public EndReplaceSegmentsRequest(@JsonProperty("segmentsTo") @Nullable List segmentsTo, + @JsonProperty("customMap") @Nullable Map customMap) { + _segmentsTo = (segmentsTo == null) ? Collections.emptyList() : segmentsTo; + _customMap = customMap; + } + + public List getSegmentsTo() { + return _segmentsTo; + } + + public Map getCustomMap() { + return _customMap; + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/RevertReplaceSegmentsRequest.java b/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/RevertReplaceSegmentsRequest.java new file mode 100644 index 00000000000..b4ed79aaf26 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/RevertReplaceSegmentsRequest.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.restlet.resources; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import javax.annotation.Nullable; + + +/** + * Request object for revertReplaceSegments API. + * + * customMap : custom map. + */ +public class RevertReplaceSegmentsRequest { + private final Map _customMap; + + public RevertReplaceSegmentsRequest(@JsonProperty("customMap") @Nullable Map customMap) { + _customMap = customMap; + } + + public Map getCustomMap() { + return _customMap; + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/StartReplaceSegmentsRequest.java b/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/StartReplaceSegmentsRequest.java index 05479526bb2..9c78c68042b 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/StartReplaceSegmentsRequest.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/restlet/resources/StartReplaceSegmentsRequest.java @@ -18,10 +18,12 @@ */ package org.apache.pinot.common.restlet.resources; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; @@ -31,17 +33,27 @@ * 1. segmentsFrom : original segments. This field can be empty in case the user tries to upload the original segments * and wants to achieve the atomic update of multiple segments. * 2. segmentsTo : merged segments. + * 3. customMap : custom map. */ public class StartReplaceSegmentsRequest { private final List _segmentsFrom; private final List _segmentsTo; + private final Map _customMap; public StartReplaceSegmentsRequest(@JsonProperty("segmentsFrom") @Nullable List segmentsFrom, - @JsonProperty("segmentsTo") List segmentsTo) { + @JsonProperty("segmentsTo") @Nullable List segmentsTo) { + this(segmentsFrom, segmentsTo, null); + } + + @JsonCreator + public StartReplaceSegmentsRequest(@JsonProperty("segmentsFrom") @Nullable List segmentsFrom, + @JsonProperty("segmentsTo") @Nullable List segmentsTo, + @JsonProperty("customMap") @Nullable Map customMap) { _segmentsFrom = (segmentsFrom == null) ? Collections.emptyList() : segmentsFrom; - _segmentsTo = segmentsTo; - Preconditions - .checkArgument(segmentsTo != null && !segmentsTo.isEmpty(), "'segmentsTo' should not be null or empty"); + _segmentsTo = (segmentsTo == null) ? Collections.emptyList() : segmentsTo; + Preconditions.checkArgument(!_segmentsFrom.isEmpty() || !_segmentsTo.isEmpty(), + "'segmentsFrom' and 'segmentsTo' cannot both be empty"); + _customMap = customMap; } public List getSegmentsFrom() { @@ -51,4 +63,8 @@ public List getSegmentsFrom() { public List getSegmentsTo() { return _segmentsTo; } + + public Map getCustomMap() { + return _customMap; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/tier/TimeBasedTierSegmentSelector.java b/pinot-common/src/main/java/org/apache/pinot/common/tier/TimeBasedTierSegmentSelector.java index 18a8a142e40..54317009bd7 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/tier/TimeBasedTierSegmentSelector.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/tier/TimeBasedTierSegmentSelector.java @@ -57,6 +57,12 @@ public boolean selectSegment(String tableNameWithType, String segmentName) { .checkNotNull(segmentZKMetadata, "Could not find zk metadata for segment: {} of table: {}", segmentName, tableNameWithType); + // don't try to move consuming segments + if (!segmentZKMetadata.getStatus().isCompleted()) { + return false; + } + + // get segment end time to decide if segment gets selected long endTimeMs = segmentZKMetadata.getEndTimeMs(); Preconditions diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java index 4854f5bf22f..354ba8cd3c4 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.google.common.collect.Ordering; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -101,67 +102,6 @@ public ColumnDataType[] getStoredColumnDataTypes() { return storedColumnDataTypes; } - /** - * Returns whether the given data schema is type compatible with this one. - *
    - *
  • All numbers are type compatible with each other
  • - *
  • Numbers are not type compatible with string
  • - *
  • Non-array types are not type compatible with array types
  • - *
- * - * @param anotherDataSchema Data schema to compare with - * @return Whether the two data schemas are type compatible - */ - public boolean isTypeCompatibleWith(DataSchema anotherDataSchema) { - if (!Arrays.equals(_columnNames, anotherDataSchema._columnNames)) { - return false; - } - int numColumns = _columnNames.length; - for (int i = 0; i < numColumns; i++) { - if (!_columnDataTypes[i].isCompatible(anotherDataSchema._columnDataTypes[i])) { - return false; - } - } - return true; - } - - /** - * Upgrade the current data schema to cover the column data types in the given data schema. - *

Data type LONG can cover INT and LONG. - *

Data type DOUBLE can cover all numbers, but with potential precision loss when use it to cover - * LONG. - *

NOTE: The given data schema should be type compatible with this one. - * - * @param originalSchema the original Data schema - * @param anotherDataSchema Data schema to cover - */ - public static DataSchema upgradeToCover(DataSchema originalSchema, DataSchema anotherDataSchema) { - int numColumns = originalSchema._columnDataTypes.length; - ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; - for (int i = 0; i < numColumns; i++) { - ColumnDataType thisColumnDataType = originalSchema._columnDataTypes[i]; - ColumnDataType thatColumnDataType = anotherDataSchema._columnDataTypes[i]; - if (thisColumnDataType != thatColumnDataType) { - if (thisColumnDataType.isArray()) { - if (thisColumnDataType.isWholeNumberArray() && thatColumnDataType.isWholeNumberArray()) { - columnDataTypes[i] = ColumnDataType.LONG_ARRAY; - } else { - columnDataTypes[i] = ColumnDataType.DOUBLE_ARRAY; - } - } else { - if (thisColumnDataType.isWholeNumber() && thatColumnDataType.isWholeNumber()) { - columnDataTypes[i] = ColumnDataType.LONG; - } else { - columnDataTypes[i] = ColumnDataType.DOUBLE; - } - } - } else { - columnDataTypes[i] = originalSchema._columnDataTypes[i]; - } - } - return new DataSchema(originalSchema._columnNames, columnDataTypes); - } - public byte[] toBytes() throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -270,7 +210,8 @@ public enum ColumnDataType { BOOLEAN_ARRAY(INT_ARRAY, new int[0]), TIMESTAMP_ARRAY(LONG_ARRAY, new long[0]), STRING_ARRAY(new String[0]), - BYTES_ARRAY(new byte[0][]); + BYTES_ARRAY(new byte[0][]), + UNKNOWN(null); private static final EnumSet NUMERIC_TYPES = EnumSet.of(INT, LONG, FLOAT, DOUBLE, BIG_DECIMAL); private static final Ordering NUMERIC_TYPE_ORDERING = Ordering.explicit(INT, LONG, FLOAT, DOUBLE); @@ -357,25 +298,35 @@ public boolean isSuperTypeOf(ColumnDataType subTypeCandidate) { public DataType toDataType() { switch (this) { case INT: + case INT_ARRAY: return DataType.INT; case LONG: + case LONG_ARRAY: return DataType.LONG; case FLOAT: + case FLOAT_ARRAY: return DataType.FLOAT; case DOUBLE: + case DOUBLE_ARRAY: return DataType.DOUBLE; case BIG_DECIMAL: return DataType.BIG_DECIMAL; case BOOLEAN: + case BOOLEAN_ARRAY: return DataType.BOOLEAN; case TIMESTAMP: + case TIMESTAMP_ARRAY: return DataType.TIMESTAMP; case STRING: + case STRING_ARRAY: return DataType.STRING; case JSON: return DataType.JSON; case BYTES: + case BYTES_ARRAY: return DataType.BYTES; + case UNKNOWN: + return DataType.UNKNOWN; default: throw new IllegalStateException(String.format("Cannot convert ColumnDataType: %s to DataType", this)); } @@ -422,6 +373,7 @@ public Serializable convert(Object value) { return toTimestampArray(value); case BYTES_ARRAY: return (byte[][]) value; + case UNKNOWN: // fall through case OBJECT: return (Serializable) value; default: @@ -526,6 +478,8 @@ private static double[] toDoubleArray(Object value) { private static long[] toLongArray(Object value) { if (value instanceof long[]) { return (long[]) value; + } else if (value instanceof LongArrayList) { + return ((LongArrayList) value).elements(); } else { int[] intValues = (int[]) value; int length = intValues.length; @@ -586,6 +540,8 @@ public static ColumnDataType fromDataTypeSV(DataType dataType) { return JSON; case BYTES: return BYTES; + case UNKNOWN: + return UNKNOWN; default: throw new IllegalStateException("Unsupported data type: " + dataType); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUploadDownloadClient.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUploadDownloadClient.java index 9a07435bf1d..e5456410b17 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUploadDownloadClient.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUploadDownloadClient.java @@ -55,8 +55,10 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.pinot.common.auth.AuthProviderUtils; import org.apache.pinot.common.exception.HttpErrorStatusException; +import org.apache.pinot.common.restlet.resources.EndReplaceSegmentsRequest; import org.apache.pinot.common.restlet.resources.StartReplaceSegmentsRequest; import org.apache.pinot.common.utils.http.HttpClient; +import org.apache.pinot.common.utils.http.HttpClientConfig; import org.apache.pinot.spi.auth.AuthProvider; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.CommonConstants; @@ -129,14 +131,49 @@ public FileUploadDownloadClient() { _httpClient = new HttpClient(); } + public FileUploadDownloadClient(HttpClientConfig httpClientConfig) { + _httpClient = new HttpClient(httpClientConfig, null); + } + public FileUploadDownloadClient(SSLContext sslContext) { - _httpClient = new HttpClient(sslContext); + _httpClient = new HttpClient(HttpClientConfig.DEFAULT_HTTP_CLIENT_CONFIG, sslContext); + } + + public FileUploadDownloadClient(HttpClientConfig httpClientConfig, SSLContext sslContext) { + _httpClient = new HttpClient(httpClientConfig, sslContext); } public HttpClient getHttpClient() { return _httpClient; } + /** + * Extracts base URI from a URI, e.g., http://example.com:8000/a/b -> http://example.com:8000 + * @param fullURI a full URI with + * @return a URI + * @throws URISyntaxException when there are problems generating the URI + */ + public static URI extractBaseURI(URI fullURI) + throws URISyntaxException { + return getURI(fullURI.getScheme(), fullURI.getHost(), fullURI.getPort()); + } + + /** + * Generates a URI from the given protocol, host and port + * @param protocol the protocol part of the URI + * @param host the host part of the URI + * @param port the port part of the URI + * @return a URI + * @throws URISyntaxException when there are problems generating the URIg + */ + public static URI getURI(String protocol, String host, int port) + throws URISyntaxException { + if (!SUPPORTED_PROTOCOLS.contains(protocol)) { + throw new IllegalArgumentException(String.format("Unsupported protocol '%s'", protocol)); + } + return new URI(protocol, null, host, port, null, null, null); + } + public static URI getURI(String protocol, String host, int port, String path) throws URISyntaxException { if (!SUPPORTED_PROTOCOLS.contains(protocol)) { @@ -452,10 +489,13 @@ private static HttpUriRequest getStartReplaceSegmentsRequest(URI uri, String jso return requestBuilder.build(); } - private static HttpUriRequest getEndReplaceSegmentsRequest(URI uri, int socketTimeoutMs, + private static HttpUriRequest getEndReplaceSegmentsRequest(URI uri, String jsonRequestBody, int socketTimeoutMs, @Nullable AuthProvider authProvider) { RequestBuilder requestBuilder = RequestBuilder.post(uri).setVersion(HttpVersion.HTTP_1_1) .setHeader(HttpHeaders.CONTENT_TYPE, HttpClient.JSON_CONTENT_TYPE); + if (jsonRequestBody != null) { + requestBuilder.setEntity(new StringEntity(jsonRequestBody, ContentType.APPLICATION_JSON)); + } AuthProviderUtils.toRequestHeaders(authProvider).forEach(requestBuilder::addHeader); HttpClient.setTimeout(requestBuilder, socketTimeoutMs); return requestBuilder.build(); @@ -783,9 +823,54 @@ public SimpleHttpResponse uploadSegment(URI uri, String segmentName, InputStream /** * Returns a map from a given tableType to a list of segments for that given tableType (OFFLINE or REALTIME) * If tableType is left unspecified, both OFFLINE and REALTIME segments will be returned in the map. + * @param controllerBaseUri the base controller URI, e.g., https://example.com:8000 + * @param rawTableName the raw table name without table type + * @param tableType the table type (OFFLINE or REALTIME) + * @param excludeReplacedSegments whether to exclude replaced segments (determined by segment lineage) + * @return a map from a given tableType to a list of segment names + * @throws Exception when failed to get segments from the controller + */ + public Map> getSegments(URI controllerBaseUri, String rawTableName, + @Nullable TableType tableType, boolean excludeReplacedSegments) + throws Exception { + return getSegments(controllerBaseUri, rawTableName, tableType, excludeReplacedSegments, null); + } + + /** + * Returns a map from a given tableType to a list of segments for that given tableType (OFFLINE or REALTIME) + * If tableType is left unspecified, both OFFLINE and REALTIME segments will be returned in the map. + * @param controllerBaseUri the base controller URI, e.g., https://example.com:8000 + * @param rawTableName the raw table name without table type + * @param tableType the table type (OFFLINE or REALTIME) + * @param excludeReplacedSegments whether to exclude replaced segments (determined by segment lineage) + * @param authProvider the {@link AuthProvider} + * @return a map from a given tableType to a list of segment names + * @throws Exception when failed to get segments from the controller + */ + public Map> getSegments(URI controllerBaseUri, String rawTableName, + @Nullable TableType tableType, boolean excludeReplacedSegments, @Nullable AuthProvider authProvider) + throws Exception { + return getSegments(controllerBaseUri, rawTableName, tableType, excludeReplacedSegments, Long.MIN_VALUE, + Long.MAX_VALUE, false, authProvider); + } + + /** + * Returns a map from a given tableType to a list of segments for that given tableType (OFFLINE or REALTIME) + * If tableType is left unspecified, both OFFLINE and REALTIME segments will be returned in the map. + * @param controllerBaseUri the base controller URI, e.g., https://example.com:8000 + * @param rawTableName the raw table name without table type + * @param tableType the table type (OFFLINE or REALTIME) + * @param excludeReplacedSegments whether to exclude replaced segments (determined by segment lineage) + * @param startTimestamp start timestamp in ms (inclusive) + * @param endTimestamp end timestamp in ms (exclusive) + * @param excludeOverlapping whether to exclude the segments overlapping with the timestamps, false by default + * @param authProvider the {@link AuthProvider} + * @return a map from a given tableType to a list of segment names + * @throws Exception when failed to get segments from the controller */ - public Map> getSegments(URI controllerUri, String rawTableName, @Nullable TableType tableType, - boolean excludeReplacedSegments) + public Map> getSegments(URI controllerBaseUri, String rawTableName, + @Nullable TableType tableType, boolean excludeReplacedSegments, long startTimestamp, long endTimestamp, + boolean excludeOverlapping, @Nullable AuthProvider authProvider) throws Exception { List tableTypes; if (tableType == null) { @@ -794,13 +879,15 @@ public Map> getSegments(URI controllerUri, String rawTableN tableTypes = Arrays.asList(tableType.toString()); } ControllerRequestURLBuilder controllerRequestURLBuilder = - ControllerRequestURLBuilder.baseUrl(controllerUri.toString()); + ControllerRequestURLBuilder.baseUrl(controllerBaseUri.toString()); Map> tableTypeToSegments = new HashMap<>(); for (String tableTypeToFilter : tableTypes) { tableTypeToSegments.put(tableTypeToFilter, new ArrayList<>()); String uri = - controllerRequestURLBuilder.forSegmentListAPI(rawTableName, tableTypeToFilter, excludeReplacedSegments); + controllerRequestURLBuilder.forSegmentListAPI(rawTableName, tableTypeToFilter, excludeReplacedSegments, + startTimestamp, endTimestamp, excludeOverlapping); RequestBuilder requestBuilder = RequestBuilder.get(uri).setVersion(HttpVersion.HTTP_1_1); + AuthProviderUtils.toRequestHeaders(authProvider).forEach(requestBuilder::addHeader); HttpClient.setTimeout(requestBuilder, HttpClient.DEFAULT_SOCKET_TIMEOUT_MS); RetryPolicies.exponentialBackoffRetryPolicy(5, 10_000L, 2.0).attempt(() -> { try { @@ -984,10 +1071,13 @@ public SimpleHttpResponse startReplaceSegments(URI uri, StartReplaceSegmentsRequ * @throws IOException * @throws HttpErrorStatusException */ - public SimpleHttpResponse endReplaceSegments(URI uri, int socketTimeoutMs, @Nullable AuthProvider authProvider) + public SimpleHttpResponse endReplaceSegments(URI uri, int socketTimeoutMs, @Nullable + EndReplaceSegmentsRequest endReplaceSegmentsRequest, @Nullable AuthProvider authProvider) throws IOException, HttpErrorStatusException { + String jsonBody = (endReplaceSegmentsRequest == null) ? null + : JsonUtils.objectToString(endReplaceSegmentsRequest); return HttpClient.wrapAndThrowHttpException( - _httpClient.sendRequest(getEndReplaceSegmentsRequest(uri, socketTimeoutMs, authProvider))); + _httpClient.sendRequest(getEndReplaceSegmentsRequest(uri, jsonBody, socketTimeoutMs, authProvider))); } /** diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUtils.java index 8667d4c4446..2c1cb10c434 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/FileUtils.java @@ -114,4 +114,24 @@ public static void close(Closeable... closeables) throws IOException { close(Arrays.asList(closeables)); } + + /** + * Concatenates the folderDir and filename and validates that the resulting file path is still within the folderDir. + * @param folderDir the parent directory + * @param filename the filename to concatenate to the parent directory + * @param msg the error message if the resulting file path is not within the parent directory + * @param args the error message arguments + * @return File object representing the concatenated file path + * @throws IllegalArgumentException if the resulting file path is not within the parent directory + * @throws IOException if the resulting file path is invalid + */ + public static File concatAndValidateFile(File folderDir, String filename, String msg, Object... args) + throws IllegalArgumentException, IOException { + File filePath = new File(folderDir, filename); + if (!filePath.getCanonicalPath().startsWith(folderDir.getCanonicalPath() + File.separator)) { + throw new IllegalArgumentException(String.format(msg, args)); + } + + return filePath; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/RegexpPatternConverterUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/RegexpPatternConverterUtils.java index c62cf86f235..31a0d59d959 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/RegexpPatternConverterUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/RegexpPatternConverterUtils.java @@ -18,6 +18,9 @@ */ package org.apache.pinot.common.utils; + +import com.google.common.primitives.Chars; + /** * Utility for converting regex patterns. */ @@ -25,9 +28,14 @@ public class RegexpPatternConverterUtils { private RegexpPatternConverterUtils() { } - /* Represents all metacharacters to be processed */ - public static final String[] REGEXP_METACHARACTERS = - {"\\", "^", "$", ".", "{", "}", "[", "]", "(", ")", "*", "+", "?", "|", "<", ">", "-", "&", "/"}; + /* + * Represents all metacharacters to be processed. + * This excludes the \ (back slash) character as that doubles up as an escape character as well. + * So it is handled separately in the conversion logic. + */ + public static final char[] REGEXP_METACHARACTERS = new char[]{ + '^', '$', '.', '{', '}', '[', ']', '(', ')', '*', '+', '?', '|', '<', '>', '-', '&', '/'}; + public static final char BACK_SLASH = '\\'; /** * Converts a LIKE pattern into REGEXP_LIKE pattern. @@ -64,24 +72,61 @@ public static String likeToRegexpLike(String likePattern) { break; } - String escaped = escapeMetaCharacters(likePattern.substring(start, end)); - StringBuilder sb = new StringBuilder(escaped.length() + 2); - sb.append(prefix); - sb.append(escaped); - sb.append(suffix); + likePattern = likePattern.substring(start, end); + return escapeMetaCharsAndWildcards(likePattern, prefix, suffix); + } + /** + * Escapes the provided pattern by considering the following constraints: + *

    + *
  • SQL wildcards escaping is handled (_, %)
  • + *
  • Regex meta characters escaping is handled
  • + *
+ * @param input the provided input string + * @param prefix the prefix to be added to the output string + * @param suffix the suffix to be added to the output string + * @return the final output string + */ + private static String escapeMetaCharsAndWildcards(String input, String prefix, String suffix) { + StringBuilder sb = new StringBuilder(); + sb.append(prefix); + // handling SQL wildcards (_, %) by replacing them with corresponding regex equivalents + // we ignore them if the SQL wildcards are escaped int i = 0; - while (i < sb.length()) { - char c = sb.charAt(i); - if (c == '_') { - sb.replace(i, i + 1, "."); - } else if (c == '%') { - sb.replace(i, i + 1, ".*"); - i++; + int len = input.length(); + boolean isPrevCharBackSlash = false; + while (i < len) { + char c = input.charAt(i); + switch (c) { + case '_': + sb.append(isPrevCharBackSlash ? c : "."); + break; + case '%': + sb.append(isPrevCharBackSlash ? c : ".*"); + break; + default: + // either the current character is a meta-character + // OR + // this means the previous character is a \ + // but it was not used for escaping SQL wildcards + // so let's escape this \ in the output + // this case is separately handled outside the meta characters list + if (Chars.indexOf(REGEXP_METACHARACTERS, c) >= 0 || isPrevCharBackSlash) { + sb.append(BACK_SLASH); + } + sb.append(c); + break; } + isPrevCharBackSlash = (c == BACK_SLASH); i++; } + // handle trailing \ + if (isPrevCharBackSlash) { + sb.append(BACK_SLASH); + } + + sb.append(suffix); return sb.toString(); } @@ -103,18 +148,6 @@ private static int indexOfLastDifferent(String str, char character) { return -1; } - /** - * Add escape characters before special characters - */ - private static String escapeMetaCharacters(String pattern) { - for (String metaCharacter : REGEXP_METACHARACTERS) { - if (pattern.contains(metaCharacter)) { - pattern = pattern.replace(metaCharacter, "\\" + metaCharacter); - } - } - return pattern; - } - /** * Converts a REGEXP_LIKE pattern into Lucene REGEXP pattern. */ diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/SimpleHttpErrorInfo.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/SimpleHttpErrorInfo.java index 90597453d54..b424ada19f0 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/SimpleHttpErrorInfo.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/SimpleHttpErrorInfo.java @@ -19,6 +19,7 @@ package org.apache.pinot.common.utils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -30,6 +31,7 @@ public class SimpleHttpErrorInfo { private String _error; @JsonCreator + @JsonIgnoreProperties(ignoreUnknown = true) public SimpleHttpErrorInfo(@JsonProperty("code") int code, @JsonProperty("error") String message) { _code = code; _error = message; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/URIUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/URIUtils.java index 042427b772d..5aff60b07fe 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/URIUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/URIUtils.java @@ -70,6 +70,19 @@ public static String getPath(String basePath, String... parts) { return stringJoiner.toString(); } + /** + * Returns the last part for the given path split by the file separator. + * If the file separator is not found, returns the whole path as the last part. + */ + public static String getLastPart(String path) { + if (path == null) { + return null; + } + int parameterIndex = path.indexOf("?"); + path = parameterIndex >= 0 ? path.substring(0, parameterIndex) : path; + return path.substring(path.lastIndexOf(File.separator) + 1); + } + /** * Returns the download URL with the segment name encoded. */ diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java index 420924af0a8..2682eb927a1 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionValue; @@ -190,4 +191,8 @@ public static Integer getGroupTrimThreshold(Map queryOptions) { String groupByTrimThreshold = queryOptions.get(QueryOptionKey.GROUP_TRIM_THRESHOLD); return groupByTrimThreshold != null ? Integer.parseInt(groupByTrimThreshold) : null; } + + public static boolean shouldDropResults(Map queryOptions) { + return Boolean.parseBoolean(queryOptions.get(CommonConstants.Broker.Request.QueryOptionKey.DROP_RESULTS)); + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TableConfigUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TableConfigUtils.java index 8abb0ea964f..0a59696b10e 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TableConfigUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TableConfigUtils.java @@ -20,13 +20,18 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Preconditions; import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.spi.config.table.DedupConfig; import org.apache.pinot.spi.config.table.DimensionTableConfig; @@ -34,6 +39,7 @@ import org.apache.pinot.spi.config.table.IndexingConfig; import org.apache.pinot.spi.config.table.QueryConfig; import org.apache.pinot.spi.config.table.QuotaConfig; +import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; import org.apache.pinot.spi.config.table.RoutingConfig; import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; @@ -50,9 +56,13 @@ import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; import org.apache.pinot.spi.config.table.ingestion.StreamIngestionConfig; import org.apache.pinot.spi.utils.JsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TableConfigUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(TableConfigUtils.class); + private TableConfigUtils() { } @@ -113,11 +123,11 @@ public static TableConfig fromZNRecord(ZNRecord znRecord) queryConfig = JsonUtils.stringToObject(queryConfigString, QueryConfig.class); } - Map instanceAssignmentConfigMap = null; + Map instanceAssignmentConfigMap = null; String instanceAssignmentConfigMapString = simpleFields.get(TableConfig.INSTANCE_ASSIGNMENT_CONFIG_MAP_KEY); if (instanceAssignmentConfigMapString != null) { instanceAssignmentConfigMap = JsonUtils.stringToObject(instanceAssignmentConfigMapString, - new TypeReference>() { + new TypeReference>() { }); } @@ -181,9 +191,9 @@ public static TableConfig fromZNRecord(ZNRecord znRecord) } return new TableConfig(tableName, tableType, validationConfig, tenantConfig, indexingConfig, customConfig, - quotaConfig, taskConfig, routingConfig, queryConfig, instanceAssignmentConfigMap, - fieldConfigList, upsertConfig, dedupConfig, dimensionTableConfig, ingestionConfig, tierConfigList, isDimTable, - tunerConfigList, instancePartitionsMap, segmentAssignmentConfigMap); + quotaConfig, taskConfig, routingConfig, queryConfig, instanceAssignmentConfigMap, fieldConfigList, upsertConfig, + dedupConfig, dimensionTableConfig, ingestionConfig, tierConfigList, isDimTable, tunerConfigList, + instancePartitionsMap, segmentAssignmentConfigMap); } public static ZNRecord toZNRecord(TableConfig tableConfig) @@ -216,8 +226,7 @@ public static ZNRecord toZNRecord(TableConfig tableConfig) if (queryConfig != null) { simpleFields.put(TableConfig.QUERY_CONFIG_KEY, queryConfig.toJsonString()); } - Map instanceAssignmentConfigMap = - tableConfig.getInstanceAssignmentConfigMap(); + Map instanceAssignmentConfigMap = tableConfig.getInstanceAssignmentConfigMap(); if (instanceAssignmentConfigMap != null) { simpleFields .put(TableConfig.INSTANCE_ASSIGNMENT_CONFIG_MAP_KEY, JsonUtils.objectToString(instanceAssignmentConfigMap)); @@ -332,6 +341,96 @@ public static void convertFromLegacyTableConfig(TableConfig tableConfig) { validationConfig.setSegmentPushType(null); } + /** + * Helper method to create a new TableConfig by overwriting the original TableConfig with tier specific configs, so + * that the consumers of TableConfig don't have to handle tier overwrites themselves. To begin with, we only + * consider to overwrite the index configs in `tableIndexConfig` and `fieldConfigList`, e.g. + * + * { + * "tableIndexConfig": { + * ... // configs allowed in IndexingConfig, for default tier + * "tierOverwrites": { + * "hotTier": {...}, // configs allowed in IndexingConfig, for hot tier + * "coldTier": {...} // configs allowed in IndexingConfig, for cold tier + * } + * } + * "fieldConfigList": [ + * { + * ... // configs allowed in FieldConfig, for default tier + * "tierOverwrites": { + * "hotTier": {...}, // configs allowed in FieldConfig, for hot tier + * "coldTier": {...} // configs allowed in FieldConfig, for cold tier + * } + * }, + * ... + * ] + * } + * + * Overwriting is to extract tier specific configs from those `tierOverwrites` sections and replace the + * corresponding configs set for default tier. + * + * TODO: Other tier specific configs like segment assignment policy may be handled in this helper method too, to + * keep tier overwrites transparent to consumers of TableConfig. + * + * @param tableConfig the input table config which is kept intact + * @param tier the target tier to overwrite the table config + * @return a new table config overwritten for the tier, or the original table if overwriting doesn't happen. + */ + public static TableConfig overwriteTableConfigForTier(TableConfig tableConfig, @Nullable String tier) { + if (tier == null) { + return tableConfig; + } + try { + boolean updated = false; + JsonNode tblCfgJson = tableConfig.toJsonNode(); + // Apply tier specific overwrites for `tableIndexConfig` + JsonNode tblIdxCfgJson = tblCfgJson.get(TableConfig.INDEXING_CONFIG_KEY); + if (tblIdxCfgJson != null && tblIdxCfgJson.has(TableConfig.TIER_OVERWRITES_KEY)) { + JsonNode tierCfgJson = tblIdxCfgJson.get(TableConfig.TIER_OVERWRITES_KEY).get(tier); + if (tierCfgJson != null) { + LOGGER.debug("Got table index config overwrites: {} for tier: {}", tierCfgJson, tier); + overwriteConfig(tblIdxCfgJson, tierCfgJson); + updated = true; + } + } + // Apply tier specific overwrites for `fieldConfigList` + JsonNode fieldCfgListJson = tblCfgJson.get(TableConfig.FIELD_CONFIG_LIST_KEY); + if (fieldCfgListJson != null && fieldCfgListJson.isArray()) { + Iterator fieldCfgListItr = fieldCfgListJson.elements(); + while (fieldCfgListItr.hasNext()) { + JsonNode fieldCfgJson = fieldCfgListItr.next(); + if (!fieldCfgJson.has(TableConfig.TIER_OVERWRITES_KEY)) { + continue; + } + JsonNode tierCfgJson = fieldCfgJson.get(TableConfig.TIER_OVERWRITES_KEY).get(tier); + if (tierCfgJson != null) { + LOGGER.debug("Got field index config overwrites: {} for tier: {}", tierCfgJson, tier); + overwriteConfig(fieldCfgJson, tierCfgJson); + updated = true; + } + } + } + if (updated) { + LOGGER.debug("Got overwritten table config: {} for tier: {}", tblCfgJson, tier); + return JsonUtils.jsonNodeToObject(tblCfgJson, TableConfig.class); + } else { + LOGGER.debug("No table config overwrites for tier: {}", tier); + return tableConfig; + } + } catch (IOException e) { + LOGGER.warn("Failed to overwrite table config for tier: {} for table: {}", tier, tableConfig.getTableName(), e); + return tableConfig; + } + } + + private static void overwriteConfig(JsonNode oldCfg, JsonNode newCfg) { + Iterator> cfgItr = newCfg.fields(); + while (cfgItr.hasNext()) { + Map.Entry cfgEntry = cfgItr.next(); + ((ObjectNode) oldCfg).set(cfgEntry.getKey(), cfgEntry.getValue()); + } + } + /** * Returns true if the table has pre-configured instance partitions for any type (OFFLINE/CONSUMING/COMPLETED). */ @@ -347,4 +446,27 @@ public static boolean hasPreConfiguredInstancePartitions(TableConfig tableConfig return hasPreConfiguredInstancePartitions(tableConfig) && tableConfig.getInstancePartitionsMap().containsKey(instancePartitionsType); } + + /** + * Get the partition column from tableConfig instance assignment config map. + * @param tableConfig table config + * @return partition column + */ + public static String getPartitionColumn(TableConfig tableConfig) { + // check InstanceAssignmentConfigMap is null or empty, + if (!MapUtils.isEmpty(tableConfig.getInstanceAssignmentConfigMap())) { + for (InstanceAssignmentConfig instanceAssignmentConfig : tableConfig.getInstanceAssignmentConfigMap().values()) { + //check InstanceAssignmentConfig has the InstanceReplicaGroupPartitionConfig with non-empty partitionColumn + if (StringUtils.isNotEmpty(instanceAssignmentConfig.getReplicaGroupPartitionConfig().getPartitionColumn())) { + return instanceAssignmentConfig.getReplicaGroupPartitionConfig().getPartitionColumn(); + } + } + } + + // for backward-compatibility, If partitionColumn value isn't there in InstanceReplicaGroupPartitionConfig + // check ReplicaGroupStrategyConfig for partitionColumn + ReplicaGroupStrategyConfig replicaGroupStrategyConfig = + tableConfig.getValidationConfig().getReplicaGroupStrategyConfig(); + return replicaGroupStrategyConfig != null ? replicaGroupStrategyConfig.getPartitionColumn() : null; + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TagNameUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TagNameUtils.java index 15ed490dc8b..28a2e30d471 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TagNameUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TagNameUtils.java @@ -102,6 +102,13 @@ public static String getRealtimeTagForTenant(@Nullable String tenantName) { return getTagForTenant(tenantName, REALTIME_SERVER_TAG_SUFFIX); } + /** + * Returns the server tag name for the given tenant and the given table type. + */ + public static String getServerTagForTenant(@Nullable String tenantName, TableType type) { + return getTagForTenant(tenantName, String.format("_%s", type)); + } + private static String getTagForTenant(@Nullable String tenantName, String tagSuffix) { if (tenantName == null) { return DEFAULT_TENANT_NAME + tagSuffix; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TierConfigUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TierConfigUtils.java index 835dddf67af..31e32113e73 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TierConfigUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/TierConfigUtils.java @@ -18,16 +18,19 @@ */ package org.apache.pinot.common.utils.config; -import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.helix.HelixManager; +import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.assignment.InstancePartitionsUtils; import org.apache.pinot.common.tier.FixedTierSegmentSelector; +import org.apache.pinot.common.tier.PinotServerTierStorage; import org.apache.pinot.common.tier.Tier; import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.tier.TierSegmentSelector; @@ -59,10 +62,38 @@ public static String normalizeTierName(String tierName) { return tierName == null ? "default" : tierName; } + /** + * Consider configured tiers and compute default instance partitions for the segment + * + * @return InstancePartitions if the one can be derived from the given sorted tiers, null otherwise + */ + @Nullable + public static InstancePartitions getTieredInstancePartitionsForSegment(String tableNameWithType, + String segmentName, @Nullable List sortedTiers, HelixManager helixManager) { + if (CollectionUtils.isEmpty(sortedTiers)) { + return null; + } + + // Find first applicable tier + for (Tier tier : sortedTiers) { + if (tier.getSegmentSelector().selectSegment(tableNameWithType, segmentName)) { + // Compute default instance partitions + PinotServerTierStorage storage = (PinotServerTierStorage) tier.getStorage(); + return InstancePartitionsUtils.computeDefaultInstancePartitionsForTag(helixManager, tableNameWithType, + tier.getName(), storage.getServerTag()); + } + } + + // Tier not found + return null; + } + + @Nullable public static String getDataDirForTier(TableConfig tableConfig, String tierName) { return getDataDirForTier(tableConfig, tierName, Collections.emptyMap()); } + @Nullable public static String getDataDirForTier(TableConfig tableConfig, String tierName, Map> instanceTierConfigs) { String tableNameWithType = tableConfig.getTableName(); @@ -78,17 +109,17 @@ public static String getDataDirForTier(TableConfig tableConfig, String tierName, } if (tierCfg != null) { Map backendProps = tierCfg.getTierBackendProperties(); - if (backendProps != null) { - dataDir = backendProps.get(CommonConstants.Tier.BACKEND_PROP_DATA_DIR); - } else { + if (backendProps == null) { LOGGER.debug("No backend props for tier: {} in TableConfig of table: {}", tierName, tableNameWithType); - } - if (StringUtils.isNotEmpty(dataDir)) { - LOGGER.debug("Got dataDir: {} for tier: {} in TableConfig of table: {}", dataDir, tierName, - tableNameWithType); - return dataDir; } else { - LOGGER.debug("No dataDir for tier: {} in TableConfig of table: {}", tierName, tableNameWithType); + dataDir = backendProps.get(CommonConstants.Tier.BACKEND_PROP_DATA_DIR); + if (StringUtils.isNotEmpty(dataDir)) { + LOGGER.debug("Got dataDir: {} for tier: {} in TableConfig of table: {}", dataDir, tierName, + tableNameWithType); + return dataDir; + } else { + LOGGER.debug("No dataDir for tier: {} in TableConfig of table: {}", tierName, tableNameWithType); + } } } } @@ -98,8 +129,6 @@ public static String getDataDirForTier(TableConfig tableConfig, String tierName, // All instance config names are lower cased while being passed down here. dataDir = instanceCfgs.get(CommonConstants.Tier.BACKEND_PROP_DATA_DIR.toLowerCase()); } - Preconditions.checkState(StringUtils.isNotEmpty(dataDir), "No dataDir for tier: %s for table: %s", tierName, - tableNameWithType); LOGGER.debug("Got dataDir: {} for tier: {} for table: {} in instance configs", dataDir, tierName, tableNameWithType); return dataDir; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/BaseSegmentFetcher.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/BaseSegmentFetcher.java index 2fadaffd0ac..d1756147d42 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/BaseSegmentFetcher.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/BaseSegmentFetcher.java @@ -22,6 +22,7 @@ import java.net.URI; import java.util.List; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.pinot.common.auth.AuthProviderUtils; import org.apache.pinot.spi.auth.AuthProvider; import org.apache.pinot.spi.env.PinotConfiguration; @@ -102,7 +103,8 @@ public void fetchSegmentToLocal(List uris, File dest) }); } - public File fetchUntarSegmentToLocalStreamed(URI uri, File dest, long rateLimit) + public File fetchUntarSegmentToLocalStreamed(URI uri, File dest, long rateLimit, + AtomicInteger attempts) throws Exception { throw new UnsupportedOperationException(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpSegmentFetcher.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpSegmentFetcher.java index 741ac18315a..be73c1908b6 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpSegmentFetcher.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpSegmentFetcher.java @@ -24,6 +24,7 @@ import java.net.URI; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.apache.http.Header; import org.apache.http.HttpHeaders; @@ -32,7 +33,10 @@ import org.apache.pinot.common.exception.HttpErrorStatusException; import org.apache.pinot.common.utils.FileUploadDownloadClient; import org.apache.pinot.common.utils.RoundRobinURIProvider; +import org.apache.pinot.common.utils.http.HttpClientConfig; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.retry.AttemptsExceededException; +import org.apache.pinot.spi.utils.retry.RetriableOperationException; import org.apache.pinot.spi.utils.retry.RetryPolicies; @@ -41,7 +45,7 @@ public class HttpSegmentFetcher extends BaseSegmentFetcher { @Override protected void doInit(PinotConfiguration config) { - _httpClient = new FileUploadDownloadClient(); + _httpClient = new FileUploadDownloadClient(HttpClientConfig.newBuilder(config).build()); } @Override @@ -51,8 +55,7 @@ public void fetchSegmentToLocal(URI downloadURI, File dest) // download from a same broken host as: 1) DNS may not RR the IP addresses 2) OS cache the DNS resolution result. RoundRobinURIProvider uriProvider = new RoundRobinURIProvider(downloadURI); - // Use the minimal value of configured retry count and number of IP addresses. - int retryCount = Math.min(_retryCount, uriProvider.numAddresses()); + int retryCount = getRetryCount(uriProvider); _logger.info("Retry downloading for {} times. retryCount from pinot server config: {}, number of IP addresses for " + "download URI: {}", retryCount, _retryCount, uriProvider.numAddresses()); @@ -95,55 +98,74 @@ public void fetchSegmentToLocal(URI downloadURI, File dest) }); } + private int getRetryCount(RoundRobinURIProvider uriProvider) { + // Use the minimal value of configured retry count and number of IP addresses. + return Math.min(_retryCount, uriProvider.numAddresses()); + } + @Override - public File fetchUntarSegmentToLocalStreamed(URI downloadURI, File dest, long maxStreamRateInByte) + public File fetchUntarSegmentToLocalStreamed(URI downloadURI, File dest, long maxStreamRateInByte, + AtomicInteger attempts) throws Exception { - // Create a RoundRobinURIProvider to round robin IP addresses when retry uploading. Otherwise may always try to + // Create a RoundRobinURIProvider to round robin IP addresses when retry uploading. Otherwise, may always try to // download from a same broken host as: 1) DNS may not RR the IP addresses 2) OS cache the DNS resolution result. RoundRobinURIProvider uriProvider = new RoundRobinURIProvider(downloadURI); - int retryCount = Math.max(_retryCount, uriProvider.numAddresses()); + + int retryCount = getRetryCount(uriProvider); + AtomicReference ret = new AtomicReference<>(); // return the untared segment directory _logger.info("Retry downloading for {} times. retryCount from pinot server config: {}, number of IP addresses for " + "download URI: {}", retryCount, _retryCount, uriProvider.numAddresses()); - RetryPolicies.exponentialBackoffRetryPolicy(retryCount, _retryWaitMs, _retryDelayScaleFactor).attempt(() -> { - URI uri = uriProvider.next(); - try { - String hostName = downloadURI.getHost(); - int port = downloadURI.getPort(); - // If the original download address is specified as host name, need add a "HOST" HTTP header to the HTTP - // request. Otherwise, if the download address is a LB address, when the LB be configured as "disallow direct - // access by IP address", downloading will fail. - List
httpHeaders = new LinkedList<>(); - if (!InetAddresses.isInetAddress(hostName)) { - httpHeaders.add(new BasicHeader(HttpHeaders.HOST, hostName + ":" + port)); - } - ret.set(_httpClient.downloadUntarFileStreamed(uri, dest, _authProvider, httpHeaders, maxStreamRateInByte)); + int tries; + try { + tries = RetryPolicies.exponentialBackoffRetryPolicy(retryCount, _retryWaitMs, _retryDelayScaleFactor).attempt( + () -> { + URI uri = uriProvider.next(); + try { + String hostName = downloadURI.getHost(); + int port = downloadURI.getPort(); + // If the original download address is specified as host name, need add a "HOST" HTTP header to the HTTP + // request. Otherwise, if the download address is a LB address, when the LB be configured as "disallow direct + // access by IP address", downloading will fail. + List
httpHeaders = new LinkedList<>(); + if (!InetAddresses.isInetAddress(hostName)) { + httpHeaders.add(new BasicHeader(HttpHeaders.HOST, hostName + ":" + port)); + } + ret.set(_httpClient.downloadUntarFileStreamed(uri, dest, _authProvider, httpHeaders, maxStreamRateInByte)); - return true; - } catch (HttpErrorStatusException e) { - int statusCode = e.getStatusCode(); - if (statusCode == HttpStatus.SC_NOT_FOUND || statusCode >= 500) { - // Temporary exception - // 404 is treated as a temporary exception, as the downloadURI may be backed by multiple hosts, - // if singe host is down, can retry with another host. - _logger.warn("Got temporary error status code: {} while downloading segment from: {} to: {}", statusCode, uri, + return true; + } catch (HttpErrorStatusException e) { + int statusCode = e.getStatusCode(); + if (statusCode == HttpStatus.SC_NOT_FOUND || statusCode >= 500) { + // Temporary exception + // 404 is treated as a temporary exception, as the downloadURI may be backed by multiple hosts, + // if singe host is down, can retry with another host. + _logger.warn("Got temporary error status code: {} while downloading segment from: {} to: {}", statusCode, + uri, dest, e); + return false; + } else { + // Permanent exception + _logger.error("Got permanent error status code: {} while downloading segment from: {} to: {}, won't retry", + statusCode, uri, dest, e); + throw e; + } + } catch (IOException e) { + _logger.warn("Caught IOException while stream download-untarring segment from: {} to: {}, retrying", uri, dest, e); return false; - } else { - // Permanent exception - _logger.error("Got permanent error status code: {} while downloading segment from: {} to: {}, won't retry", - statusCode, uri, dest, e); - throw e; + } catch (Exception e) { + _logger.warn("Caught exception while downloading segment from: {} to: {}", uri, dest, e); + return false; } - } catch (IOException e) { - _logger.warn("Caught IOException while stream download-untarring segment from: {} to: {}, retrying", - uri, dest, e); - return false; - } catch (Exception e) { - _logger.warn("Caught exception while downloading segment from: {} to: {}", uri, dest, e); - return false; - } - }); + }); + } catch (AttemptsExceededException e) { + attempts.set(e.getAttempts()); + throw e; + } catch (RetriableOperationException e) { + attempts.set(e.getAttempts()); + throw e; + } + attempts.set(tries); return ret.get(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpsSegmentFetcher.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpsSegmentFetcher.java index 64dcb601c1e..71528595d8d 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpsSegmentFetcher.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/HttpsSegmentFetcher.java @@ -22,6 +22,7 @@ import javax.net.ssl.SSLContext; import org.apache.pinot.common.utils.ClientSSLContextGenerator; import org.apache.pinot.common.utils.FileUploadDownloadClient; +import org.apache.pinot.common.utils.http.HttpClientConfig; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants; @@ -69,6 +70,6 @@ protected void doInit(PinotConfiguration config) { } SSLContext sslContext = new ClientSSLContextGenerator(sslConfig).generate(); - _httpClient = new FileUploadDownloadClient(sslContext); + _httpClient = new FileUploadDownloadClient(HttpClientConfig.newBuilder(config).build(), sslContext); } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcher.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcher.java index da4936f4336..78d720751f5 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcher.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcher.java @@ -21,6 +21,7 @@ import java.io.File; import java.net.URI; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.pinot.spi.env.PinotConfiguration; @@ -40,7 +41,7 @@ void fetchSegmentToLocal(URI uri, File dest) /** * Fetches a segment from URI location and untar to local in a streamed manner */ - File fetchUntarSegmentToLocalStreamed(URI uri, File dest, long rateLimit) + File fetchUntarSegmentToLocalStreamed(URI uri, File dest, long rateLimit, AtomicInteger attempts) throws Exception; /** diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactory.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactory.java index 2fc0124cb42..3c3f66248cc 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactory.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactory.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.pinot.common.auth.AuthConfig; import org.apache.pinot.common.auth.AuthProviderUtils; import org.apache.pinot.spi.crypt.PinotCrypter; @@ -166,14 +167,17 @@ private void fetchSegmentToLocalInternal(URI uri, File dest) * @return the untared directory * @throws Exception */ - public static File fetchAndStreamUntarToLocal(String uri, File tempRootDir, long maxStreamRateInByte) + public static File fetchAndStreamUntarToLocal(String uri, File tempRootDir, + long maxStreamRateInByte, AtomicInteger attempts) throws Exception { - return getInstance().fetchAndStreamUntarToLocalInternal(new URI(uri), tempRootDir, maxStreamRateInByte); + return getInstance().fetchAndStreamUntarToLocalInternal(new URI(uri), tempRootDir, maxStreamRateInByte, attempts); } - private File fetchAndStreamUntarToLocalInternal(URI uri, File tempRootDir, long maxStreamRateInByte) + private File fetchAndStreamUntarToLocalInternal(URI uri, File tempRootDir, + long maxStreamRateInByte, AtomicInteger attempts) throws Exception { - return getSegmentFetcher(uri.getScheme()).fetchUntarSegmentToLocalStreamed(uri, tempRootDir, maxStreamRateInByte); + return getSegmentFetcher(uri.getScheme()).fetchUntarSegmentToLocalStreamed(uri, tempRootDir, maxStreamRateInByte, + attempts); } /** diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/HelixHelper.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/HelixHelper.java index f39316cec99..4160dd44efc 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/HelixHelper.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/HelixHelper.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -78,6 +79,11 @@ private HelixHelper() { public static final String BROKER_RESOURCE = CommonConstants.Helix.BROKER_RESOURCE_INSTANCE; + private static int _minNumCharsInISToTurnOnCompression = -1; + + public static synchronized void setMinNumCharsInISToTurnOnCompression(int minNumChars) { + _minNumCharsInISToTurnOnCompression = minNumChars; + } public static IdealState cloneIdealState(IdealState idealState) { return new IdealState( (ZNRecord) ZN_RECORD_SERIALIZER.deserialize(ZN_RECORD_SERIALIZER.serialize(idealState.getRecord()))); @@ -127,7 +133,7 @@ public Boolean call() { updatedIdealState.setNumPartitions(numPartitions); // If the ideal state is large enough, enable compression - boolean enableCompression = numPartitions > NUM_PARTITIONS_THRESHOLD_TO_ENABLE_COMPRESSION; + boolean enableCompression = shouldCompress(updatedIdealState); if (enableCompression) { updatedZNRecord.setBooleanField(ENABLE_COMPRESSIONS_KEY, true); } else { @@ -163,6 +169,34 @@ public Boolean call() { return true; } } + + private boolean shouldCompress(IdealState is) { + if (is.getNumPartitions() > NUM_PARTITIONS_THRESHOLD_TO_ENABLE_COMPRESSION) { + return true; + } + + // Find the number of characters in one partition in idealstate, and extrapolate + // to estimate the number of characters. + // We could serialize the znode to determine the exact size, but that would mean serializing every + // idealstate znode twice. We avoid some extra GC by estimating the size instead. Such estimations + // should be good for most installations that have similar segment and instance names. + Iterator it = is.getPartitionSet().iterator(); + if (it.hasNext()) { + String partitionName = it.next(); + int numChars = partitionName.length(); + Map stateMap = is.getInstanceStateMap(partitionName); + for (Map.Entry entry : stateMap.entrySet()) { + numChars += entry.getKey().length(); + numChars += entry.getValue().length(); + } + numChars *= is.getNumPartitions(); + if (_minNumCharsInISToTurnOnCompression > 0 + && numChars > _minNumCharsInISToTurnOnCompression) { + return true; + } + } + return false; + } }); return idealStateWrapper._idealState; } catch (Exception e) { diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java index 4bd7117ab83..06361da5e98 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java @@ -53,6 +53,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.apache.pinot.common.auth.AuthProviderUtils; @@ -86,14 +87,18 @@ public class HttpClient implements AutoCloseable { private final CloseableHttpClient _httpClient; public HttpClient() { - this(null); + this(HttpClientConfig.DEFAULT_HTTP_CLIENT_CONFIG, null); } public HttpClient(@Nullable SSLContext sslContext) { + this(HttpClientConfig.DEFAULT_HTTP_CLIENT_CONFIG, sslContext); + } + + public HttpClient(HttpClientConfig httpClientConfig, @Nullable SSLContext sslContext) { SSLContext context = sslContext != null ? sslContext : TlsUtils.getSslContext(); // Set NoopHostnameVerifier to skip validating hostname when uploading/downloading segments. SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE); - _httpClient = HttpClients.custom().setSSLSocketFactory(csf).build(); + _httpClient = buildCloseableHttpClient(httpClientConfig, csf); } public static HttpClient getInstance() { @@ -101,7 +106,8 @@ public static HttpClient getInstance() { } private static final class HttpClientHolder { - static final HttpClient HTTP_CLIENT = new HttpClient(TlsUtils.getSslContext()); + static final HttpClient HTTP_CLIENT = + new HttpClient(HttpClientConfig.DEFAULT_HTTP_CLIENT_CONFIG, TlsUtils.getSslContext()); } // -------------------------------------------------------------------------- @@ -464,6 +470,18 @@ public static void setTimeout(RequestBuilder requestBuilder, int socketTimeoutMs requestBuilder.setConfig(requestConfig); } + private static CloseableHttpClient buildCloseableHttpClient(HttpClientConfig httpClientConfig, + SSLConnectionSocketFactory csf) { + HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(csf); + if (httpClientConfig.getMaxConnTotal() > 0) { + httpClientBuilder.setMaxConnTotal(httpClientConfig.getMaxConnTotal()); + } + if (httpClientConfig.getMaxConnPerRoute() > 0) { + httpClientBuilder.setMaxConnPerRoute(httpClientConfig.getMaxConnPerRoute()); + } + return httpClientBuilder.build(); + } + private static String getErrorMessage(HttpUriRequest request, CloseableHttpResponse response) { String controllerHost = null; String controllerVersion = null; diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClientConfig.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClientConfig.java new file mode 100644 index 00000000000..d424efbfaa6 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClientConfig.java @@ -0,0 +1,107 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.utils.http; + +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.spi.env.PinotConfiguration; + + +public class HttpClientConfig { + // Default config uses default values which are same as what Apache Commons Http-Client uses. + public static final HttpClientConfig DEFAULT_HTTP_CLIENT_CONFIG = HttpClientConfig.newBuilder().build(); + + protected static final String MAX_CONNS_CONFIG_NAME = "http.client.maxConnTotal"; + protected static final String MAX_CONNS_PER_ROUTE_CONFIG_NAME = "http.client.maxConnPerRoute"; + protected static final String DISABLE_DEFAULT_USER_AGENT_CONFIG_NAME = "http.client.disableDefaultUserAgent"; + + private final int _maxConnTotal; + private final int _maxConnPerRoute; + private final boolean _disableDefaultUserAgent; + + private HttpClientConfig(int maxConnTotal, int maxConnPerRoute, boolean disableDefaultUserAgent) { + _maxConnTotal = maxConnTotal; + _maxConnPerRoute = maxConnPerRoute; + _disableDefaultUserAgent = disableDefaultUserAgent; + } + + public int getMaxConnTotal() { + return _maxConnTotal; + } + + public int getMaxConnPerRoute() { + return _maxConnPerRoute; + } + + public boolean isDisableDefaultUserAgent() { + return _disableDefaultUserAgent; + } + + /** + * Creates a {@link HttpClientConfig.Builder} and initializes it with relevant configs from the provided + * configuration. Since http-clients are used in a bunch of places in the code, each use-case can have their own + * prefix for their config. The caller should call {@link PinotConfiguration#subset(String)} to remove their prefix + * and this builder will look for exact matches of its relevant configs. + */ + public static Builder newBuilder(PinotConfiguration pinotConfiguration) { + Builder builder = new Builder(); + String maxConns = pinotConfiguration.getProperty(MAX_CONNS_CONFIG_NAME); + if (StringUtils.isNotEmpty(maxConns)) { + builder.withMaxConns(Integer.parseInt(maxConns)); + } + String maxConnsPerRoute = pinotConfiguration.getProperty(MAX_CONNS_PER_ROUTE_CONFIG_NAME); + if (StringUtils.isNotEmpty(maxConnsPerRoute)) { + builder.withMaxConnsPerRoute(Integer.parseInt(maxConnsPerRoute)); + } + boolean disableDefaultUserAgent = pinotConfiguration.getProperty(DISABLE_DEFAULT_USER_AGENT_CONFIG_NAME, false); + builder.withDisableDefaultUserAgent(disableDefaultUserAgent); + return builder; + } + + private static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + private int _maxConns = -1; + private int _maxConnsPerRoute = -1; + private boolean _disableDefaultUserAgent = false; + + private Builder() { + } + + public Builder withMaxConns(int maxConns) { + _maxConns = maxConns; + return this; + } + + public Builder withMaxConnsPerRoute(int maxConnsPerRoute) { + _maxConnsPerRoute = maxConnsPerRoute; + return this; + } + + public Builder withDisableDefaultUserAgent(boolean disableDefaultUserAgent) { + _disableDefaultUserAgent = disableDefaultUserAgent; + return this; + } + + public HttpClientConfig build() { + return new HttpClientConfig(_maxConns, _maxConnsPerRoute, _disableDefaultUserAgent); + } + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java index deb26491b17..6ac7845c9d8 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java @@ -19,13 +19,17 @@ package org.apache.pinot.common.utils.request; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.apache.calcite.sql.SqlLiteral; import org.apache.calcite.sql.SqlNumericLiteral; import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.request.DataSource; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; @@ -44,10 +48,16 @@ public class RequestUtils { private static final Logger LOGGER = LoggerFactory.getLogger(RequestUtils.class); + private static final JsonNode EMPTY_OBJECT_NODE = new ObjectMapper().createObjectNode(); private RequestUtils() { } + public static SqlNodeAndOptions parseQuery(String query) + throws SqlCompilationException { + return parseQuery(query, EMPTY_OBJECT_NODE); + } + public static SqlNodeAndOptions parseQuery(String query, JsonNode request) throws SqlCompilationException { long parserStartTimeNs = System.nanoTime(); @@ -115,11 +125,13 @@ public static Expression getLiteralExpression(SqlLiteral node) { literal.setDoubleValue(node.bigDecimalValue().doubleValue()); } } else { - // TODO: Support null literal and other types. switch (node.getTypeName()) { case BOOLEAN: literal.setBoolValue(node.booleanValue()); break; + case NULL: + literal.setNullValue(true); + break; default: literal.setStringValue(StringUtils.replace(node.toValue(), "''", "'")); break; @@ -166,7 +178,16 @@ public static Expression getLiteralExpression(byte[] value) { return expression; } + public static Expression getNullLiteralExpression() { + Expression expression = createNewLiteralExpression(); + expression.getLiteral().setNullValue(true); + return expression; + } + public static Expression getLiteralExpression(Object object) { + if (object == null) { + return getNullLiteralExpression(); + } if (object instanceof Integer || object instanceof Long) { return RequestUtils.getLiteralExpression(((Number) object).longValue()); } @@ -247,11 +268,19 @@ public static String prettyPrint(Expression expression) { return null; } - public static String getTableName(PinotQuery pinotQuery) { - while (pinotQuery.getDataSource().getSubquery() != null) { - pinotQuery = pinotQuery.getDataSource().getSubquery(); + private static Set getTableNames(DataSource dataSource) { + if (dataSource.getSubquery() != null) { + return getTableNames(dataSource.getSubquery()); + } else if (dataSource.isSetJoin()) { + return ImmutableSet.builder() + .addAll(getTableNames(dataSource.getJoin().getLeft())) + .addAll(getTableNames(dataSource.getJoin().getLeft())).build(); } - return pinotQuery.getDataSource().getTableName(); + return ImmutableSet.of(dataSource.getTableName()); + } + + public static Set getTableNames(PinotQuery pinotQuery) { + return getTableNames(pinotQuery.getDataSource()); } public static Map getOptionsFromJson(JsonNode request, String optionsKey) { diff --git a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java index 57721f62e54..5d7581874f2 100644 --- a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java @@ -20,6 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; @@ -36,6 +37,7 @@ import org.apache.calcite.sql.SqlDataTypeSpec; import org.apache.calcite.sql.SqlExplain; import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlJoin; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlLiteral; import org.apache.calcite.sql.SqlNode; @@ -57,11 +59,12 @@ import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; +import org.apache.pinot.common.request.Join; +import org.apache.pinot.common.request.JoinType; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.segment.spi.AggregationFunctionType; -import org.apache.pinot.spi.utils.Pairs; import org.apache.pinot.sql.FilterKind; import org.apache.pinot.sql.parsers.parser.SqlInsertFromFile; import org.apache.pinot.sql.parsers.parser.SqlParserImpl; @@ -75,6 +78,11 @@ public class CalciteSqlParser { private CalciteSqlParser() { } + public static final String ASC = "asc"; + public static final String DESC = "desc"; + public static final String NULLS_LAST = "nullslast"; + public static final String NULLS_FIRST = "nullsfirst"; + public static final ImmutableSet ORDER_BY_FUNCTIONS = ImmutableSet.of(ASC, DESC, NULLS_LAST, NULLS_FIRST); public static final List QUERY_REWRITERS = new ArrayList<>(QueryRewriterFactory.getQueryRewriters()); private static final Logger LOGGER = LoggerFactory.getLogger(CalciteSqlParser.class); @@ -111,9 +119,6 @@ public static SqlNodeAndOptions compileToSqlNodeAndOptions(String sql) throws SqlCompilationException { long parseStartTimeNs = System.nanoTime(); - // Remove the comments from the query - sql = removeComments(sql); - // Remove the terminating semicolon from the query sql = removeTerminatingSemicolon(sql); @@ -253,7 +258,7 @@ private static void validateDistinctQuery(PinotQuery pinotQuery) List distinctExpressions = getAliasLeftExpressionsFromDistinctExpression(function); for (Expression orderByExpression : orderByList) { // NOTE: Order-by is always a Function with the ordering of the Expression - if (!distinctExpressions.contains(orderByExpression.getFunctionCall().getOperands().get(0))) { + if (!distinctExpressions.contains(removeOrderByFunctions(orderByExpression))) { throw new IllegalStateException("ORDER-BY columns should be included in the DISTINCT columns"); } } @@ -413,12 +418,7 @@ public static PinotQuery compileSqlNodeToPinotQuery(SqlNode sqlNode) { // FROM SqlNode fromNode = selectNode.getFrom(); if (fromNode != null) { - DataSource dataSource = new DataSource(); - dataSource.setTableName(fromNode.toString()); - pinotQuery.setDataSource(dataSource); - if (fromNode instanceof SqlSelect || fromNode instanceof SqlOrderBy) { - dataSource.setSubquery(compileSqlNodeToPinotQuery(fromNode)); - } + pinotQuery.setDataSource(compileToDataSource(fromNode)); } // WHERE SqlNode whereNode = selectNode.getWhere(); @@ -455,6 +455,62 @@ public static PinotQuery compileSqlNodeToPinotQuery(SqlNode sqlNode) { return pinotQuery; } + private static DataSource compileToDataSource(SqlNode sqlNode) { + DataSource dataSource = new DataSource(); + switch (sqlNode.getKind()) { + case IDENTIFIER: + dataSource.setTableName(sqlNode.toString()); + break; + case AS: + List operandList = ((SqlBasicCall) sqlNode).getOperandList(); + dataSource.setSubquery(compileSqlNodeToPinotQuery(operandList.get(0))); + dataSource.setTableName(operandList.get(1).toString()); + break; + case SELECT: + case ORDER_BY: + dataSource.setSubquery(compileSqlNodeToPinotQuery(sqlNode)); + break; + case JOIN: + dataSource.setJoin(compileToJoin((SqlJoin) sqlNode)); + break; + default: + throw new IllegalStateException("Unsupported SQL node kind as DataSource: " + sqlNode.getKind()); + } + return dataSource; + } + + private static Join compileToJoin(SqlJoin sqlJoin) { + Join join = new Join(); + switch (sqlJoin.getJoinType()) { + case INNER: + join.setType(JoinType.INNER); + break; + case LEFT: + join.setType(JoinType.LEFT); + break; + case RIGHT: + join.setType(JoinType.RIGHT); + break; + case FULL: + join.setType(JoinType.FULL); + break; + default: + throw new IllegalStateException("Unsupported join type: " + sqlJoin.getJoinType()); + } + join.setLeft(compileToDataSource(sqlJoin.getLeft())); + join.setRight(compileToDataSource(sqlJoin.getRight())); + switch (sqlJoin.getConditionType()) { + case ON: + join.setCondition(toExpression(sqlJoin.getCondition())); + break; + case NONE: + break; + default: + throw new IllegalStateException("Unsupported join condition type: " + sqlJoin.getConditionType()); + } + return join; + } + private static void queryRewrite(PinotQuery pinotQuery) { for (QueryRewriter queryRewriter : QUERY_REWRITERS) { pinotQuery = queryRewriter.rewrite(pinotQuery); @@ -494,103 +550,6 @@ private static Map extractOptionsMap(List optionsStateme return options; } - /** - * Removes comments from the query. - * NOTE: Comment indicator within single quotes (literal) and double quotes (identifier) are ignored. - */ - @VisibleForTesting - static String removeComments(String sql) { - boolean openSingleQuote = false; - boolean openDoubleQuote = false; - boolean commented = false; - boolean singleLineCommented = false; - boolean multiLineCommented = false; - int commentStartIndex = -1; - List commentedParts = new ArrayList<>(); - - int length = sql.length(); - int index = 0; - while (index < length) { - switch (sql.charAt(index)) { - case '\'': - if (!commented && !openDoubleQuote) { - openSingleQuote = !openSingleQuote; - } - break; - case '"': - if (!commented && !openSingleQuote) { - openDoubleQuote = !openDoubleQuote; - } - break; - case '-': - // Single line comment start indicator: -- - if (!commented && !openSingleQuote && !openDoubleQuote && index < length - 1 - && sql.charAt(index + 1) == '-') { - commented = true; - singleLineCommented = true; - commentStartIndex = index; - index++; - } - break; - case '\n': - // Single line comment end indicator: \n - if (singleLineCommented) { - commentedParts.add(new Pairs.IntPair(commentStartIndex, index + 1)); - commented = false; - singleLineCommented = false; - commentStartIndex = -1; - } - break; - case '/': - // Multi-line comment start indicator: /* - if (!commented && !openSingleQuote && !openDoubleQuote && index < length - 1 - && sql.charAt(index + 1) == '*') { - commented = true; - multiLineCommented = true; - commentStartIndex = index; - index++; - } - break; - case '*': - // Multi-line comment end indicator: */ - if (multiLineCommented && index < length - 1 && sql.charAt(index + 1) == '/') { - commentedParts.add(new Pairs.IntPair(commentStartIndex, index + 2)); - commented = false; - multiLineCommented = false; - commentStartIndex = -1; - index++; - } - break; - default: - break; - } - index++; - } - - if (commentedParts.isEmpty()) { - if (singleLineCommented) { - return sql.substring(0, commentStartIndex); - } else { - return sql; - } - } else { - StringBuilder stringBuilder = new StringBuilder(); - int startIndex = 0; - for (Pairs.IntPair commentedPart : commentedParts) { - stringBuilder.append(sql, startIndex, commentedPart.getLeft()).append(' '); - startIndex = commentedPart.getRight(); - } - if (startIndex < length) { - if (singleLineCommented) { - stringBuilder.append(sql, startIndex, commentStartIndex); - } else { - stringBuilder.append(sql, startIndex, length); - } - } - return stringBuilder.toString(); - } - } - private static List convertDistinctSelectList(SqlNodeList selectList) { List selectExpr = new ArrayList<>(); selectExpr.add(convertDistinctAndSelectListToFunctionExpression(selectList)); @@ -611,23 +570,34 @@ private static List convertSelectList(SqlNodeList selectList) { private static List convertOrderByList(SqlNodeList orderList) { List orderByExpr = new ArrayList<>(); - final Iterator iterator = orderList.iterator(); - while (iterator.hasNext()) { - final SqlNode next = iterator.next(); - orderByExpr.add(convertOrderBy(next)); + for (SqlNode sqlNode : orderList) { + orderByExpr.add(convertOrderBy(sqlNode, true)); } return orderByExpr; } - private static Expression convertOrderBy(SqlNode node) { + private static Expression convertOrderBy(SqlNode node, boolean createAscExpression) { + // If the order is ASC, the SqlNode will not have an ASC operator. In this case we need to create an ASC function in + // the expression. + // The SqlNode puts the NULLS FIRST/LAST operator in an outer level of the DESC operator. Expression expression; - if (node.getKind() == SqlKind.DESCENDING) { + if (node.getKind() == SqlKind.NULLS_LAST) { SqlBasicCall basicCall = (SqlBasicCall) node; - expression = RequestUtils.getFunctionExpression("desc"); - expression.getFunctionCall().addToOperands(toExpression(basicCall.getOperandList().get(0))); - } else { - expression = RequestUtils.getFunctionExpression("asc"); + expression = RequestUtils.getFunctionExpression(NULLS_LAST); + expression.getFunctionCall().addToOperands(convertOrderBy(basicCall.getOperandList().get(0), true)); + } else if (node.getKind() == SqlKind.NULLS_FIRST) { + SqlBasicCall basicCall = (SqlBasicCall) node; + expression = RequestUtils.getFunctionExpression(NULLS_FIRST); + expression.getFunctionCall().addToOperands(convertOrderBy(basicCall.getOperandList().get(0), true)); + } else if (node.getKind() == SqlKind.DESCENDING) { + SqlBasicCall basicCall = (SqlBasicCall) node; + expression = RequestUtils.getFunctionExpression(DESC); + expression.getFunctionCall().addToOperands(convertOrderBy(basicCall.getOperandList().get(0), false)); + } else if (createAscExpression) { + expression = RequestUtils.getFunctionExpression(ASC); expression.getFunctionCall().addToOperands(toExpression(node)); + } else { + return toExpression(node); } return expression; } @@ -713,15 +683,16 @@ private static Expression toExpression(SqlNode node) { SqlNode elseOperand = caseSqlNode.getElseOperand(); Expression caseFuncExpr = RequestUtils.getFunctionExpression("case"); Preconditions.checkState(whenOperands.size() == thenOperands.size()); - for (int i = 0; i < whenOperands.size(); i++) { - SqlNode whenSqlNode = whenOperands.get(i); + // TODO: convert this to new format once 0.13 is released + for (SqlNode whenSqlNode : whenOperands.getList()) { Expression whenExpression = toExpression(whenSqlNode); if (isAggregateExpression(whenExpression)) { throw new SqlCompilationException( "Aggregation functions inside WHEN Clause is not supported - " + whenSqlNode); } caseFuncExpr.getFunctionCall().addToOperands(whenExpression); - SqlNode thenSqlNode = thenOperands.get(i); + } + for (SqlNode thenSqlNode : thenOperands.getList()) { Expression thenExpression = toExpression(thenSqlNode); if (isAggregateExpression(thenExpression)) { throw new SqlCompilationException( @@ -964,4 +935,11 @@ public static boolean isLiteralOnlyExpression(Expression e) { } return false; } + + public static Expression removeOrderByFunctions(Expression expression) { + while (expression.isSetFunctionCall() && ORDER_BY_FUNCTIONS.contains(expression.getFunctionCall().operator)) { + expression = expression.getFunctionCall().getOperands().get(0); + } + return expression; + } } diff --git a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/hints/PinotRelationalHints.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/parser/SqlPhysicalExplain.java similarity index 53% rename from pinot-query-planner/src/main/java/org/apache/pinot/query/planner/hints/PinotRelationalHints.java rename to pinot-common/src/main/java/org/apache/pinot/sql/parsers/parser/SqlPhysicalExplain.java index 2c4cb976a6b..2062a5af675 100644 --- a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/hints/PinotRelationalHints.java +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/parser/SqlPhysicalExplain.java @@ -16,21 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.query.planner.hints; - -import org.apache.calcite.rel.hint.RelHint; +package org.apache.pinot.sql.parsers.parser; +import org.apache.calcite.sql.SqlExplain; +import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.parser.SqlParserPos; /** - * Provide certain relational hint to query planner for better optimization. + * Calcite extension for creating a physical plan sql node from a EXPLAIN IMPLEMENTATION query. + * + *

Syntax: EXPLAIN IMPLEMENTATION PLAN [ [INCLUDING | EXCLUDING] [ALL] ATTRIBUTES ] FOR SELECT

*/ -public class PinotRelationalHints { - public static final RelHint USE_HASH_DISTRIBUTE = RelHint.builder("USE_HASH_DISTRIBUTE").build(); - public static final RelHint USE_BROADCAST_DISTRIBUTE = RelHint.builder("USE_BROADCAST_DISTRIBUTE").build(); - public static final RelHint AGG_INTERMEDIATE_STAGE = RelHint.builder("AGG_INTERMEDIATE_STAGE").build(); - public static final RelHint AGG_LEAF_STAGE = RelHint.builder("AGG_LEAF_STAGE").build(); - - private PinotRelationalHints() { - // do not instantiate. +public class SqlPhysicalExplain extends SqlExplain { + public SqlPhysicalExplain(SqlParserPos pos, SqlNode explicandum, SqlLiteral detailLevel, SqlLiteral depth, + SqlLiteral format, int dynamicParameterCount) { + super(pos, explicandum, detailLevel, depth, format, dynamicParameterCount); } } diff --git a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/ArgMinMaxRewriter.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/ArgMinMaxRewriter.java new file mode 100644 index 00000000000..9fd29be69c8 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/ArgMinMaxRewriter.java @@ -0,0 +1,192 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.sql.parsers.rewriter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.ExpressionType; +import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Literal; +import org.apache.pinot.common.request.PinotQuery; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * This rewriter rewrites ARG_MIN/ARG_MAX function, so that the functions with the same measuring expressions + * are consolidated and added as a single function with a list of projection expressions. For example, the query + * "SELECT ARG_MIN(col1, col2, col3), ARG_MIN(col1, col2, col4) FROM myTable" will be consolidated to a single + * function "PARENT_ARG_MIN(0, 2, col1, col2, col3, col4)". and added to the end of the selection list. + * While the original ARG_MIN(col1, col2, col3) and ARG_MIN(col1, col2, col4) will be rewritten to + * CHILD_ARG_MIN(0, col3, col1, col2, col3) and CHILD_ARG_MIN(0, col4, col1, col2, col4) respectively. + * The 2 new parameters for CHILD_ARG_MIN are the function ID (0) and the projection column (col1/col4), + * used as column key in the parent aggregation result, during result rewriting. + * PARENT_ARG_MIN(0, 2, col1, col2, col3, col4) means a parent aggregation function with function ID 0, + * 2 measuring columns (col1, col2), 2 projection columns (col3, col4). The function ID is unique for each + * consolidated function with the same function type and measuring columns. + * Later, the aggregation, result of the consolidated function will be filled into the corresponding + * columns of the original ARG_MIN/ARG_MAX. For more syntax details please refer to ParentAggregationFunction, + * ChildAggregationFunction and ChildAggregationResultRewriter. + */ +public class ArgMinMaxRewriter implements QueryRewriter { + + private static final String ARG_MAX = "argmax"; + private static final String ARG_MIN = "argmin"; + + private static final String ARG_MAX_PARENT = + CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX + ARG_MAX; + private static final String ARG_MIN_PARENT = + CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX + ARG_MIN; + + @Override + public PinotQuery rewrite(PinotQuery pinotQuery) { + // This map stores the mapping from the list of measuring expressions to the set of projection expressions + HashMap, Set> argMinFunctionMap = new HashMap<>(); + // This map stores the mapping from the list of measuring expressions to the function ID + HashMap, Integer> argMinFunctionIDMap = new HashMap<>(); + + HashMap, Set> argMaxFunctionMap = new HashMap<>(); + HashMap, Integer> argMaxFunctionIDMap = new HashMap<>(); + + Iterator iterator = pinotQuery.getSelectList().iterator(); + while (iterator.hasNext()) { + boolean added = extractAndRewriteArgMinMaxFunctions(iterator.next(), argMaxFunctionMap, argMaxFunctionIDMap, + argMinFunctionMap, argMinFunctionIDMap); + // Remove the original function if it is not added, meaning it is a duplicate + if (!added) { + iterator.remove(); + } + } + + appendParentArgMinMaxFunctions(false, pinotQuery.getSelectList(), argMinFunctionMap, argMinFunctionIDMap); + appendParentArgMinMaxFunctions(true, pinotQuery.getSelectList(), argMaxFunctionMap, argMaxFunctionIDMap); + + return pinotQuery; + } + + /** + * This method appends the consolidated ARG_MIN/ARG_MAX functions to the end of the selection list. + * The consolidated function call will be in the following format: + * ARG_MAX(functionID, numMeasuringColumns, measuringColumn1, measuringColumn2, ... projectionColumn1, + * projectionColumn2, ...) + * where functionID is the ID of the consolidated function, numMeasuringColumns is the number of measuring + * columns, measuringColumn1, measuringColumn2, ... are the measuring columns, and projectionColumn1, + * projectionColumn2, ... are the projection columns. + * The number of projection columns is the same as the number of ARG_MIN/ARG_MAX functions with the same + * measuring columns. + */ + private void appendParentArgMinMaxFunctions(boolean isMax, List selectList, + HashMap, Set> argMinMaxFunctionMap, + HashMap, Integer> argMinMaxFunctionIDMap) { + for (Map.Entry, Set> entry : argMinMaxFunctionMap.entrySet()) { + Literal functionID = new Literal(); + functionID.setLongValue(argMinMaxFunctionIDMap.get(entry.getKey())); + Literal numMeasuringColumns = new Literal(); + numMeasuringColumns.setLongValue(entry.getKey().size()); + + Function parentFunction = new Function(isMax ? ARG_MAX_PARENT : ARG_MIN_PARENT); + parentFunction.addToOperands(new Expression(ExpressionType.LITERAL).setLiteral(functionID)); + parentFunction.addToOperands(new Expression(ExpressionType.LITERAL).setLiteral(numMeasuringColumns)); + for (Expression expression : entry.getKey()) { + parentFunction.addToOperands(expression); + } + for (Expression expression : entry.getValue()) { + parentFunction.addToOperands(expression); + } + selectList.add(new Expression(ExpressionType.FUNCTION).setFunctionCall(parentFunction)); + } + } + + /** + * This method extracts the ARG_MIN/ARG_MAX functions from the given expression and rewrites the functions + * with the same measuring expressions to use the same function ID. + * @return true if the function is not duplicated, false otherwise. + */ + private boolean extractAndRewriteArgMinMaxFunctions(Expression expression, + HashMap, Set> argMaxFunctionMap, + HashMap, Integer> argMaxFunctionIDMap, + HashMap, Set> argMinFunctionMap, + HashMap, Integer> argMinFunctionIDMap) { + Function function = expression.getFunctionCall(); + if (function == null) { + return true; + } + String functionName = function.getOperator(); + if (!(functionName.equals(ARG_MIN) || functionName.equals(ARG_MAX))) { + return true; + } + List operands = function.getOperands(); + if (operands.size() < 2) { + throw new IllegalStateException("Invalid number of arguments for " + functionName + ", argmin/argmax should " + + "have at least 2 arguments, got: " + operands.size()); + } + List argMinMaxMeasuringExpressions = new ArrayList<>(); + for (int i = 0; i < operands.size() - 1; i++) { + argMinMaxMeasuringExpressions.add(operands.get(i)); + } + Expression argMinMaxProjectionExpression = operands.get(operands.size() - 1); + + if (functionName.equals(ARG_MIN)) { + return updateArgMinMaxFunctionMap(argMinMaxMeasuringExpressions, argMinMaxProjectionExpression, argMinFunctionMap, + argMinFunctionIDMap, function); + } else { + return updateArgMinMaxFunctionMap(argMinMaxMeasuringExpressions, argMinMaxProjectionExpression, argMaxFunctionMap, + argMaxFunctionIDMap, function); + } + } + + /** + * This method rewrites the ARG_MIN/ARG_MAX function with the given measuring expressions to use the same + * function ID. + * @return true if the function is not duplicated, false otherwise. + */ + private boolean updateArgMinMaxFunctionMap(List argMinMaxMeasuringExpressions, + Expression argMinMaxProjectionExpression, HashMap, Set> argMinMaxFunctionMap, + HashMap, Integer> argMinMaxFunctionIDMap, Function function) { + int size = argMinMaxFunctionIDMap.size(); + int id = argMinMaxFunctionIDMap.computeIfAbsent(argMinMaxMeasuringExpressions, (k) -> size); + + AtomicBoolean added = new AtomicBoolean(true); + + argMinMaxFunctionMap.compute(argMinMaxMeasuringExpressions, (k, v) -> { + if (v == null) { + v = new HashSet<>(); + } + added.set(v.add(argMinMaxProjectionExpression)); + return v; + }); + + String operator = function.operator; + function.setOperator(CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX + operator); + + List operands = function.getOperands(); + operands.add(0, argMinMaxProjectionExpression); + Literal functionID = new Literal(); + functionID.setLongValue(id); + operands.add(0, new Expression(ExpressionType.LITERAL).setLiteral(functionID)); + + return added.get(); + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CLPDecodeRewriter.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CLPDecodeRewriter.java new file mode 100644 index 00000000000..c870f84bb04 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CLPDecodeRewriter.java @@ -0,0 +1,177 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.sql.parsers.rewriter; + +import java.util.List; +import javax.annotation.Nullable; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.ExpressionType; +import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Identifier; +import org.apache.pinot.common.request.Literal; +import org.apache.pinot.common.request.PinotQuery; +import org.apache.pinot.sql.parsers.SqlCompilationException; + + +/** + * Query rewriter to rewrite clpDecode so that users can pass in the name of a CLP-encoded column group instead of the + * names of all the columns in the group. + *

+ * Usage: + *

+ *   clpDecode("columnGroupName"[, defaultValue])
+ * 
+ * which will be rewritten to: + *
+ *   clpDecode("columnGroupName_logtype", "columnGroupName_dictionaryVars", "columnGroupName_encodedVars"[,
+ *   defaultValue])
+ * 
+ * The "defaultValue" is optional. See + * {@link org.apache.pinot.core.operator.transform.function.CLPDecodeTransformFunction} for its description. + *

+ * Sample queries: + *

+ *   SELECT clpDecode("message") FROM table
+ *   SELECT clpDecode("message", 'null') FROM table
+ * 
+ * See {@link org.apache.pinot.core.operator.transform.function.CLPDecodeTransformFunction} for details about the + * underlying clpDecode transformer. + */ +public class CLPDecodeRewriter implements QueryRewriter { + public static final String LOGTYPE_COLUMN_SUFFIX = "_logtype"; + public static final String DICTIONARY_VARS_COLUMN_SUFFIX = "_dictionaryVars"; + public static final String ENCODED_VARS_COLUMN_SUFFIX = "_encodedVars"; + + private static final String _CLPDECODE_LOWERCASE_TRANSFORM_NAME = + TransformFunctionType.CLPDECODE.getName().toLowerCase(); + + @Override + public PinotQuery rewrite(PinotQuery pinotQuery) { + List selectExpressions = pinotQuery.getSelectList(); + if (null != selectExpressions) { + for (Expression e : selectExpressions) { + tryRewritingExpression(e); + } + } + List groupByExpressions = pinotQuery.getGroupByList(); + if (null != groupByExpressions) { + for (Expression e : groupByExpressions) { + tryRewritingExpression(e); + } + } + List orderByExpressions = pinotQuery.getOrderByList(); + if (null != orderByExpressions) { + for (Expression e : orderByExpressions) { + tryRewritingExpression(e); + } + } + tryRewritingExpression(pinotQuery.getFilterExpression()); + tryRewritingExpression(pinotQuery.getHavingExpression()); + return pinotQuery; + } + + /** + * Rewrites any instances of clpDecode in the given expression + * @param expression Expression which may contain instances of clpDecode + */ + private void tryRewritingExpression(Expression expression) { + if (null == expression) { + return; + } + Function function = expression.getFunctionCall(); + if (null == function) { + return; + } + + String functionName = function.getOperator(); + if (functionName.equals(_CLPDECODE_LOWERCASE_TRANSFORM_NAME)) { + rewriteCLPDecodeFunction(expression); + return; + } + + // Function isn't a CLP function that needs rewriting, but the arguments might be, so we recursively process them. + for (Expression op : function.getOperands()) { + tryRewritingExpression(op); + } + } + + /** + * Rewrites the given instance of clpDecode as described in the class' Javadoc + * @param expression clpDecode function expression + */ + private void rewriteCLPDecodeFunction(Expression expression) { + Function function = expression.getFunctionCall(); + List arguments = function.getOperands(); + + // Validate clpDecode's arguments + int numArgs = arguments.size(); + if (numArgs < 1 || numArgs > 2) { + // Too few/many args for this rewriter, so do nothing and let it pass through to the clpDecode transform function + return; + } + + Expression arg0 = arguments.get(0); + if (ExpressionType.IDENTIFIER != arg0.getType()) { + throw new SqlCompilationException("clpDecode: 1st argument must be a column group name (identifier)."); + } + String columnGroupName = arg0.getIdentifier().getName(); + + Literal defaultValueLiteral = null; + if (numArgs > 1) { + Expression arg1 = arguments.get(1); + if (ExpressionType.LITERAL != arg1.getType()) { + throw new SqlCompilationException("clpDecode: 2nd argument must be a default value (literal)."); + } + defaultValueLiteral = arg1.getLiteral(); + } + + // Replace the columnGroup with the individual columns + arguments.clear(); + addCLPDecodeOperands(columnGroupName, defaultValueLiteral, function); + } + + /** + * Adds the CLPDecode transform function's operands to the given function + * @param columnGroupName Name of the CLP-encoded column group + * @param defaultValueLiteral Optional default value to pass through to the transform function + * @param clpDecode The function to add the operands to + */ + private void addCLPDecodeOperands(String columnGroupName, @Nullable Literal defaultValueLiteral, Function clpDecode) { + Expression e; + + e = new Expression(ExpressionType.IDENTIFIER); + e.setIdentifier(new Identifier(columnGroupName + LOGTYPE_COLUMN_SUFFIX)); + clpDecode.addToOperands(e); + + e = new Expression(ExpressionType.IDENTIFIER); + e.setIdentifier(new Identifier(columnGroupName + DICTIONARY_VARS_COLUMN_SUFFIX)); + clpDecode.addToOperands(e); + + e = new Expression(ExpressionType.IDENTIFIER); + e.setIdentifier(new Identifier(columnGroupName + ENCODED_VARS_COLUMN_SUFFIX)); + clpDecode.addToOperands(e); + + if (null != defaultValueLiteral) { + e = new Expression(ExpressionType.LITERAL); + e.setLiteral(defaultValueLiteral); + clpDecode.addToOperands(e); + } + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CompileTimeFunctionsInvoker.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CompileTimeFunctionsInvoker.java index 1c93d64e855..2cb89d5fe8b 100644 --- a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CompileTimeFunctionsInvoker.java +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/CompileTimeFunctionsInvoker.java @@ -26,6 +26,7 @@ import org.apache.pinot.common.function.FunctionRegistry; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Literal; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.sql.parsers.SqlCompilationException; @@ -74,7 +75,12 @@ protected static Expression invokeCompileTimeFunctionExpression(@Nullable Expres if (functionInfo != null) { Object[] arguments = new Object[numOperands]; for (int i = 0; i < numOperands; i++) { - arguments[i] = function.getOperands().get(i).getLiteral().getFieldValue(); + Literal literal = function.getOperands().get(i).getLiteral(); + if (literal.isSetNullValue()) { + arguments[i] = null; + } else { + arguments[i] = function.getOperands().get(i).getLiteral().getFieldValue(); + } } try { FunctionInvoker invoker = new FunctionInvoker(functionInfo); diff --git a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/QueryRewriterFactory.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/QueryRewriterFactory.java index 4ae6c1bd93a..ef36ee1080c 100644 --- a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/QueryRewriterFactory.java +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/QueryRewriterFactory.java @@ -33,7 +33,7 @@ private QueryRewriterFactory() { private static final Logger LOGGER = LoggerFactory.getLogger(QueryRewriterFactory.class); - static final List DEFAULT_QUERY_REWRITERS_CLASS_NAMES = + public static final List DEFAULT_QUERY_REWRITERS_CLASS_NAMES = ImmutableList.of(CompileTimeFunctionsInvoker.class.getName(), SelectionsRewriter.class.getName(), PredicateComparisonRewriter.class.getName(), OrdinalsUpdater.class.getName(), AliasApplier.class.getName(), NonAggregationGroupByToDistinctQueryRewriter.class.getName()); diff --git a/pinot-common/src/main/proto/worker.proto b/pinot-common/src/main/proto/worker.proto index 8f780bd260a..dfb1cd53eb0 100644 --- a/pinot-common/src/main/proto/worker.proto +++ b/pinot-common/src/main/proto/worker.proto @@ -44,12 +44,22 @@ import "plan.proto"; service PinotQueryWorker { // Dispatch a QueryRequest to a PinotQueryWorker rpc Submit(QueryRequest) returns (QueryResponse); + + rpc Cancel(CancelRequest) returns (CancelResponse); +} + +message CancelRequest { + int64 requestId = 1; } -// QueryRequest is the dispatched content for a specific query stage on a specific worker. +message CancelResponse { + // intentionally left empty +} + +// QueryRequest is the dispatched content for all query stages to a physical worker. message QueryRequest { - map metadata = 1; - StagePlan stagePlan = 2; + repeated StagePlan stagePlan = 1; + map metadata = 2; } // QueryResponse is the dispatched response from worker, it doesn't contain actual data, only dispatch status. @@ -60,23 +70,25 @@ message QueryResponse { message StagePlan { int32 stageId = 1; - string instanceId = 2; - StageNode stageRoot = 3; - map stageMetadata = 4; + StageNode stageRoot = 2; + StageMetadata stageMetadata = 3; } message StageMetadata { - repeated string instances = 1; - repeated string dataSources = 2; - map instanceToSegmentMap = 3; - string timeColumn = 4; - string timeValue = 5; + repeated WorkerMetadata workerMetadata = 1; + map customProperty = 2; + string serverAddress = 3; + repeated int32 workerIds = 4; } -message SegmentMap { - map tableTypeToSegmentList = 1; +message WorkerMetadata { + string virtualAddress = 1; + map mailboxMetadata = 2; + map customProperty = 3; } -message SegmentList { - repeated string segments = 1; +message MailboxMetadata { + repeated string mailboxId = 1; + repeated string virtualAddress = 2; + map customProperty = 3; } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java b/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java index 1f055a4d09f..0f85f4a69ee 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.HashMap; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -37,7 +38,7 @@ public void shouldEncodeContentsAsJSON() MetadataBlock metadataBlock = new MetadataBlock(type); // Then: - byte[] expected = new ObjectMapper().writeValueAsBytes(new MetadataBlock.Contents("EOS")); + byte[] expected = new ObjectMapper().writeValueAsBytes(new MetadataBlock.Contents("EOS", new HashMap<>())); assertEquals(metadataBlock._variableSizeDataBytes, expected); } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/function/FunctionDefinitionRegistryTest.java b/pinot-common/src/test/java/org/apache/pinot/common/function/FunctionDefinitionRegistryTest.java index f473a01011e..d5370fe79cf 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/function/FunctionDefinitionRegistryTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/function/FunctionDefinitionRegistryTest.java @@ -42,9 +42,9 @@ public class FunctionDefinitionRegistryTest { ); private static final List IGNORED_FUNCTION_NAMES = ImmutableList.of( // functions we are not supporting post transform anyway - "valuein", "mapvalue", "inidset", "lookup", "groovy", "scalar", "geotoh3", "case", "not_in", "timeconvert", + "valuein", "mapvalue", "inidset", "lookup", "groovy", "scalar", "geotoh3", "not_in", "timeconvert", // functions not needed for register b/c they are in std sql table or they will not be composed directly. - "in", "and", "or", "not", "range", "extract" + "in", "and", "or", "range", "extract" ); @Test @@ -68,7 +68,7 @@ public void testCalciteFunctionMapAllRegistered() { } for (TransformFunctionType enumType : TransformFunctionType.values()) { if (!isIgnored(enumType.getName().toLowerCase())) { - for (String funcName : enumType.getAliases()) { + for (String funcName : enumType.getAlternativeNames()) { assertTrue(registeredCalciteFunctionNameIgnoreCase.contains(funcName.toLowerCase()), "Unable to find transform function signature for: " + funcName); } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java b/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java index ee98705f107..9ca44e7fc60 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java @@ -258,7 +258,7 @@ public void testJsonFunctionOnObjectArray() public static Object[][] jsonPathStringTestCases() { return new Object[][]{ {ImmutableMap.of("foo", "x", "bar", ImmutableMap.of("foo", "y")), "$.foo", "x"}, - {ImmutableMap.of("foo", "x", "bar", ImmutableMap.of("foo", "y")), "$.qux", "null"}, + {ImmutableMap.of("foo", "x", "bar", ImmutableMap.of("foo", "y")), "$.qux", null}, {ImmutableMap.of("foo", "x", "bar", ImmutableMap.of("foo", "y")), "$.bar", "{\"foo\":\"y\"}"}, }; } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayAwareJacksonJsonProviderTest.java b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayAwareJacksonJsonProviderTest.java index 3f16ed83ddd..6a9f651ea2a 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayAwareJacksonJsonProviderTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayAwareJacksonJsonProviderTest.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import org.apache.pinot.segment.spi.utils.JavaVersion; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -105,7 +106,10 @@ public void testToIterable() { } catch (NullPointerException e) { // It's supposed to get a JsonPathException, but JsonPath library actually // has a bug leading to NullPointerException while creating the JsonPathException. - assertNull(e.getMessage()); + if (JavaVersion.VERSION < 14) { + // In modern Java versions messages is something like "Cannot invoke "Object.getClass()" because "obj" is null" + assertNull(e.getMessage()); + } } } } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/lineage/SegmentLineageTest.java b/pinot-common/src/test/java/org/apache/pinot/common/lineage/SegmentLineageTest.java index 52f64c7902a..2c61962a3a5 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/lineage/SegmentLineageTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/lineage/SegmentLineageTest.java @@ -111,4 +111,28 @@ public void testSegmentLineage() { } Assert.assertEquals(segmentLineage.getLineageEntryIds().size(), 0); } + + @Test + public void teatSegmentLineageEntryEquals() { + LineageEntry expectedLineageEntry = + new LineageEntry(Arrays.asList("seg1", "seg2"), Arrays.asList("seg3", "seg4"), LineageEntryState.IN_PROGRESS, + 12345L); + LineageEntry actualLineageEntry = + new LineageEntry(Arrays.asList("seg1", "seg2"), Arrays.asList("seg3", "seg4"), LineageEntryState.IN_PROGRESS, + 12345L); + Assert.assertEquals(actualLineageEntry, expectedLineageEntry); + actualLineageEntry = + new LineageEntry(Arrays.asList("seg1", "seg2"), Arrays.asList("seg3", "seg4"), LineageEntryState.IN_PROGRESS, + 12346L); + Assert.assertNotEquals(actualLineageEntry, expectedLineageEntry); + actualLineageEntry = + new LineageEntry(Arrays.asList("seg1"), Arrays.asList("seg3", "seg4"), LineageEntryState.IN_PROGRESS, 12345L); + Assert.assertNotEquals(actualLineageEntry, expectedLineageEntry); + actualLineageEntry = + new LineageEntry(Arrays.asList("seg1"), Arrays.asList("seg3", "seg4"), LineageEntryState.COMPLETED, 12345L); + Assert.assertNotEquals(actualLineageEntry, expectedLineageEntry); + actualLineageEntry = + new LineageEntry(Arrays.asList("seg1"), Arrays.asList("seg3", "seg4"), LineageEntryState.REVERTED, 12345L); + Assert.assertNotEquals(actualLineageEntry, expectedLineageEntry); + } } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/metadata/MetadataEqualsHashCodeTest.java b/pinot-common/src/test/java/org/apache/pinot/common/metadata/MetadataEqualsHashCodeTest.java index c7dfccaa84d..b2a6236337e 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/metadata/MetadataEqualsHashCodeTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/metadata/MetadataEqualsHashCodeTest.java @@ -39,15 +39,18 @@ public class MetadataEqualsHashCodeTest { public void testEqualsAndHashCode() { EqualsVerifier.forClass(InstanceZKMetadata.class).suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS) .usingGetClass().verify(); - EqualsVerifier.forClass(Schema.class).suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS).usingGetClass() + EqualsVerifier.forClass(DimensionFieldSpec.class).suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS) + .usingGetClass().verify(); + EqualsVerifier.forClass(MetricFieldSpec.class).suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS) + .usingGetClass().verify(); + EqualsVerifier.forClass(TimeFieldSpec.class).suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS).usingGetClass() + .verify(); + EqualsVerifier.forClass(DateTimeFieldSpec.class).suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS) + .usingGetClass().verify(); + // NOTE: Suppress Warning.ALL_FIELDS_SHOULD_BE_USED because some fields are derived from other fields, but we cannot + // declare them as transitive because they are still needed after ser/de + EqualsVerifier.forClass(Schema.class) + .suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS, Warning.ALL_FIELDS_SHOULD_BE_USED).usingGetClass() .verify(); - EqualsVerifier.forClass(DimensionFieldSpec.class) - .suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS).usingGetClass().verify(); - EqualsVerifier.forClass(MetricFieldSpec.class) - .suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS).usingGetClass().verify(); - EqualsVerifier.forClass(TimeFieldSpec.class) - .suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS).usingGetClass().verify(); - EqualsVerifier.forClass(DateTimeFieldSpec.class) - .suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS).usingGetClass().verify(); } } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/tier/TierConfigUtilsTest.java b/pinot-common/src/test/java/org/apache/pinot/common/tier/TierConfigUtilsTest.java index 2b942a1cab7..75200fcb4ee 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/tier/TierConfigUtilsTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/tier/TierConfigUtilsTest.java @@ -192,30 +192,21 @@ public void testTierComparator() { @Test public void testGetDataDirForTier() { TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable").build(); - try { - TierConfigUtils.getDataDirForTier(tableConfig, "tier1"); - } catch (Exception e) { - Assert.assertEquals(e.getMessage(), "No dataDir for tier: tier1 for table: myTable_OFFLINE"); - } + String dataDir = TierConfigUtils.getDataDirForTier(tableConfig, "tier1"); + Assert.assertNull(dataDir); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable").setTierConfigList(Lists .newArrayList(new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, TierFactory.PINOT_SERVER_STORAGE_TYPE, "tag_OFFLINE", null, null))).build(); - try { - TierConfigUtils.getDataDirForTier(tableConfig, "tier1"); - } catch (Exception e) { - Assert.assertEquals(e.getMessage(), "No dataDir for tier: tier1 for table: myTable_OFFLINE"); - } - try { - TierConfigUtils.getDataDirForTier(tableConfig, "myTier"); - } catch (Exception e) { - Assert.assertEquals(e.getMessage(), "No dataDir for tier: myTier for table: myTable_OFFLINE"); - } + dataDir = TierConfigUtils.getDataDirForTier(tableConfig, "tier1"); + Assert.assertNull(dataDir); + dataDir = TierConfigUtils.getDataDirForTier(tableConfig, "myTier"); + Assert.assertNull(dataDir); // Provide instance tierConfigs for the tier. Map> instanceTierConfigs = new HashMap<>(); Map tierCfgMap = new HashMap<>(); tierCfgMap.put("datadir", "/abc/xyz"); instanceTierConfigs.put("myTier", tierCfgMap); - String dataDir = TierConfigUtils.getDataDirForTier(tableConfig, "myTier", instanceTierConfigs); + dataDir = TierConfigUtils.getDataDirForTier(tableConfig, "myTier", instanceTierConfigs); Assert.assertEquals(dataDir, "/abc/xyz"); // Table tierConfigs overwrite those from instance tierConfigs. tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable").setTierConfigList(Lists diff --git a/pinot-common/src/test/java/org/apache/pinot/common/tier/TierSegmentSelectorTest.java b/pinot-common/src/test/java/org/apache/pinot/common/tier/TierSegmentSelectorTest.java index 4a7d55c17a2..21c6fe5da13 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/tier/TierSegmentSelectorTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/tier/TierSegmentSelectorTest.java @@ -114,6 +114,30 @@ public void testTimeBasedSegmentSelector() { Assert.assertFalse(segmentSelector.selectSegment(tableNameWithType, segmentName)); } + @Test + public void testRealTimeConsumingSegmentShouldNotBeRelocated() { + + long now = System.currentTimeMillis(); + + String segmentName = "myTable__4__1__" + now; + String tableNameWithType = "myTable_REALTIME"; + SegmentZKMetadata realtimeSegmentZKMetadata = new SegmentZKMetadata(segmentName); + realtimeSegmentZKMetadata.setStatus(CommonConstants.Segment.Realtime.Status.IN_PROGRESS); + + ZNRecord segmentZKMetadataZNRecord = realtimeSegmentZKMetadata.toZNRecord(); + + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + when(propertyStore + .get(eq(ZKMetadataProvider.constructPropertyStorePathForSegment(tableNameWithType, segmentName)), any(), + anyInt())).thenReturn(segmentZKMetadataZNRecord); + + HelixManager helixManager = mock(HelixManager.class); + when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore); + + TimeBasedTierSegmentSelector segmentSelector = new TimeBasedTierSegmentSelector(helixManager, "7d"); + Assert.assertFalse(segmentSelector.selectSegment(tableNameWithType, segmentName)); + } + @Test public void testFixedSegmentSelector() { diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java index 71b062452b2..e90842d9257 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java @@ -34,14 +34,8 @@ public class DataSchemaTest { "string_array", "boolean_array", "timestamp_array", "bytes_array" }; private static final int NUM_COLUMNS = COLUMN_NAMES.length; - private static final DataSchema.ColumnDataType[] COLUMN_DATA_TYPES = - {INT, LONG, FLOAT, DOUBLE, STRING, OBJECT, INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY, DOUBLE_ARRAY, STRING_ARRAY, - BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY}; - private static final DataSchema.ColumnDataType[] COMPATIBLE_COLUMN_DATA_TYPES = - {LONG, FLOAT, DOUBLE, INT, STRING, OBJECT, LONG_ARRAY, FLOAT_ARRAY, DOUBLE_ARRAY, INT_ARRAY, STRING_ARRAY, - BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY}; - private static final DataSchema.ColumnDataType[] UPGRADED_COLUMN_DATA_TYPES = { - LONG, DOUBLE, DOUBLE, DOUBLE, STRING, OBJECT, LONG_ARRAY, DOUBLE_ARRAY, DOUBLE_ARRAY, DOUBLE_ARRAY, STRING_ARRAY, + private static final DataSchema.ColumnDataType[] COLUMN_DATA_TYPES = { + INT, LONG, FLOAT, DOUBLE, STRING, OBJECT, INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY, DOUBLE_ARRAY, STRING_ARRAY, BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY }; @@ -72,22 +66,6 @@ public void testSerDe() Assert.assertEquals(dataSchema.hashCode(), dataSchemaAfterSerDe.hashCode()); } - @Test - public void testTypeCompatible() { - DataSchema dataSchema = new DataSchema(COLUMN_NAMES, COLUMN_DATA_TYPES); - DataSchema compatibleDataSchema = new DataSchema(COLUMN_NAMES, COMPATIBLE_COLUMN_DATA_TYPES); - Assert.assertTrue(dataSchema.isTypeCompatibleWith(compatibleDataSchema)); - - String[] anotherColumnNames = new String[NUM_COLUMNS]; - Arrays.fill(anotherColumnNames, "foo"); - DataSchema incompatibleDataSchema = new DataSchema(anotherColumnNames, COLUMN_DATA_TYPES); - Assert.assertFalse(dataSchema.isTypeCompatibleWith(incompatibleDataSchema)); - - dataSchema = DataSchema.upgradeToCover(dataSchema, compatibleDataSchema); - DataSchema upgradedDataSchema = new DataSchema(COLUMN_NAMES, UPGRADED_COLUMN_DATA_TYPES); - Assert.assertEquals(dataSchema, upgradedDataSchema); - } - @Test public void testSuperTypeCheckers() { List numberTypeToTest = Arrays.asList(INT, LONG, FLOAT, DOUBLE); @@ -207,8 +185,9 @@ public void testColumnDataType() { Assert.assertFalse(columnDataType.isCompatible(BYTES_ARRAY)); } - for (DataSchema.ColumnDataType columnDataType : new DataSchema.ColumnDataType[]{STRING_ARRAY, BOOLEAN_ARRAY, - TIMESTAMP_ARRAY, BYTES_ARRAY}) { + for (DataSchema.ColumnDataType columnDataType : new DataSchema.ColumnDataType[]{ + STRING_ARRAY, BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY + }) { Assert.assertFalse(columnDataType.isNumber()); Assert.assertFalse(columnDataType.isWholeNumber()); Assert.assertTrue(columnDataType.isArray()); diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/LoopUtils.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/FileUploadDownloadClientWithoutServerTest.java similarity index 56% rename from pinot-spi/src/main/java/org/apache/pinot/spi/utils/LoopUtils.java rename to pinot-common/src/test/java/org/apache/pinot/common/utils/FileUploadDownloadClientWithoutServerTest.java index 7ffaa26d80c..34426e95744 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/LoopUtils.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/FileUploadDownloadClientWithoutServerTest.java @@ -16,23 +16,26 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.spi.utils; +package org.apache.pinot.common.utils; -import org.apache.pinot.spi.exception.EarlyTerminationException; -import org.apache.pinot.spi.trace.Tracing; +import java.net.URI; +import java.net.URISyntaxException; +import org.testng.Assert; +import org.testng.annotations.Test; -public class LoopUtils { - - private LoopUtils() { +public class FileUploadDownloadClientWithoutServerTest { + @Test + public void testExtractBaseURI() + throws URISyntaxException { + Assert.assertEquals(FileUploadDownloadClient.extractBaseURI(new URI("http://example.com:8000/a/b?c=d")), + new URI("http://example.com:8000")); } - public static final int MAX_ENTRIES_KEYS_MERGED_PER_INTERRUPTION_CHECK_MASK = 0b1_1111_1111_1111; - // Check for thread interruption, every time after merging 8192 keys - public static void checkMergePhaseInterruption(int mergedKeys) { - if ((mergedKeys & MAX_ENTRIES_KEYS_MERGED_PER_INTERRUPTION_CHECK_MASK) == 0 - && (Tracing.ThreadAccountantOps.isInterrupted())) { - throw new EarlyTerminationException(); - } + @Test + public void testGetURI() + throws URISyntaxException { + Assert.assertEquals(FileUploadDownloadClient.getURI("http", "example.com", 8000), + new URI("http://example.com:8000")); } } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/RegexpPatternConverterUtilsTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/RegexpPatternConverterUtilsTest.java index 8c94cb48865..0341ee76066 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/utils/RegexpPatternConverterUtilsTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/RegexpPatternConverterUtilsTest.java @@ -125,4 +125,41 @@ public void testTrailingSize2() { String regexpLikePattern = RegexpPatternConverterUtils.likeToRegexpLike("z%"); assertEquals(regexpLikePattern, "^z"); } + + @Test + public void testEscapedWildcard1() { + // the first underscore (_ in _b) is escaped, so it is meant to match an actual "_b" string in the provided + // string + // the second underscore (_ in b_) is not escaped, so it is a SQL wildcard that is used to match a single + // character, which in the regex space is "." + String regexpLikePattern = RegexpPatternConverterUtils.likeToRegexpLike("a\\_b_\\"); + assertEquals(regexpLikePattern, "^a\\_b.\\\\$"); + String luceneRegExpPattern = RegexpPatternConverterUtils.regexpLikeToLuceneRegExp(regexpLikePattern); + assertEquals(luceneRegExpPattern, "a\\_b.\\\\"); + } + + @Test + public void testEscapedWildcard2() { + // the % (% in %b) is escaped, so it is meant to match an actual "%b" string in the provided + // string + // the "\" before c is a normal "\", so it is meant to match an actual "\" string in the provided + // string, this is done because "c" is not a SQL wildcard - hence the "\" before that is used as-is + // and is not used for escaping "c" + // so, this "\" is escaped in the output as it is a regex metacharacter and the converted regex + // will match "a%b\cde" in the provided string + String regexpLikePattern = RegexpPatternConverterUtils.likeToRegexpLike("a\\%b\\cde"); + assertEquals(regexpLikePattern, "^a\\%b\\\\cde$"); + String luceneRegExpPattern = RegexpPatternConverterUtils.regexpLikeToLuceneRegExp(regexpLikePattern); + assertEquals(luceneRegExpPattern, "a\\%b\\\\cde"); + } + @Test + public void testEscapedWildcard3() { + // here the "\" character is used to escape _, so _ here is not treated as a SQL wildcard + // but it is meant to actually match "_" in the provided string + // so the corresponding regex doesn't convert the "_" to "." + String regexpLikePattern = RegexpPatternConverterUtils.likeToRegexpLike("%2\\_2%"); + assertEquals(regexpLikePattern, "2\\_2"); + String luceneRegExpPattern = RegexpPatternConverterUtils.regexpLikeToLuceneRegExp(regexpLikePattern); + assertEquals(luceneRegExpPattern, ".*2\\_2.*"); + } } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/URIUtilsTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/URIUtilsTest.java index 4cec446067f..18022a9d5bc 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/utils/URIUtilsTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/URIUtilsTest.java @@ -28,6 +28,7 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; public class URIUtilsTest { @@ -52,6 +53,18 @@ public void testGetPath() { assertEquals(URIUtils.getPath("file:/foo/bar", "table", "segment+%25"), "file:/foo/bar/table/segment+%25"); } + @Test + public void testGetLastPart() { + assertNull(URIUtils.getLastPart(null)); + assertEquals(URIUtils.getLastPart(""), ""); + assertEquals(URIUtils.getLastPart("http://foo/bar"), "bar"); + assertEquals(URIUtils.getLastPart("http://foo/bar?moo=x"), "bar"); + assertEquals(URIUtils.getLastPart("?"), ""); + assertEquals(URIUtils.getLastPart("?moo=x"), ""); + assertEquals(URIUtils.getLastPart("/foo/bar"), "bar"); + assertEquals(URIUtils.getLastPart("file:/foo/bar"), "bar"); + } + @Test public void testConstructDownloadUrl() { assertEquals(URIUtils.constructDownloadUrl("http://foo/bar", "table", "segment"), diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigSerDeTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigSerDeTest.java index aad62db8f74..eb1d31bab08 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigSerDeTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigSerDeTest.java @@ -64,11 +64,12 @@ public class TableConfigSerDeTest { - + private static final double NO_DICTIONARY_THRESHOLD_RATIO = 0.72; @Test public void testSerDe() throws IOException { - TableConfigBuilder tableConfigBuilder = new TableConfigBuilder(TableType.OFFLINE).setTableName("testTable"); + TableConfigBuilder tableConfigBuilder = new TableConfigBuilder(TableType.OFFLINE).setTableName("testTable") + .setNoDictionarySizeRatioThreshold(NO_DICTIONARY_THRESHOLD_RATIO).setOptimizeDictionaryForMetrics(true); { // Default table config TableConfig tableConfig = tableConfigBuilder.build(); @@ -210,9 +211,9 @@ public void testSerDe() InstanceAssignmentConfig instanceAssignmentConfig = new InstanceAssignmentConfig(new InstanceTagPoolConfig("tenant_OFFLINE", true, 3, null), new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")), - new InstanceReplicaGroupPartitionConfig(true, 0, 3, 5, 0, 0, false)); + new InstanceReplicaGroupPartitionConfig(true, 0, 3, 5, 0, 0, false, null)); TableConfig tableConfig = tableConfigBuilder.setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, instanceAssignmentConfig)).build(); + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), instanceAssignmentConfig)).build(); checkInstanceAssignmentConfig(tableConfig); @@ -367,6 +368,8 @@ private void checkDefaultTableConfig(TableConfig tableConfig) { assertNull(tableConfig.getInstanceAssignmentConfigMap()); assertNull(tableConfig.getSegmentAssignmentConfigMap()); assertNull(tableConfig.getFieldConfigList()); + assertEquals(tableConfig.getIndexingConfig().isOptimizeDictionaryForMetrics(), true); + assertEquals(tableConfig.getIndexingConfig().getNoDictionarySizeRatioThreshold(), NO_DICTIONARY_THRESHOLD_RATIO); // Serialize ObjectNode tableConfigJson = (ObjectNode) tableConfig.toJsonNode(); @@ -488,12 +491,12 @@ private void checkTierConfigList(TableConfig tableConfig) { } private void checkInstanceAssignmentConfig(TableConfig tableConfig) { - Map instanceAssignmentConfigMap = - tableConfig.getInstanceAssignmentConfigMap(); + Map instanceAssignmentConfigMap = tableConfig.getInstanceAssignmentConfigMap(); assertNotNull(instanceAssignmentConfigMap); assertEquals(instanceAssignmentConfigMap.size(), 1); - assertTrue(instanceAssignmentConfigMap.containsKey(InstancePartitionsType.OFFLINE)); - InstanceAssignmentConfig instanceAssignmentConfig = instanceAssignmentConfigMap.get(InstancePartitionsType.OFFLINE); + assertTrue(instanceAssignmentConfigMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + InstanceAssignmentConfig instanceAssignmentConfig = + instanceAssignmentConfigMap.get(InstancePartitionsType.OFFLINE.toString()); InstanceTagPoolConfig tagPoolConfig = instanceAssignmentConfig.getTagPoolConfig(); assertEquals(tagPoolConfig.getTag(), "tenant_OFFLINE"); diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigUtilsTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigUtilsTest.java index 9fa44eda212..9d1f86dc233 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigUtilsTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/config/TableConfigUtilsTest.java @@ -18,10 +18,14 @@ */ package org.apache.pinot.common.utils.config; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; +import org.apache.pinot.spi.config.table.StarTreeIndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.ingestion.BatchIngestionConfig; @@ -33,6 +37,7 @@ import org.apache.pinot.spi.stream.StreamLevelConsumer; import org.apache.pinot.spi.stream.StreamMessageDecoder; import org.apache.pinot.spi.stream.StreamMetadataProvider; +import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.Assert; import org.testng.annotations.Test; @@ -44,6 +49,7 @@ public class TableConfigUtilsTest { private static final String TABLE_NAME = "testTable"; + private static final String PARTITION_COLUMN = "partitionColumn"; /** * Test the {@link TableConfigUtils#convertFromLegacyTableConfig(TableConfig)} utility. @@ -85,6 +91,80 @@ public void testConvertFromLegacyTableConfig() { Assert.assertNull(validationConfig.getSegmentPushType()); } + @Test + public void testOverwriteTableConfigForTier() + throws Exception { + String col1CfgStr = "{" + + " \"name\": \"col1\"," + + " \"encodingType\": \"DICTIONARY\"," + + " \"indexes\": {" + + " \"bloom\": {\"enabled\": \"true\"}" + + " }," + + " \"tierOverwrites\": {" + + " \"coldTier\": {" + + " \"encodingType\": \"RAW\"," + + " \"indexes\": {}" + + " }" + + " }" + + "}"; + FieldConfig col2Cfg = JsonUtils.stringToObject(col1CfgStr, FieldConfig.class); + String stIdxCfgStr = "{" + + " \"dimensionsSplitOrder\": [\"col1\"]," + + " \"functionColumnPairs\": [\"MAX__col1\"]," + + " \"maxLeafRecords\": 10" + + "}"; + StarTreeIndexConfig stIdxCfg = JsonUtils.stringToObject(stIdxCfgStr, StarTreeIndexConfig.class); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME) + .setStarTreeIndexConfigs(Collections.singletonList(stIdxCfg)) + .setTierOverwrites(JsonUtils.stringToJsonNode("{\"coldTier\": {\"starTreeIndexConfigs\": []}}")) + .setFieldConfigList(Collections.singletonList(col2Cfg)).build(); + + TableConfig tierTblCfg = TableConfigUtils.overwriteTableConfigForTier(tableConfig, "unknownTier"); + Assert.assertEquals(tierTblCfg, tableConfig); + tierTblCfg = TableConfigUtils.overwriteTableConfigForTier(tableConfig, null); + Assert.assertEquals(tierTblCfg, tableConfig); + // Check original TableConfig and tier specific TableConfig + Assert.assertEquals(tierTblCfg.getFieldConfigList().get(0).getEncodingType(), FieldConfig.EncodingType.DICTIONARY); + Assert.assertEquals(tierTblCfg.getFieldConfigList().get(0).getIndexes().size(), 1); + Assert.assertEquals(tierTblCfg.getIndexingConfig().getStarTreeIndexConfigs().size(), 1); + tierTblCfg = TableConfigUtils.overwriteTableConfigForTier(tableConfig, "coldTier"); + Assert.assertEquals(tierTblCfg.getFieldConfigList().get(0).getEncodingType(), FieldConfig.EncodingType.RAW); + Assert.assertEquals(tierTblCfg.getFieldConfigList().get(0).getIndexes().size(), 0); + Assert.assertEquals(tierTblCfg.getIndexingConfig().getStarTreeIndexConfigs().size(), 0); + } + + @Test + public void testOverwriteTableConfigForTierWithError() + throws Exception { + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME) + .setTierOverwrites(JsonUtils.stringToJsonNode("{\"coldTier\": {\"starTreeIndexConfigs\": {}}}")).build(); + TableConfig tierTblCfg = TableConfigUtils.overwriteTableConfigForTier(tableConfig, "coldTier"); + Assert.assertEquals(tierTblCfg, tableConfig); + } + + @Test + public void testGetPartitionColumnWithoutAnyConfig() { + // without instanceAssignmentConfigMap + TableConfig tableConfig = + new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME).build(); + Assert.assertNull(TableConfigUtils.getPartitionColumn(tableConfig)); + } + + @Test + public void testGetPartitionColumnWithReplicaGroupConfig() { + ReplicaGroupStrategyConfig replicaGroupStrategyConfig = + new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1); + TableConfig tableConfig = + new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME).build(); + + // setting up ReplicaGroupStrategyConfig for backward compatibility test. + SegmentsValidationAndRetentionConfig validationConfig = new SegmentsValidationAndRetentionConfig(); + validationConfig.setReplicaGroupStrategyConfig(replicaGroupStrategyConfig); + tableConfig.setValidationConfig(validationConfig); + + Assert.assertEquals(PARTITION_COLUMN, TableConfigUtils.getPartitionColumn(tableConfig)); + } + /** * Helper method to create a test StreamConfigs map. * @return Map containing Stream Configs diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactoryTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactoryTest.java index 845ab40e7c4..389ccdc6d61 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactoryTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/fetcher/SegmentFetcherFactoryTest.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.pinot.spi.crypt.PinotCrypter; import org.apache.pinot.spi.crypt.PinotCrypterFactory; import org.apache.pinot.spi.env.PinotConfiguration; @@ -115,10 +116,11 @@ public void fetchSegmentToLocal(URI uri, File dest) } @Override - public File fetchUntarSegmentToLocalStreamed(URI uri, File dest, long rateLimit) + public File fetchUntarSegmentToLocalStreamed(URI uri, File dest, long rateLimit, AtomicInteger attempts) throws Exception { assertEquals(uri, new URI(TEST_URI)); _fetchFileToLocalCalled++; + attempts.set(0); return new File("fakeSegmentIndexFile"); } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/http/HttpClientConfigTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/http/HttpClientConfigTest.java new file mode 100644 index 00000000000..f01340e0319 --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/http/HttpClientConfigTest.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.utils.http; + +import org.apache.pinot.spi.env.PinotConfiguration; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class HttpClientConfigTest { + + @Test + public void testNewBuilder() { + // Ensure config values are picked up by the builder. + PinotConfiguration pinotConfiguration = new PinotConfiguration(); + pinotConfiguration.setProperty(HttpClientConfig.MAX_CONNS_CONFIG_NAME, "123"); + pinotConfiguration.setProperty(HttpClientConfig.MAX_CONNS_PER_ROUTE_CONFIG_NAME, "11"); + pinotConfiguration.setProperty(HttpClientConfig.DISABLE_DEFAULT_USER_AGENT_CONFIG_NAME, "true"); + HttpClientConfig httpClientConfig = HttpClientConfig.newBuilder(pinotConfiguration).build(); + Assert.assertEquals(123, httpClientConfig.getMaxConnTotal()); + Assert.assertEquals(11, httpClientConfig.getMaxConnPerRoute()); + Assert.assertTrue(httpClientConfig.isDisableDefaultUserAgent()); + + // Ensure default builder uses negative values + HttpClientConfig defaultConfig = HttpClientConfig.newBuilder(new PinotConfiguration()).build(); + Assert.assertTrue(defaultConfig.getMaxConnTotal() < 0, "default value should be < 0"); + Assert.assertTrue(defaultConfig.getMaxConnPerRoute() < 0, "default value should be < 0"); + Assert.assertFalse(defaultConfig.isDisableDefaultUserAgent(), "Default user agent should be enabled by default"); + } +} diff --git a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java index 2164241f8ce..b951b737de7 100644 --- a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java @@ -26,10 +26,13 @@ import java.util.concurrent.TimeUnit; import org.apache.calcite.sql.SqlNodeList; import org.apache.calcite.sql.SqlNumericLiteral; +import org.apache.pinot.common.request.DataSource; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; +import org.apache.pinot.common.request.Join; +import org.apache.pinot.common.request.JoinType; import org.apache.pinot.common.request.Literal; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.segment.spi.AggregationFunctionType; @@ -93,11 +96,11 @@ public void testCaseWhenStatements() { Assert.assertEquals(greatThanFunc.getOperator(), FilterKind.GREATER_THAN.name()); Assert.assertEquals(greatThanFunc.getOperands().get(0).getIdentifier().getName(), "Quantity"); Assert.assertEquals(greatThanFunc.getOperands().get(1).getLiteral().getFieldValue(), 30L); - Assert.assertEquals(caseFunc.getOperands().get(1).getLiteral().getFieldValue(), "The quantity is greater than 30"); - Function equalsFunc = caseFunc.getOperands().get(2).getFunctionCall(); + Function equalsFunc = caseFunc.getOperands().get(1).getFunctionCall(); Assert.assertEquals(equalsFunc.getOperator(), FilterKind.EQUALS.name()); Assert.assertEquals(equalsFunc.getOperands().get(0).getIdentifier().getName(), "Quantity"); Assert.assertEquals(equalsFunc.getOperands().get(1).getLiteral().getFieldValue(), 30L); + Assert.assertEquals(caseFunc.getOperands().get(2).getLiteral().getFieldValue(), "The quantity is greater than 30"); Assert.assertEquals(caseFunc.getOperands().get(3).getLiteral().getFieldValue(), "The quantity is 30"); Assert.assertEquals(caseFunc.getOperands().get(4).getLiteral().getFieldValue(), "The quantity is under 30"); @@ -124,16 +127,16 @@ public void testCaseWhenStatements() { Assert.assertEquals(greatThanFunc.getOperator(), FilterKind.GREATER_THAN.name()); Assert.assertEquals(greatThanFunc.getOperands().get(0).getIdentifier().getName(), "Quantity"); Assert.assertEquals(greatThanFunc.getOperands().get(1).getLiteral().getFieldValue(), 30L); - Assert.assertEquals(caseFunc.getOperands().get(1).getLiteral().getFieldValue(), 3L); - greatThanFunc = caseFunc.getOperands().get(2).getFunctionCall(); + greatThanFunc = caseFunc.getOperands().get(1).getFunctionCall(); Assert.assertEquals(greatThanFunc.getOperator(), FilterKind.GREATER_THAN.name()); Assert.assertEquals(greatThanFunc.getOperands().get(0).getIdentifier().getName(), "Quantity"); Assert.assertEquals(greatThanFunc.getOperands().get(1).getLiteral().getFieldValue(), 20L); - Assert.assertEquals(caseFunc.getOperands().get(3).getLiteral().getFieldValue(), 2L); - greatThanFunc = caseFunc.getOperands().get(4).getFunctionCall(); + greatThanFunc = caseFunc.getOperands().get(2).getFunctionCall(); Assert.assertEquals(greatThanFunc.getOperator(), FilterKind.GREATER_THAN.name()); Assert.assertEquals(greatThanFunc.getOperands().get(0).getIdentifier().getName(), "Quantity"); Assert.assertEquals(greatThanFunc.getOperands().get(1).getLiteral().getFieldValue(), 10L); + Assert.assertEquals(caseFunc.getOperands().get(3).getLiteral().getFieldValue(), 3L); + Assert.assertEquals(caseFunc.getOperands().get(4).getLiteral().getFieldValue(), 2L); Assert.assertEquals(caseFunc.getOperands().get(5).getLiteral().getFieldValue(), 1L); Assert.assertEquals(caseFunc.getOperands().get(6).getLiteral().getFieldValue(), 0L); } @@ -866,15 +869,11 @@ public void testRemoveComments() { testRemoveComments("select * from myTable--hello\n", "select * from myTable"); testRemoveComments("select * from--hello\nmyTable", "select * from myTable"); testRemoveComments("select * from/*hello*/myTable", "select * from myTable"); - // Multi-line comment must have end indicator - testRemoveComments("select * from myTable/*hello", "select * from myTable/*hello"); testRemoveComments("select * from myTable--", "select * from myTable"); testRemoveComments("select * from myTable--\n", "select * from myTable"); testRemoveComments("select * from--\nmyTable", "select * from myTable"); testRemoveComments("select * from/**/myTable", "select * from myTable"); - // End indicator itself has no effect testRemoveComments("select * from\nmyTable", "select * from\nmyTable"); - testRemoveComments("select * from*/myTable", "select * from*/myTable"); // Mix of single line and multi-line comment indicators testRemoveComments("select * from myTable--hello--world", "select * from myTable"); @@ -906,7 +905,9 @@ public void testRemoveComments() { } private void testRemoveComments(String sqlWithComments, String expectedSqlWithoutComments) { - Assert.assertEquals(CalciteSqlParser.removeComments(sqlWithComments).trim(), expectedSqlWithoutComments.trim()); + PinotQuery commentedResult = CalciteSqlParser.compileToPinotQuery(sqlWithComments); + PinotQuery expectedResult = CalciteSqlParser.compileToPinotQuery(expectedSqlWithoutComments); + Assert.assertEquals(commentedResult, expectedResult); } @Test @@ -1059,7 +1060,7 @@ public void testSqlDistinctQueryCompilation() { Assert.assertEquals(selectListExpressions.get(0).getType(), ExpressionType.FUNCTION); Function distinctFunction = selectListExpressions.get(0).getFunctionCall(); - Assert.assertEquals(distinctFunction.getOperator(), AggregationFunctionType.DISTINCT.getName()); + Assert.assertEquals(distinctFunction.getOperator(), "distinct"); Assert.assertEquals(distinctFunction.getOperands().size(), 1); Identifier c1 = distinctFunction.getOperands().get(0).getIdentifier(); @@ -1073,7 +1074,7 @@ public void testSqlDistinctQueryCompilation() { Assert.assertEquals(selectListExpressions.get(0).getType(), ExpressionType.FUNCTION); distinctFunction = selectListExpressions.get(0).getFunctionCall(); - Assert.assertEquals(distinctFunction.getOperator(), AggregationFunctionType.DISTINCT.getName()); + Assert.assertEquals(distinctFunction.getOperator(), "distinct"); Assert.assertEquals(distinctFunction.getOperands().size(), 2); c1 = distinctFunction.getOperands().get(0).getIdentifier(); @@ -1090,7 +1091,7 @@ public void testSqlDistinctQueryCompilation() { Assert.assertEquals(selectListExpressions.get(0).getType(), ExpressionType.FUNCTION); distinctFunction = selectListExpressions.get(0).getFunctionCall(); - Assert.assertEquals(distinctFunction.getOperator(), AggregationFunctionType.DISTINCT.getName()); + Assert.assertEquals(distinctFunction.getOperator(), "distinct"); Assert.assertEquals(distinctFunction.getOperands().size(), 3); final Expression filter = pinotQuery.getFilterExpression(); @@ -1207,7 +1208,7 @@ public void testSqlDistinctQueryCompilation() { Assert.assertEquals(selectListExpressions.get(0).getType(), ExpressionType.FUNCTION); distinctFunction = selectListExpressions.get(0).getFunctionCall(); - Assert.assertEquals(distinctFunction.getOperator(), AggregationFunctionType.DISTINCT.getName()); + Assert.assertEquals(distinctFunction.getOperator(), "distinct"); Assert.assertEquals(distinctFunction.getOperands().size(), 1); Function add = distinctFunction.getOperands().get(0).getFunctionCall(); @@ -1226,7 +1227,7 @@ public void testSqlDistinctQueryCompilation() { Assert.assertEquals(selectListExpressions.get(0).getType(), ExpressionType.FUNCTION); distinctFunction = selectListExpressions.get(0).getFunctionCall(); - Assert.assertEquals(distinctFunction.getOperator(), AggregationFunctionType.DISTINCT.getName()); + Assert.assertEquals(distinctFunction.getOperator(), "distinct"); Assert.assertEquals(distinctFunction.getOperands().size(), 2); // check for DISTINCT's first operand ADD(....) @@ -1269,7 +1270,7 @@ public void testSqlDistinctQueryCompilation() { Assert.assertEquals(selectListExpressions.get(0).getType(), ExpressionType.FUNCTION); distinctFunction = selectListExpressions.get(0).getFunctionCall(); - Assert.assertEquals(distinctFunction.getOperator(), AggregationFunctionType.DISTINCT.getName()); + Assert.assertEquals(distinctFunction.getOperator(), "distinct"); Assert.assertEquals(distinctFunction.getOperands().size(), 4); // check for DISTINCT's first operand ADD(....) @@ -1831,7 +1832,7 @@ public void testDistinctSumRewrite() { "distinct_bar"); query = "SELECT sum(distinct bar) AS distinct_bar, count(*), sum(a),min(a),max(b) FROM foo GROUP BY city ORDER BY " - + "distinct_bar"; + + "distinct_bar"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); Assert.assertEquals(pinotQuery.getSelectList().size(), 5); Function selectFunctionCall = pinotQuery.getSelectList().get(0).getFunctionCall(); @@ -2239,9 +2240,8 @@ public void testCompilationInvokedFunction() { result = pinotQuery.getSelectList().get(0).getLiteral().getBoolValue(); Assert.assertTrue(result); - query = - "select isSubnetOf('2001:db8:85a3::8a2e:370:7334/62', '2001:0db8:85a3:0003:ffff:ffff:ffff:ffff') from " - + "mytable"; + query = "select isSubnetOf('2001:db8:85a3::8a2e:370:7334/62', '2001:0db8:85a3:0003:ffff:ffff:ffff:ffff') from " + + "mytable"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); result = pinotQuery.getSelectList().get(0).getLiteral().getBoolValue(); Assert.assertTrue(result); @@ -2949,13 +2949,24 @@ public void testInvalidQueryWithAggregateFunction() { * Test for customized components in src/main/codegen/parserImpls.ftl file. */ @Test - public void testParserExtensionImpl() { + public void testParserExtensionImpl() + throws Exception { String customSql = "INSERT INTO db.tbl FROM FILE 'file:///tmp/file1', FILE 'file:///tmp/file2'"; SqlNodeAndOptions sqlNodeAndOptions = testSqlWithCustomSqlParser(customSql); Assert.assertTrue(sqlNodeAndOptions.getSqlNode() instanceof SqlInsertFromFile); Assert.assertEquals(sqlNodeAndOptions.getSqlType(), PinotSqlType.DML); } + private static SqlNodeAndOptions testSqlWithCustomSqlParser(String sqlString) + throws Exception { + try (StringReader inStream = new StringReader(sqlString)) { + SqlParserImpl sqlParser = CalciteSqlParser.newSqlParser(inStream); + SqlNodeList sqlNodeList = sqlParser.SqlStmtsEof(); + // Extract OPTION statements from sql. + return CalciteSqlParser.extractSqlNodeAndOptions(sqlString, sqlNodeList); + } + } + @Test public void shouldParseBasicAtTimeZoneExtension() { // Given: @@ -3012,15 +3023,105 @@ public void shouldParseOutsideExprAtTimeZoneExtension() { Assert.assertEquals(fun.operands.get(0).getFunctionCall().operands.get(1).getLiteral().getStringValue(), "pst"); } - private static SqlNodeAndOptions testSqlWithCustomSqlParser(String sqlString) { - try (StringReader inStream = new StringReader(sqlString)) { - SqlParserImpl sqlParser = CalciteSqlParser.newSqlParser(inStream); - SqlNodeList sqlNodeList = sqlParser.SqlStmtsEof(); - // Extract OPTION statements from sql. - return CalciteSqlParser.extractSqlNodeAndOptions(sqlString, sqlNodeList); - } catch (Exception e) { - Assert.fail("test custom sql parser failed", e); - return null; - } + @Test + public void testJoin() { + String query = "SELECT T1.a, T2.b FROM T1 JOIN T2"; + PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + DataSource dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + Join join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.INNER); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + Assert.assertEquals(join.getRight().getTableName(), "T2"); + Assert.assertNull(join.getCondition()); + + query = "SELECT T1.a, T2.b FROM T1 INNER JOIN T2 ON T1.key = T2.key"; + pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.INNER); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + Assert.assertEquals(join.getRight().getTableName(), "T2"); + Assert.assertEquals(join.getCondition(), CalciteSqlParser.compileToExpression("T1.key = T2.key")); + + query = "SELECT T1.a, T2.b FROM T1 FULL JOIN T2 ON T1.key = T2.key"; + pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.FULL); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + Assert.assertEquals(join.getRight().getTableName(), "T2"); + Assert.assertEquals(join.getCondition(), CalciteSqlParser.compileToExpression("T1.key = T2.key")); + + query = "SELECT T1.a, T2.b FROM T1 LEFT JOIN T2 ON T1.a > T2.b"; + pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.LEFT); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + Assert.assertEquals(join.getRight().getTableName(), "T2"); + Assert.assertEquals(join.getCondition(), CalciteSqlParser.compileToExpression("T1.a > T2.b")); + + query = + "SELECT T1.a, T2.b FROM T1 RIGHT JOIN (SELECT a, COUNT(*) AS b FROM T3 GROUP BY a) AS T2 ON T1.key = T2.key"; + pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.RIGHT); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + DataSource right = join.getRight(); + Assert.assertEquals(right.getTableName(), "T2"); + PinotQuery rightSubquery = right.getSubquery(); + Assert.assertEquals(rightSubquery, + CalciteSqlParser.compileToPinotQuery("SELECT a, COUNT(*) AS b FROM T3 GROUP BY a")); + Assert.assertEquals(join.getCondition(), CalciteSqlParser.compileToExpression("T1.key = T2.key")); + + query = "SELECT T1.a, T2.b FROM T1 JOIN (SELECT key, COUNT(*) AS b FROM T3 JOIN T4 GROUP BY key) AS T2 " + + "ON T1.key = T2.key"; + pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.INNER); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + right = join.getRight(); + Assert.assertEquals(right.getTableName(), "T2"); + rightSubquery = right.getSubquery(); + Assert.assertEquals(rightSubquery, + CalciteSqlParser.compileToPinotQuery("SELECT key, COUNT(*) AS b FROM T3 JOIN T4 GROUP BY key")); + Assert.assertEquals(join.getCondition(), CalciteSqlParser.compileToExpression("T1.key = T2.key")); + + // test for self join queries. + query = "SELECT T1.a FROM T1 JOIN(SELECT key FROM T1) as self ON T1.key=self.key"; + pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + dataSource = pinotQuery.getDataSource(); + Assert.assertNull(dataSource.getTableName()); + Assert.assertNull(dataSource.getSubquery()); + Assert.assertNotNull(dataSource.getJoin()); + join = dataSource.getJoin(); + Assert.assertEquals(join.getType(), JoinType.INNER); + Assert.assertEquals(join.getLeft().getTableName(), "T1"); + right = join.getRight(); + Assert.assertEquals(right.getTableName(), "self"); + rightSubquery = right.getSubquery(); + Assert.assertEquals(rightSubquery, + CalciteSqlParser.compileToPinotQuery("SELECT key FROM T1")); + Assert.assertEquals(join.getCondition(), CalciteSqlParser.compileToExpression("T1.key = self.key")); } } diff --git a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/rewriter/ArgMinMaxRewriterTest.java b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/rewriter/ArgMinMaxRewriterTest.java new file mode 100644 index 00000000000..479f609146d --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/rewriter/ArgMinMaxRewriterTest.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.sql.parsers.rewriter; + +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + + +public class ArgMinMaxRewriterTest { + private static final QueryRewriter QUERY_REWRITER = new ArgMinMaxRewriter(); + + @Test + public void testQueryRewrite() { + testQueryRewrite("SELECT ARG_MIN(col1,col2), ARG_MIN(col1,col3) FROM myTable", + "SELECT CHILD_ARG_MIN(0,col2,col1,col2), " + + "CHILD_ARG_MIN(0,col3,col1,col3)," + + "PARENT_ARG_MIN(0,1,col1,col2,col3) FROM myTable"); + + testQueryRewrite("SELECT ARG_MIN(col1,col2), ARG_MIN(col1,col2) FROM myTable", + "SELECT CHILD_ARG_MIN(0,col2,col1,col2)," + + "PARENT_ARG_MIN(0,1,col1,col2) FROM myTable"); + + testQueryRewrite("SELECT ARG_MIN(col1,col2,col5), ARG_MIN(col1,col2,col6), ARG_MAX(col1,col2,col6) " + + "FROM myTable", + "SELECT CHILD_ARG_MIN(0,col5,col1,col2,col5), " + + "CHILD_ARG_MIN(0,col6,col1,col2,col6), " + + "CHILD_ARG_MAX(0,col6,col1,col2,col6)," + + "PARENT_ARG_MIN(0,2,col1,col2,col6,col5)," + + "PARENT_ARG_MAX(0,2,col1,col2,col6) FROM myTable"); + } + + @Test + public void testQueryRewriteWithOrderBy() { + testQueryRewrite("SELECT ARG_MIN(col1,col2,col5), ARG_MIN(col1,col3,col6)," + + "ARG_MIN(col3,col1,col6) FROM myTable GROUP BY col3 " + + "ORDER BY col3 DESC", + "SELECT CHILD_ARG_MIN(0,col5,col1,col2,col5), " + + "CHILD_ARG_MIN(1,col6,col1,col3,col6)," + + "CHILD_ARG_MIN(2,col6,col3,col1,col6)," + + "PARENT_ARG_MIN(1,2,col1,col3,col6)," + + "PARENT_ARG_MIN(0,2,col1,col2,col5)," + + "PARENT_ARG_MIN(2,2,col3,col1,col6)" + + "FROM myTable GROUP BY col3 ORDER BY col3 DESC"); + + testQueryRewrite("SELECT ARG_MIN(col1,col2,col5), ARG_MAX(col1,col2,col5) FROM myTable GROUP BY col3 " + + "ORDER BY ADD(co1, co3) DESC", + "SELECT CHILD_ARG_MIN(0,col5,col1,col2,col5)," + + "CHILD_ARG_MAX(0,col5,col1,col2,col5)," + + "PARENT_ARG_MIN(0,2,col1,col2,col5), " + + "PARENT_ARG_MAX(0,2,col1,col2,col5) " + + "FROM myTable GROUP BY col3 ORDER BY ADD(co1, co3) DESC"); + } + + private void testQueryRewrite(String original, String expected) { + assertEquals(QUERY_REWRITER.rewrite(CalciteSqlParser.compileToPinotQuery(original)), + CalciteSqlParser.compileToPinotQuery(expected)); + } +} diff --git a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/rewriter/CLPDecodeRewriterTest.java b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/rewriter/CLPDecodeRewriterTest.java new file mode 100644 index 00000000000..e6bdb8dff6a --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/rewriter/CLPDecodeRewriterTest.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.sql.parsers.rewriter; + +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.apache.pinot.sql.parsers.SqlCompilationException; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertThrows; + + +public class CLPDecodeRewriterTest { + private static final QueryRewriter _QUERY_REWRITER = new CLPDecodeRewriter(); + + @Test + public void testCLPDecodeRewrite() { + // clpDecode rewrite from column group to individual columns + testQueryRewrite("SELECT clpDecode(message) FROM clpTable", + "SELECT clpDecode(message_logtype, message_dictionaryVars, message_encodedVars) FROM clpTable"); + testQueryRewrite("SELECT clpDecode(message, 'null') FROM clpTable", + "SELECT clpDecode(message_logtype, message_dictionaryVars, message_encodedVars, 'null') FROM clpTable"); + + // clpDecode passthrough + testQueryRewrite("SELECT clpDecode(message_logtype, message_dictionaryVars, message_encodedVars) FROM clpTable", + "SELECT clpDecode(message_logtype, message_dictionaryVars, message_encodedVars) FROM clpTable"); + testQueryRewrite( + "SELECT clpDecode(message_logtype, message_dictionaryVars, message_encodedVars, 'null') FROM clpTable", + "SELECT clpDecode(message_logtype, message_dictionaryVars, message_encodedVars, 'null') FROM clpTable"); + } + + @Test + public void testUnsupportedCLPDecodeQueries() { + testUnsupportedQuery("SELECT clpDecode('message') FROM clpTable"); + testUnsupportedQuery("SELECT clpDecode('message', 'default') FROM clpTable"); + testUnsupportedQuery("SELECT clpDecode('message', default) FROM clpTable"); + testUnsupportedQuery("SELECT clpDecode(message, default) FROM clpTable"); + } + + private void testQueryRewrite(String original, String expected) { + assertEquals(_QUERY_REWRITER.rewrite(CalciteSqlParser.compileToPinotQuery(original)), + CalciteSqlParser.compileToPinotQuery(expected)); + } + + private void testUnsupportedQuery(String query) { + assertThrows(SqlCompilationException.class, + () -> _QUERY_REWRITER.rewrite(CalciteSqlParser.compileToPinotQuery(query))); + } +} diff --git a/pinot-common/src/test/java/org/apache/pinot/util/TestUtils.java b/pinot-common/src/test/java/org/apache/pinot/util/TestUtils.java index 60de17ae317..d98c27821d7 100644 --- a/pinot-common/src/test/java/org/apache/pinot/util/TestUtils.java +++ b/pinot-common/src/test/java/org/apache/pinot/util/TestUtils.java @@ -22,6 +22,8 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.time.Duration; +import java.time.Instant; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.io.FileUtils; @@ -110,6 +112,46 @@ public static void waitForCondition(Function condition, long chec } } + /** + * Like {@link #waitForCondition(Function, long, long, String, boolean)}, but supports functions that throw checked + * exceptions. + * + * The first time every logPeriod that the function throws an exception, it is printed in the standard error. + */ + public static void waitForCondition(SupplierWithException condition, long checkIntervalMs, long timeoutMs, + @Nullable String errorMessage, boolean raiseError, @Nullable Duration logPeriod) { + long endTime = System.currentTimeMillis() + timeoutMs; + Instant errorLogInstant = Instant.EPOCH; + String errorMessageSuffix = errorMessage != null ? ", error message: " + errorMessage : ""; + Throwable lastError = null; + int numErrors = 0; + while (System.currentTimeMillis() < endTime) { + try { + if (Boolean.TRUE.equals(condition.get())) { + return; + } + Thread.sleep(checkIntervalMs); + } catch (InterruptedException e) { + lastError = e; + break; + } catch (Exception e) { + lastError = e; + if (logPeriod != null) { + numErrors++; + Instant now = Instant.now(); + if (logPeriod.compareTo(Duration.between(errorLogInstant, now)) < 0) { + LOGGER.warn("Error while waiting for condition", e); + errorLogInstant = now; + } + } + } + } + if (raiseError) { + Assert.fail("Failed to meet condition in " + timeoutMs + "ms after " + numErrors + " errors" + + errorMessageSuffix, lastError); + } + } + /** * Wait for a result to be returned * @@ -120,12 +162,36 @@ public static void waitForCondition(Function condition, long chec */ public static T waitForResult(SupplierWithException supplier, long timeoutMs) throws InterruptedException { + return waitForResult(supplier, timeoutMs, null); + } + + /** + * Like {@link #waitForResult(SupplierWithException, long)}, but optionally prints in stderr the first error found + * every given log period. + * @param supplier result value supplier + * @param timeoutMs timeout + * @param logPeriod how often logs will be printed. The longer the duration, the fewer the logs. + * @return result value (non-throwable) + * @throws InterruptedException if {@code Thread.sleep()} is interrupted + */ + public static T waitForResult(SupplierWithException supplier, long timeoutMs, @Nullable Duration logPeriod) + throws InterruptedException { long tEnd = System.currentTimeMillis() + timeoutMs; + Instant lastError = Instant.now(); while (tEnd > System.currentTimeMillis()) { try { return supplier.get(); - } catch (Throwable ignore) { - // ignore + } catch (InterruptedException e) { + throw new IllegalStateException("Waiting interrupted after " + + (System.currentTimeMillis() + timeoutMs - tEnd) + "ms"); + } catch (Throwable t) { + if (logPeriod != null) { + Instant now = Instant.now(); + if (logPeriod.compareTo(Duration.between(lastError, now)) < 0) { + t.printStackTrace(); + } + lastError = now; + } } Thread.sleep(1000); diff --git a/pinot-common/src/test/resources/testConfigs/multipleIndexPartialUpsertConfig.json b/pinot-common/src/test/resources/testConfigs/multipleIndexPartialUpsertConfig.json new file mode 100644 index 00000000000..8e273a05140 --- /dev/null +++ b/pinot-common/src/test/resources/testConfigs/multipleIndexPartialUpsertConfig.json @@ -0,0 +1,41 @@ +{ + "tableName": "testTableUpsert", + "segmentsConfig": { + "replication": "1", + "schemaName": "testTable", + "timeColumnName": "time" + }, + "fieldConfigList": [ + { + "encodingType": "DICTIONARY", + "indexTypes": [ + "INVERTED", + "RANGE" + ], + "name": "hits" + } + ], + "tableIndexConfig": { + "invertedIndexColumns": [ + "hits" + ], + "rangeIndexColumns": [ + "hits" + ], + "loadMode": "HEAP" + }, + "tenants": { + "broker": "DefaultTenant", + "server": "DefaultTenant" + }, + "tableType": "REALTIME", + "upsertConfig": { + "mode": "PARTIAL", + "comparisonColumn": "_comparison_column", + "partialUpsertStrategies": {}, + "defaultPartialUpsertStrategy": "OVERWRITE", + "hashFunction": "MURMUR3", + "enableSnapshot": false + }, + "metadata": {} +} diff --git a/pinot-common/src/test/resources/testConfigs/multipleIndexPartialUpsertMultipleComparisonColumnConfig.json b/pinot-common/src/test/resources/testConfigs/multipleIndexPartialUpsertMultipleComparisonColumnConfig.json new file mode 100644 index 00000000000..06ac7c26241 --- /dev/null +++ b/pinot-common/src/test/resources/testConfigs/multipleIndexPartialUpsertMultipleComparisonColumnConfig.json @@ -0,0 +1,41 @@ +{ + "tableName": "testTableUpsertMultiComparison", + "segmentsConfig": { + "replication": "1", + "schemaName": "testTable", + "timeColumnName": "time" + }, + "fieldConfigList": [ + { + "encodingType": "DICTIONARY", + "indexTypes": [ + "INVERTED", + "RANGE" + ], + "name": "hits" + } + ], + "tableIndexConfig": { + "invertedIndexColumns": [ + "hits" + ], + "rangeIndexColumns": [ + "hits" + ], + "loadMode": "HEAP" + }, + "tenants": { + "broker": "DefaultTenant", + "server": "DefaultTenant" + }, + "tableType": "REALTIME", + "upsertConfig": { + "mode": "PARTIAL", + "comparisonColumns": ["_comparison_column", "foo"], + "partialUpsertStrategies": {}, + "defaultPartialUpsertStrategy": "OVERWRITE", + "hashFunction": "MURMUR3", + "enableSnapshot": false + }, + "metadata": {} +} diff --git a/pinot-common/src/thrift/query.thrift b/pinot-common/src/thrift/query.thrift index 891098fd3c0..12a601c2df3 100644 --- a/pinot-common/src/thrift/query.thrift +++ b/pinot-common/src/thrift/query.thrift @@ -18,11 +18,6 @@ */ namespace java org.apache.pinot.common.request -struct DataSource { - 1: optional string tableName; - 2: optional PinotQuery subquery; -} - struct PinotQuery { 1: optional i32 version; 2: optional DataSource dataSource; @@ -39,10 +34,24 @@ struct PinotQuery { 13: optional map expressionOverrideHints; } -enum ExpressionType { - LITERAL, - IDENTIFIER, - FUNCTION +struct DataSource { + 1: optional string tableName; + 2: optional PinotQuery subquery; + 3: optional Join join; +} + +struct Join { + 1: required JoinType type; + 2: required DataSource left; + 3: required DataSource right; + 4: optional Expression condition; +} + +enum JoinType { + INNER, + LEFT, + RIGHT, + FULL } struct Expression { @@ -52,19 +61,27 @@ struct Expression { 4: optional Identifier identifier; } -struct Identifier { - 1: required string name; +enum ExpressionType { + LITERAL, + IDENTIFIER, + FUNCTION } union Literal { 1: optional bool boolValue; - 2: optional byte byteValue; + 2: optional i8 byteValue; 3: optional i16 shortValue; 4: optional i32 intValue; 5: optional i64 longValue; 6: optional double doubleValue; 7: optional string stringValue; 8: optional binary binaryValue; + // Set to true when the literal value is a null. + 9: optional bool nullValue; +} + +struct Identifier { + 1: required string name; } struct Function { diff --git a/pinot-compatibility-verifier/pom.xml b/pinot-compatibility-verifier/pom.xml index 5dd497062b4..07754e386d9 100644 --- a/pinot-compatibility-verifier/pom.xml +++ b/pinot-compatibility-verifier/pom.xml @@ -84,6 +84,10 @@ net.minidev json-smart + + com.typesafe.netty + netty-reactive-streams + @@ -92,11 +96,28 @@ ${project.version} test-jar + + org.testng + testng + runtime + + + com.beust + jcommander + + + org.apache.pinot pinot-controller ${project.version} test-jar + + + jakarta.activation + jakarta.activation-api + + diff --git a/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/CompatibilityOpsRunner.java b/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/CompatibilityOpsRunner.java index bd76f24064f..c7dee3dc3f0 100644 --- a/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/CompatibilityOpsRunner.java +++ b/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/CompatibilityOpsRunner.java @@ -18,14 +18,21 @@ */ package org.apache.pinot.compat; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.ScalarNode; +import org.yaml.snakeyaml.representer.Representer; public class CompatibilityOpsRunner { @@ -46,8 +53,11 @@ private boolean runOps() _parentDir = path.getParent().toString(); InputStream inputStream = Files.newInputStream(path); - ObjectMapper om = new ObjectMapper(new YAMLFactory()); - CompatTestOperation operation = om.readValue(inputStream, CompatTestOperation.class); + Representer representer = new Representer(new DumperOptions()); + representer.getPropertyUtils().setSkipMissingProperties(true); + Yaml yaml = new Yaml(new CustomConstructor(new LoaderOptions()), representer); + + CompatTestOperation operation = yaml.loadAs(inputStream, CompatTestOperation.class); LOGGER.info("Running compat verifications from file:{} ({})", path.toString(), operation.getDescription()); boolean passed = true; @@ -79,4 +89,42 @@ public static void main(String[] args) } System.exit(exitStatus); } + + public static class CustomConstructor extends Constructor { + + public CustomConstructor(LoaderOptions loadingConfig) { + super(loadingConfig); + } + + @Override + protected Object constructObject(Node node) { + if (node.getType() == BaseOp.class) { + MappingNode mappingNode = (MappingNode) node; + for (NodeTuple tuple : (mappingNode).getValue()) { + if (((ScalarNode) tuple.getKeyNode()).getValue().equals("type")) { + String type = ((ScalarNode) tuple.getValueNode()).getValue(); + switch (type) { + case "segmentOp": + node.setType(SegmentOp.class); + break; + case "tableOp": + node.setType(TableOp.class); + break; + case "queryOp": + node.setType(QueryOp.class); + break; + case "streamOp": + node.setType(StreamOp.class); + break; + default: + throw new RuntimeException("Unknown type: " + type); + } + mappingNode.getValue().remove(tuple); + break; + } + } + } + return super.constructObject(node); + } + } } diff --git a/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/StreamOp.java b/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/StreamOp.java index ac69226211d..fa0e9e38bfa 100644 --- a/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/StreamOp.java +++ b/pinot-compatibility-verifier/src/main/java/org/apache/pinot/compat/StreamOp.java @@ -21,8 +21,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.base.Function; import java.io.File; +import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -30,7 +30,7 @@ import java.util.Properties; import java.util.Set; import java.util.UUID; -import javax.annotation.Nullable; +import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.io.FileUtils; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.AdminClientConfig; @@ -279,7 +279,9 @@ private long fetchExistingTotalDocs(String tableName) String errorMsg = String.format("Failed when running query: '%s'; got exceptions:\n%s\n", query, response.toPrettyString()); JsonNode exceptions = response.get(EXCEPTIONS); - if (String.valueOf(QueryException.BROKER_INSTANCE_MISSING_ERROR).equals(exceptions.get(ERROR_CODE).toString())) { + JsonNode errorCode = exceptions.get(ERROR_CODE); + if (String.valueOf(QueryException.BROKER_INSTANCE_MISSING_ERROR).equals(String.valueOf(errorCode)) + && errorCode != null) { LOGGER.warn(errorMsg + ".Trying again"); return 0; } @@ -304,16 +306,14 @@ private long fetchExistingTotalDocs(String tableName) private void waitForDocsLoaded(String tableName, long targetDocs, long timeoutMs) { LOGGER.info("Wait Doc to load ..."); - TestUtils.waitForCondition(new Function() { - @Nullable - @Override - public Boolean apply(@Nullable Void aVoid) { - try { - return fetchExistingTotalDocs(tableName) == targetDocs; - } catch (Exception e) { - return null; - } - } - }, 100L, timeoutMs, "Failed to load " + targetDocs + " documents", true); + AtomicLong loadedDocs = new AtomicLong(-1); + TestUtils.waitForCondition( + () -> { + long existingTotalDocs = fetchExistingTotalDocs(tableName); + loadedDocs.set(existingTotalDocs); + return existingTotalDocs == targetDocs; + }, 100L, timeoutMs, + "Failed to load " + targetDocs + " documents. Found " + loadedDocs.get() + " instead", true, + Duration.ofSeconds(1)); } } diff --git a/pinot-connectors/pinot-flink-connector/pom.xml b/pinot-connectors/pinot-flink-connector/pom.xml index b6a3be32f92..608a0117f34 100644 --- a/pinot-connectors/pinot-flink-connector/pom.xml +++ b/pinot-connectors/pinot-flink-connector/pom.xml @@ -120,7 +120,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M2 + 3.2.1 diff --git a/pinot-connectors/pinot-flink-connector/src/test/java/org/apache/pinot/connector/flink/sink/PinotSinkIntegrationTest.java b/pinot-connectors/pinot-flink-connector/src/test/java/org/apache/pinot/connector/flink/sink/PinotSinkIntegrationTest.java index a2bb529b1c7..e51d261a1c1 100644 --- a/pinot-connectors/pinot-flink-connector/src/test/java/org/apache/pinot/connector/flink/sink/PinotSinkIntegrationTest.java +++ b/pinot-connectors/pinot-flink-connector/src/test/java/org/apache/pinot/connector/flink/sink/PinotSinkIntegrationTest.java @@ -46,11 +46,12 @@ import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.util.TestUtils; -import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + public class PinotSinkIntegrationTest extends BaseClusterIntegrationTest { public static final String RAW_TABLE_NAME = "testTable"; @@ -78,7 +79,7 @@ public void setUp() Map batchConfigs = new HashMap<>(); batchConfigs.put(BatchConfigProperties.OUTPUT_DIR_URI, _tarDir.getAbsolutePath()); batchConfigs.put(BatchConfigProperties.OVERWRITE_OUTPUT, "false"); - batchConfigs.put(BatchConfigProperties.PUSH_CONTROLLER_URI, _controllerBaseApiUrl); + batchConfigs.put(BatchConfigProperties.PUSH_CONTROLLER_URI, getControllerBaseApiUrl()); IngestionConfig ingestionConfig = new IngestionConfig(); ingestionConfig.setBatchIngestionConfig( new BatchIngestionConfig(Collections.singletonList(batchConfigs), "APPEND", "HOURLY")); @@ -90,11 +91,17 @@ public void setUp() .addSingleValueDimension("b", FieldSpec.DataType.LONG) .addSingleValueDimension("c", FieldSpec.DataType.STRING).setPrimaryKeyColumns(Lists.newArrayList("a")) .build(); + + addSchema(_schema); + addTableConfig(_tableConfig); } @AfterClass public void tearDown() throws Exception { + dropOfflineTable(OFFLINE_TABLE_NAME); + deleteSchema(RAW_TABLE_NAME); + stopServer(); stopBroker(); stopController(); @@ -106,61 +113,38 @@ public void tearDown() @Test public void testPinotSinkWrite() throws Exception { - addSchema(_schema); - addTableConfig(_tableConfig); - + // Single-thread write StreamExecutionEnvironment execEnv = StreamExecutionEnvironment.getExecutionEnvironment(); execEnv.setParallelism(1); DataStream srcDs = execEnv.fromCollection(_data).returns(_typeInfo); srcDs.addSink(new PinotSinkFunction<>(new FlinkRowGenericRowConverter(_typeInfo), _tableConfig, _schema)); execEnv.execute(); - Assert.assertEquals(getNumSegments(), 1); - Assert.assertEquals(getTotalNumDocs(), 6); + verifySegments(1, 6); - deleteSchema(RAW_TABLE_NAME); - dropOfflineTable(OFFLINE_TABLE_NAME); - } + dropAllSegments(RAW_TABLE_NAME, TableType.OFFLINE); - @Test - public void testPinotSinkParallelWrite() - throws Exception { - addSchema(_schema); - addTableConfig(_tableConfig); - - StreamExecutionEnvironment execEnv = StreamExecutionEnvironment.getExecutionEnvironment(); + // Parallel write + execEnv = StreamExecutionEnvironment.getExecutionEnvironment(); execEnv.setParallelism(2); - DataStream srcDs = execEnv.fromCollection(_data).returns(_typeInfo).keyBy(r -> r.getField(0)); + srcDs = execEnv.fromCollection(_data).returns(_typeInfo).keyBy(r -> r.getField(0)); srcDs.addSink(new PinotSinkFunction<>(new FlinkRowGenericRowConverter(_typeInfo), _tableConfig, _schema)); execEnv.execute(); - Assert.assertEquals(getNumSegments(), 2); - Assert.assertEquals(getTotalNumDocs(), 6); - - deleteSchema(RAW_TABLE_NAME); - dropOfflineTable(OFFLINE_TABLE_NAME); - } - - private int getNumSegments() - throws IOException { - String jsonOutputStr = sendGetRequest( - _controllerRequestURLBuilder.forSegmentListAPI(OFFLINE_TABLE_NAME, TableType.OFFLINE.toString())); - JsonNode array = JsonUtils.stringToJsonNode(jsonOutputStr); - return array.get(0).get("OFFLINE").size(); + verifySegments(2, 6); } - private int getTotalNumDocs() + private void verifySegments(int numSegments, int numTotalDocs) throws IOException { - String jsonOutputStr = sendGetRequest( - _controllerRequestURLBuilder.forSegmentListAPI(OFFLINE_TABLE_NAME, TableType.OFFLINE.toString())); - JsonNode array = JsonUtils.stringToJsonNode(jsonOutputStr); - JsonNode segments = array.get(0).get("OFFLINE"); - int totalDocCount = 0; - for (int i = 0; i < segments.size(); i++) { + JsonNode segments = JsonUtils.stringToJsonNode(sendGetRequest( + _controllerRequestURLBuilder.forSegmentListAPI(OFFLINE_TABLE_NAME, TableType.OFFLINE.toString()))).get(0) + .get("OFFLINE"); + assertEquals(segments.size(), numSegments); + int actualNumTotalDocs = 0; + for (int i = 0; i < numSegments; i++) { String segmentName = segments.get(i).asText(); - jsonOutputStr = sendGetRequest( - _controllerRequestURLBuilder.forSegmentMetadata(OFFLINE_TABLE_NAME, segmentName)); - JsonNode metadata = JsonUtils.stringToJsonNode(jsonOutputStr); - totalDocCount += metadata.get("segment.total.docs").asInt(); + actualNumTotalDocs += JsonUtils.stringToJsonNode( + sendGetRequest(_controllerRequestURLBuilder.forSegmentMetadata(OFFLINE_TABLE_NAME, segmentName))) + .get("segment.total.docs").asInt(); } - return totalDocCount; + assertEquals(actualNumTotalDocs, numTotalDocs); } } diff --git a/pinot-connectors/pinot-spark-connector/README.md b/pinot-connectors/pinot-spark-2-connector/README.md similarity index 68% rename from pinot-connectors/pinot-spark-connector/README.md rename to pinot-connectors/pinot-spark-2-connector/README.md index 771de871ae9..bbe02ab22a8 100644 --- a/pinot-connectors/pinot-spark-connector/README.md +++ b/pinot-connectors/pinot-spark-2-connector/README.md @@ -20,7 +20,7 @@ --> # Spark-Pinot Connector -Spark-pinot connector to read and write data from/to Pinot. +Spark-pinot connector to read data from Pinot. Detailed read model documentation is here; [Spark-Pinot Connector Read Model](documentation/read_model.md) @@ -28,12 +28,14 @@ Detailed read model documentation is here; [Spark-Pinot Connector Read Model](do ## Features - Query realtime, offline or hybrid tables - Distributed, parallel scan +- Streaming reads using gRPC (optional) - SQL support instead of PQL - Column and filter push down to optimize performance - Overlap between realtime and offline segments is queried exactly once for hybrid tables - Schema discovery - Dynamic inference - Static analysis of case class +- Supports query options ## Quick Start ```scala @@ -57,7 +59,23 @@ val data = spark.read data.show(100) ``` -For more examples, see `src/test/scala/example/ExampleSparkPinotConnectorTest.scala` +## Examples + +There are more examples included in `src/test/scala/.../ExampleSparkPinotConnectorTest.scala`. +You can run the examples locally (e.g. using your IDE) in standalone mode by starting a local Pinot cluster. See: https://docs.pinot.apache.org/basics/getting-started/running-pinot-locally + +You can also run the tests in _cluster mode_ using following command: +```shell +export SPARK_CLUSTER= + +# Edit the ExampleSparkPinotConnectorTest to get rid of `.master("local")` and rebuild the jar before running this command +spark-submit \ + --class org.apache.pinot.connector.spark.datasource.ExampleSparkPinotConnectorTest \ + --jars ./target/pinot-spark-2-connector-0.13.0-SNAPSHOT-shaded.jar \ + --master $SPARK_CLUSTER \ + --deploy-mode cluster \ + ./target/pinot-spark-2-connector-0.13.0-SNAPSHOT-tests.jar +``` Spark-Pinot connector uses Spark `DatasourceV2 API`. Please check the Databricks presentation for DatasourceV2 API; diff --git a/pinot-connectors/pinot-spark-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg b/pinot-connectors/pinot-spark-2-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg similarity index 100% rename from pinot-connectors/pinot-spark-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg rename to pinot-connectors/pinot-spark-2-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg diff --git a/pinot-connectors/pinot-spark-connector/documentation/read_model.md b/pinot-connectors/pinot-spark-2-connector/documentation/read_model.md similarity index 94% rename from pinot-connectors/pinot-spark-connector/documentation/read_model.md rename to pinot-connectors/pinot-spark-2-connector/documentation/read_model.md index 547bd9eda37..675091871d1 100644 --- a/pinot-connectors/pinot-spark-connector/documentation/read_model.md +++ b/pinot-connectors/pinot-spark-2-connector/documentation/read_model.md @@ -74,7 +74,7 @@ If `segmentsPerSplit` is equal to 1, there will be created 6 Spark partition; If `segmentsPerSplit` value is too low, that means more parallelism. But this also mean that a lot of connection will be opened with Pinot servers, and will increase QPS on the Pinot servers. -If `segmetnsPerSplit` value is too high, that means less parallelism. Each Pinot server will scan more segments per request. +If `segmentsPerSplit` value is too high, that means less parallelism. Each Pinot server will scan more segments per request. **Note:** Pinot servers prunes segments based on the segment metadata when query comes. In some cases(for example filtering based on the some columns), some servers may not return data. Therefore, some Spark partitions will be empty. In this cases, `repartition()` may be applied for efficient data analysis after loading data to Spark. @@ -120,7 +120,7 @@ val df = spark.read .load() .filter($"DestStateName" === "Florida") .filter($"Origin" === "ORD") - .select($"Carrier") + .select($"DestStateName", $"Origin", $"Carrier") ``` - Offline query: `select DestStateName, Origin, Carrier from airlineStats_OFFLINE where DestStateName = 'Florida and Origin = 'ORD' LIMIT {Int.MaxValue}` @@ -137,6 +137,5 @@ val df = spark.read | usePushDownFilters | Push filters to pinot servers or not. If true, data exchange between pinot server and spark will be minimized. | No | true | | segmentsPerSplit | Represents the maximum segment count that will be scanned by pinot server in one connection | No | 3 | | pinotServerTimeoutMs | The maximum timeout(ms) to get data from pinot server | No | 10 mins | - - - \ No newline at end of file +| useGrpcServer | Boolean value to enable reads via gRPC. This option is more memory efficient both on Pinot server and Spark executor side because it utilizes streaming. Requires gRPC to be enabled on Pinot server. | No | false | +| queryOptions | Comma separated list of Pinot query options (e.g. "enableNullHandling=true,skipUpsert=true") | No | "" | \ No newline at end of file diff --git a/pinot-connectors/pinot-spark-2-connector/pom.xml b/pinot-connectors/pinot-spark-2-connector/pom.xml new file mode 100644 index 00000000000..0275c98dee0 --- /dev/null +++ b/pinot-connectors/pinot-spark-2-connector/pom.xml @@ -0,0 +1,268 @@ + + + + 4.0.0 + + pinot-connectors + org.apache.pinot + 0.13.0-SNAPSHOT + .. + + pinot-spark-2-connector + Pinot Spark 2 Connector + https://pinot.apache.org/ + + ${basedir}/../.. + 2.4.6 + 2.8 + 1.3.0 + 3.1.1 + org.apache.pinot.\$internal + + + + + scala-2.12 + + true + + + 2.12.11 + 2.12 + + + + org.scala-lang.modules + scala-xml_${scala.compat.version} + ${scalaxml.version} + + + org.scala-lang + scala-library + + + + + org.apache.spark + spark-sql_${scala.compat.version} + ${spark.version} + provided + + + org.apache.curator + curator-recipes + + + com.thoughtworks.paranamer + paranamer + + + org.scala-lang.modules + scala-xml_${scala.compat.version} + + + org.scala-lang + scala-library + + + com.zaxxer + HikariCP-java7 + + + + + org.scala-lang + scala-library + ${scala.version} + provided + + + + org.scalatest + scalatest_${scala.compat.version} + ${scalatest.version} + test + + + org.scala-lang.modules + scala-xml_${scala.compat.version} + + + org.scala-lang + scala-library + + + + + + + + + maven-shade-plugin + + + package + + shade + + + + + com + ${shadeBase}.com + + com.google.protobuf.** + com.google.common.** + + + + + + + + + net.alchim31.maven + scala-maven-plugin + 3.2.2 + + + eclipse-add-source + + add-source + + + + scala-compile-first + process-resources + + compile + + + + scala-test-compile-first + process-test-resources + + testCompile + + + + attach-scaladocs + verify + + doc-jar + + + + + ${scala.version} + + -unchecked + -deprecation + -feature + + + -Xms1024m + -Xmx1024m + + + -source + ${jdk.version} + -target + ${jdk.version} + -Xlint:all,-serial,-path + + + + + + + + release-sign-artifacts + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + + + + + + src/main/scala + src/test/scala + + + src/main/resources + + + + + src/test/resources + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.7 + + true + + + + + org.scalatest + scalatest-maven-plugin + 1.0 + + ${project.build.directory}/surefire-reports + . + false + + + + test + + test + + + + + + + + + + org.apache.pinot + pinot-spark-common + ${project.parent.version} + + + diff --git a/pinot-connectors/pinot-spark-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister b/pinot-connectors/pinot-spark-2-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister similarity index 100% rename from pinot-connectors/pinot-spark-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister rename to pinot-connectors/pinot-spark-2-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReader.scala b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReader.scala similarity index 85% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReader.scala rename to pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReader.scala index f806a2b2685..d600764e7f7 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReader.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReader.scala @@ -20,8 +20,10 @@ package org.apache.pinot.connector.spark.datasource import java.util.{List => JList} -import org.apache.pinot.connector.spark.connector._ -import org.apache.pinot.connector.spark.connector.query.SQLSelectionQueryGenerator +import org.apache.pinot.connector.spark.common.{InstanceInfo, PinotClusterClient, PinotDataSourceReadOptions} +import org.apache.pinot.connector.spark.common.query.ScanQueryGenerator +import org.apache.pinot.connector.spark.common.partition.PinotSplitter +import org.apache.pinot.connector.spark.datasource.query.FilterPushDown import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.sources._ import org.apache.spark.sql.sources.v2.DataSourceOptions @@ -44,7 +46,7 @@ class PinotDataSourceReader(options: DataSourceOptions, userSchema: Option[Struc with SupportsPushDownFilters with SupportsPushDownRequiredColumns { - private val readParameters = PinotDataSourceReadOptions.from(options) + private val readParameters = PinotDataSourceReadOptions.from(options.asMap()) private var acceptedFilters: Array[Filter] = Array.empty private var currentSchema: StructType = _ @@ -53,7 +55,7 @@ class PinotDataSourceReader(options: DataSourceOptions, userSchema: Option[Struc currentSchema = userSchema.getOrElse { val pinotTableSchema = PinotClusterClient.getTableSchema(readParameters.controller, readParameters.tableName) - PinotUtils.pinotSchemaToSparkSchema(pinotTableSchema) + TypeConverter.pinotSchemaToSparkSchema(pinotTableSchema) } } currentSchema @@ -71,15 +73,16 @@ class PinotDataSourceReader(options: DataSourceOptions, userSchema: Option[Struc } val whereCondition = FilterPushDown.compileFiltersToSqlWhereClause(this.acceptedFilters) - val generatedSQLs = SQLSelectionQueryGenerator.generate( + val scanQuery = ScanQueryGenerator.generate( readParameters.tableName, readParameters.tableType, timeBoundaryInfo, schema.fieldNames, - whereCondition + whereCondition, + readParameters.queryOptions ) - val routingTable = PinotClusterClient.getRoutingTable(readParameters.broker, generatedSQLs) + val routingTable = PinotClusterClient.getRoutingTable(readParameters.broker, scanQuery) val instanceInfo : Map[String, InstanceInfo] = Map() val instanceInfoReader = (instance:String) => { // cached reader to reduce network round trips @@ -90,7 +93,7 @@ class PinotDataSourceReader(options: DataSourceOptions, userSchema: Option[Struc } PinotSplitter - .generatePinotSplits(generatedSQLs, routingTable, instanceInfoReader, readParameters) + .generatePinotSplits(scanQuery, routingTable, instanceInfoReader, readParameters) .zipWithIndex .map { case (pinotSplit, partitionId) => diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceV2.scala b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceV2.scala similarity index 100% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceV2.scala rename to pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceV2.scala diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartition.scala b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartition.scala similarity index 63% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartition.scala rename to pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartition.scala index d185f126e46..46909c1ecf3 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartition.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartition.scala @@ -18,8 +18,10 @@ */ package org.apache.pinot.connector.spark.datasource -import org.apache.pinot.connector.spark.connector.PinotSplit -import org.apache.spark.internal.Logging +import org.apache.pinot.common.datatable.DataTable +import org.apache.pinot.connector.spark.common.PinotDataSourceReadOptions +import org.apache.pinot.connector.spark.common.partition.PinotSplit +import org.apache.pinot.connector.spark.common.reader.PinotAbstractPartitionReader import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.sources.v2.reader.{InputPartition, InputPartitionReader} import org.apache.spark.sql.types.StructType @@ -29,10 +31,14 @@ class PinotInputPartition( partitionId: Int, pinotSplit: PinotSplit, dataSourceOptions: PinotDataSourceReadOptions) - extends InputPartition[InternalRow] - with Logging { + extends InputPartition[InternalRow] { override def createPartitionReader(): InputPartitionReader[InternalRow] = { - new PinotInputPartitionReader(schema, partitionId, pinotSplit, dataSourceOptions) + new InputPartitionReader[InternalRow] with PinotAbstractPartitionReader[InternalRow] { + override def _partitionId: Int = partitionId + override def _pinotSplit: PinotSplit = pinotSplit + override def _dataSourceOptions: PinotDataSourceReadOptions = dataSourceOptions + override def _translator: DataTable => Seq[InternalRow] = TypeConverter.pinotDataTableToInternalRows(_, schema) + } } } diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotUtils.scala b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/TypeConverter.scala similarity index 88% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotUtils.scala rename to pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/TypeConverter.scala index b7a61803c58..eef46d255d8 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotUtils.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/TypeConverter.scala @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.datasource import org.apache.pinot.common.datatable.DataTable import org.apache.pinot.common.utils.DataSchema.ColumnDataType -import org.apache.pinot.connector.spark.exceptions.PinotException +import org.apache.pinot.connector.spark.common.PinotException import org.apache.pinot.spi.data.{FieldSpec, Schema} import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.catalyst.util.ArrayData @@ -32,7 +32,7 @@ import scala.collection.JavaConverters._ /** * Helper methods for spark-pinot conversions */ -private[pinot] object PinotUtils { +private[datasource] object TypeConverter { /** Convert a Pinot schema to Spark schema. */ def pinotSchemaToSparkSchema(schema: Schema): StructType = { @@ -66,6 +66,9 @@ private[pinot] object PinotUtils { dataTable: DataTable, sparkSchema: StructType): Seq[InternalRow] = { val dataTableColumnNames = dataTable.getDataSchema.getColumnNames + val nullRowIdsByColumn = (0 until dataTable.getDataSchema.size()).map{ col => + dataTable.getNullRowIds(col) + } (0 until dataTable.getNumberOfRows).map { rowIndex => // spark schema is used to ensure columns order val columns = sparkSchema.fields.map { field => @@ -73,10 +76,13 @@ private[pinot] object PinotUtils { if (colIndex < 0) { throw PinotException(s"'${field.name}' not found in Pinot server response") } else { - // pinot column data type can be used directly, - // because all of them is supported in spark schema - val columnDataType = dataTable.getDataSchema.getColumnDataType(colIndex) - readPinotColumnData(dataTable, columnDataType, rowIndex, colIndex) + if (nullRowIdsByColumn(colIndex) != null + && nullRowIdsByColumn(colIndex).contains(rowIndex)) { + null + } else { + val columnDataType = dataTable.getDataSchema.getColumnDataType(colIndex) + readPinotColumnData(dataTable, columnDataType, rowIndex, colIndex) + } } } InternalRow.fromSeq(columns) diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/FilterPushDown.scala b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/query/FilterPushDown.scala similarity index 72% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/FilterPushDown.scala rename to pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/query/FilterPushDown.scala index 9d1290af8a1..331594663ad 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/FilterPushDown.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/query/FilterPushDown.scala @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.datasource.query import java.sql.{Date, Timestamp} @@ -81,25 +81,29 @@ private[pinot] object FilterPushDown { case _ => value } + private def escapeAttr(attr: String): String = { + if (attr.contains("\"")) attr else s""""$attr"""" + } + private def compileFilter(filter: Filter): Option[String] = { val whereCondition = filter match { - case EqualTo(attr, value) => s"$attr = ${compileValue(value)}" + case EqualTo(attr, value) => s"${escapeAttr(attr)} = ${compileValue(value)}" case EqualNullSafe(attr, value) => - s"NOT ($attr != ${compileValue(value)} OR $attr IS NULL OR " + + s"NOT (${escapeAttr(attr)} != ${compileValue(value)} OR ${escapeAttr(attr)} IS NULL OR " + s"${compileValue(value)} IS NULL) OR " + - s"($attr IS NULL AND ${compileValue(value)} IS NULL)" - case LessThan(attr, value) => s"$attr < ${compileValue(value)}" - case GreaterThan(attr, value) => s"$attr > ${compileValue(value)}" - case LessThanOrEqual(attr, value) => s"$attr <= ${compileValue(value)}" - case GreaterThanOrEqual(attr, value) => s"$attr >= ${compileValue(value)}" - case IsNull(attr) => s"$attr IS NULL" - case IsNotNull(attr) => s"$attr IS NOT NULL" - case StringStartsWith(attr, value) => s"$attr LIKE '$value%'" - case StringEndsWith(attr, value) => s"$attr LIKE '%$value'" - case StringContains(attr, value) => s"$attr LIKE '%$value%'" + s"(${escapeAttr(attr)} IS NULL AND ${compileValue(value)} IS NULL)" + case LessThan(attr, value) => s"${escapeAttr(attr)} < ${compileValue(value)}" + case GreaterThan(attr, value) => s"${escapeAttr(attr)} > ${compileValue(value)}" + case LessThanOrEqual(attr, value) => s"${escapeAttr(attr)} <= ${compileValue(value)}" + case GreaterThanOrEqual(attr, value) => s"${escapeAttr(attr)} >= ${compileValue(value)}" + case IsNull(attr) => s"${escapeAttr(attr)} IS NULL" + case IsNotNull(attr) => s"${escapeAttr(attr)} IS NOT NULL" + case StringStartsWith(attr, value) => s"${escapeAttr(attr)} LIKE '$value%'" + case StringEndsWith(attr, value) => s"${escapeAttr(attr)} LIKE '%$value'" + case StringContains(attr, value) => s"${escapeAttr(attr)} LIKE '%$value%'" case In(attr, value) if value.isEmpty => - s"CASE WHEN $attr IS NULL THEN NULL ELSE FALSE END" - case In(attr, value) => s"$attr IN (${compileValue(value)})" + s"CASE WHEN ${escapeAttr(attr)} IS NULL THEN NULL ELSE FALSE END" + case In(attr, value) => s"${escapeAttr(attr)} IN (${compileValue(value)})" case Not(f) => compileFilter(f).map(p => s"NOT ($p)").orNull case Or(f1, f2) => val or = Seq(f1, f2).flatMap(compileFilter) diff --git a/pinot-connectors/pinot-spark-connector/src/test/resources/schema/pinot-schema.json b/pinot-connectors/pinot-spark-2-connector/src/test/resources/schema/pinot-schema.json similarity index 100% rename from pinot-connectors/pinot-spark-connector/src/test/resources/schema/pinot-schema.json rename to pinot-connectors/pinot-spark-2-connector/src/test/resources/schema/pinot-schema.json diff --git a/pinot-connectors/pinot-spark-connector/src/test/resources/schema/spark-schema.json b/pinot-connectors/pinot-spark-2-connector/src/test/resources/schema/spark-schema.json similarity index 100% rename from pinot-connectors/pinot-spark-connector/src/test/resources/schema/spark-schema.json rename to pinot-connectors/pinot-spark-2-connector/src/test/resources/schema/spark-schema.json diff --git a/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/BaseTest.scala b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/BaseTest.scala new file mode 100644 index 00000000000..0754dcef2b3 --- /dev/null +++ b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/BaseTest.scala @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.datasource + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +trait BaseTest extends AnyFunSuite with Matchers diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/ExampleSparkPinotConnectorTest.scala b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/ExampleSparkPinotConnectorTest.scala similarity index 98% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/ExampleSparkPinotConnectorTest.scala rename to pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/ExampleSparkPinotConnectorTest.scala index f05266990e2..b293cd12fa5 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/ExampleSparkPinotConnectorTest.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/ExampleSparkPinotConnectorTest.scala @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark +package org.apache.pinot.connector.spark.datasource -import org.apache.pinot.connector.spark.utils.Logging +import org.apache.pinot.connector.spark.common.Logging import org.apache.spark.sql.SparkSession import org.apache.spark.sql.types.{DataTypes, StructField, StructType} diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/PinotUtilsTest.scala b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/TypeConverterTest.scala similarity index 80% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/PinotUtilsTest.scala rename to pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/TypeConverterTest.scala index 673e7c982e8..1b04b322d5c 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/PinotUtilsTest.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/TypeConverterTest.scala @@ -16,26 +16,26 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.datasource +import org.apache.pinot.common.datatable.DataTableFactory import org.apache.pinot.common.utils.DataSchema import org.apache.pinot.common.utils.DataSchema.ColumnDataType -import org.apache.pinot.connector.spark.BaseTest -import org.apache.pinot.connector.spark.connector.PinotUtils._ -import org.apache.pinot.connector.spark.exceptions.PinotException +import org.apache.pinot.connector.spark.common.PinotException import org.apache.pinot.core.common.datatable.DataTableBuilderFactory import org.apache.pinot.spi.data.Schema import org.apache.pinot.spi.utils.ByteArray import org.apache.spark.sql.catalyst.util.ArrayData import org.apache.spark.sql.types._ import org.apache.spark.unsafe.types.UTF8String +import org.roaringbitmap.RoaringBitmap import scala.io.Source /** * Test pinot/spark conversions like schema, data table etc. */ -class PinotUtilsTest extends BaseTest { +class TypeConverterTest extends BaseTest { test("Pinot DataTable should be converted to Spark InternalRows") { val columnNames = Array( @@ -115,7 +115,7 @@ class PinotUtilsTest extends BaseTest { ) ) - val result = pinotDataTableToInternalRows(dataTable, schema).head + val result = TypeConverter.pinotDataTableToInternalRows(dataTable, schema).head result.getArray(0) shouldEqual ArrayData.toArrayData(Seq(1, 2, 0)) result.getInt(1) shouldEqual 5 result.getArray(2) shouldEqual ArrayData.toArrayData(Seq(0d, 10.3d)) @@ -156,15 +156,44 @@ class PinotUtilsTest extends BaseTest { ) val exception = intercept[PinotException] { - pinotDataTableToInternalRows(dataTable, schema) + TypeConverter.pinotDataTableToInternalRows(dataTable, schema) } exception.getMessage shouldEqual s"'longCol' not found in Pinot server response" } + test("Converter should identify and correctly return null columns") { + val columnNames = Array("strCol", "intCol") + val columnTypes = Array(ColumnDataType.STRING, ColumnDataType.INT) + val dataSchema = new DataSchema(columnNames, columnTypes) + DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4) + + val dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema) + dataTableBuilder.startRow() + dataTableBuilder.setColumn(0, "null") + dataTableBuilder.setColumn(1, 5) + dataTableBuilder.finishRow() + + val nullRowIds = new RoaringBitmap() + nullRowIds.add(0) + dataTableBuilder.setNullRowIds(nullRowIds) + dataTableBuilder.setNullRowIds(null) + val dataTable = dataTableBuilder.build() + + val schema = StructType( + Seq( + StructField("strCol", StringType, true), + StructField("intCol", IntegerType, true) + ) + ) + + val result = TypeConverter.pinotDataTableToInternalRows(dataTable, schema).head + result.get(0, StringType) shouldEqual null + } + test("Pinot schema should be converted to spark schema") { val pinotSchemaAsString = Source.fromResource("schema/pinot-schema.json").mkString - val resultSchema = pinotSchemaToSparkSchema(Schema.fromString(pinotSchemaAsString)) + val resultSchema = TypeConverter.pinotSchemaToSparkSchema(Schema.fromString(pinotSchemaAsString)) val sparkSchemaAsString = Source.fromResource("schema/spark-schema.json").mkString val sparkSchema = DataType.fromJson(sparkSchemaAsString).asInstanceOf[StructType] resultSchema.fields should contain theSameElementsAs sparkSchema.fields diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/FilterPushDownTest.scala b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/query/FilterPushDownTest.scala similarity index 65% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/FilterPushDownTest.scala rename to pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/query/FilterPushDownTest.scala index 27d5008e639..eeb961ef25d 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/FilterPushDownTest.scala +++ b/pinot-connectors/pinot-spark-2-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/query/FilterPushDownTest.scala @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.datasource.query import java.sql.{Date, Timestamp} -import org.apache.pinot.connector.spark.BaseTest +import org.apache.pinot.connector.spark.datasource.BaseTest import org.apache.spark.sql.sources._ /** @@ -61,15 +61,22 @@ class FilterPushDownTest extends BaseTest { test("SQL query should be created from spark filters") { val whereClause = FilterPushDown.compileFiltersToSqlWhereClause(filters) val expectedOutput = - s"(attr1 = 1) AND (attr2 IN ('1', '2', '''5''')) AND (attr3 < 1) AND (attr4 <= 3) AND (attr5 > 10) AND " + - s"(attr6 >= 15) AND (NOT (attr7 = '1')) AND ((attr8 < 10) AND (attr9 <= 3)) AND " + - s"((attr10 = 'hello') OR (attr11 >= 13)) AND (attr12 LIKE '%pinot%') AND (attr13 IN (10, 20)) AND " + - s"(NOT (attr20 != '123' OR attr20 IS NULL OR '123' IS NULL) OR (attr20 IS NULL AND '123' IS NULL)) AND " + - s"(attr14 IS NULL) AND (attr15 IS NOT NULL) AND (attr16 LIKE 'pinot1%') AND (attr17 LIKE '%pinot2') AND " + - s"(attr18 = '2020-01-01 00:00:15.0') AND (attr19 < '2020-01-01') AND (attr21 = List(1, 2)) AND " + - s"(attr22 = 10.5)" + s"""("attr1" = 1) AND ("attr2" IN ('1', '2', '''5''')) AND ("attr3" < 1) AND ("attr4" <= 3) AND ("attr5" > 10) AND """ + + s"""("attr6" >= 15) AND (NOT ("attr7" = '1')) AND (("attr8" < 10) AND ("attr9" <= 3)) AND """ + + s"""(("attr10" = 'hello') OR ("attr11" >= 13)) AND ("attr12" LIKE '%pinot%') AND ("attr13" IN (10, 20)) AND """ + + s"""(NOT ("attr20" != '123' OR "attr20" IS NULL OR '123' IS NULL) OR ("attr20" IS NULL AND '123' IS NULL)) AND """ + + s"""("attr14" IS NULL) AND ("attr15" IS NOT NULL) AND ("attr16" LIKE 'pinot1%') AND ("attr17" LIKE '%pinot2') AND """ + + s"""("attr18" = '2020-01-01 00:00:15.0') AND ("attr19" < '2020-01-01') AND ("attr21" = List(1, 2)) AND """ + + s"""("attr22" = 10.5)""" whereClause.get shouldEqual expectedOutput } + test("Shouldn't escape column names which are already escaped") { + val whereClause = FilterPushDown.compileFiltersToSqlWhereClause( + Array(EqualTo("\"some\".\"nested\".\"column\"", 1))) + val expectedOutput = "(\"some\".\"nested\".\"column\" = 1)" + + whereClause.get shouldEqual expectedOutput + } } diff --git a/pinot-connectors/pinot-spark-3-connector/README.md b/pinot-connectors/pinot-spark-3-connector/README.md new file mode 100644 index 00000000000..9883c902596 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/README.md @@ -0,0 +1,86 @@ + +# Spark-Pinot Connector + +Spark-pinot connector to read data from Pinot. + +Detailed read model documentation is here; [Spark-Pinot Connector Read Model](documentation/read_model.md) + + +## Features +- Query realtime, offline or hybrid tables +- Distributed, parallel scan +- Streaming reads using gRPC (optional) +- SQL support instead of PQL +- Column and filter push down to optimize performance +- Overlap between realtime and offline segments is queried exactly once for hybrid tables +- Schema discovery + - Dynamic inference + - Static analysis of case class +- Supports query options + +## Quick Start +```scala +import org.apache.spark.sql.SparkSession + +val spark: SparkSession = SparkSession + .builder() + .appName("spark-pinot-connector-test") + .master("local") + .getOrCreate() + +import spark.implicits._ + +val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "offline") + .load() + .filter($"DestStateName" === "Florida") + +data.show(100) +``` + +## Examples + +There are more examples included in `src/test/scala/.../ExampleSparkPinotConnectorTest.scala`. +You can run the examples locally (e.g. using your IDE) in standalone mode by starting a local Pinot cluster. See: https://docs.pinot.apache.org/basics/getting-started/running-pinot-locally + +You can also run the tests in _cluster mode_ using following command: +```shell +export SPARK_CLUSTER= + +# Edit the ExampleSparkPinotConnectorTest to get rid of `.master("local")` and rebuild the jar before running this command +spark-submit \ + --class org.apache.pinot.connector.spark.v3.datasource.ExampleSparkPinotConnectorTest \ + --jars ./target/pinot-spark-3-connector-0.13.0-SNAPSHOT-shaded.jar \ + --master $SPARK_CLUSTER \ + --deploy-mode cluster \ + ./target/pinot-spark-3-connector-0.13.0-SNAPSHOT-tests.jar +``` + +Spark-Pinot connector uses Spark `DatasourceV2 API`. Please check the Databricks presentation for DatasourceV2 API; + +https://www.slideshare.net/databricks/apache-spark-data-source-v2-with-wenchen-fan-and-gengliang-wang + +## Future Works +- Add integration tests for read operation +- Add write support(pinot segment write logic will be changed in later versions of pinot) diff --git a/pinot-connectors/pinot-spark-3-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg b/pinot-connectors/pinot-spark-3-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg new file mode 100644 index 00000000000..dc3b88ae68f Binary files /dev/null and b/pinot-connectors/pinot-spark-3-connector/documentation/images/spark-pinot-connector-executor-server-interaction.jpg differ diff --git a/pinot-connectors/pinot-spark-3-connector/documentation/read_model.md b/pinot-connectors/pinot-spark-3-connector/documentation/read_model.md new file mode 100644 index 00000000000..c6fc1b085f8 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/documentation/read_model.md @@ -0,0 +1,141 @@ + +# Read Model + +Connector can scan offline, hybrid and realtime tables. Base two options <`table` and `tableType`> parameters have to given like below; +- For offline table: `table: tbl`, `tableType: OFFLINE or offline` +- For realtime table `table: tbl`, `tableType: REALTIME or realtime` +- For hybrid table `table: tbl`, `tableType: HYBRID or hybrid` + +An example scan; + +```scala +val df = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "offline") + .load() +``` + +Custom schema can be specified directly. If schema is not specified, connector read table schema from Pinot controller, and then convert to the Spark schema. + +### Architecture + +Connector reads data from `Pinot Servers` directly. For this operation, firstly, connector creates query with given filters(if filter push down is enabled) and columns, then finds routing table for created query. It creates pinot splits that contains **ONE PINOT SERVER and ONE OR MORE SEGMENT per spark partition**, based on the routing table and `segmentsPerSplit`(detailed explain is defined below). Lastly, each partition read data from specified pinot server in parallel. + +![Spark-Pinot Connector Architecture](images/spark-pinot-connector-executor-server-interaction.jpg) + +Each Spark partition open connection with Pinot server, and read data. For example, assume that routing table informations for specified query is like that: + +``` +- realtime -> + - realtimeServer1 -> (segment1, segment2, segment3) + - realtimeServer2 -> (segment4) +- offline -> + - offlineServer10 -> (segment10, segment20) +``` + +If `segmentsPerSplit` is equal to 3, there will be created 3 Spark partition like below; + +| Spark Partition | Queried Pinot Server/Segments | +| ------------- | ------------- | +| partition1 | realtimeServer1 / segment1, segment2, segment3 | +| partition2 | realtimeServer2 / segment4 | +| partition3 | offlineServer10 / segment10, segment20 | + +If `segmentsPerSplit` is equal to 1, there will be created 6 Spark partition; + +| Spark Partition | Queried Pinot Server/Segments | +| ------------- | ------------- | +| partition1 | realtimeServer1 / segment1 | +| partition2 | realtimeServer1 / segment2 | +| partition3 | realtimeServer1 / segment3 | +| partition4 | realtimeServer2 / segment4 | +| partition5 | offlineServer10 / segment10 | +| partition6 | offlineServer10 / segment20 | + +If `segmentsPerSplit` value is too low, that means more parallelism. But this also mean that a lot of connection will be opened with Pinot servers, and will increase QPS on the Pinot servers. + +If `segmentsPerSplit` value is too high, that means less parallelism. Each Pinot server will scan more segments per request. + +**Note:** Pinot servers prunes segments based on the segment metadata when query comes. In some cases(for example filtering based on the some columns), some servers may not return data. Therefore, some Spark partitions will be empty. In this cases, `repartition()` may be applied for efficient data analysis after loading data to Spark. + +### Filter And Column Push Down +Connector supports filter and column push down. Filters and columns are pushed to the pinot servers. Filter and column push down improves the performance while reading data because of its minimizing data transfer between Pinot and Spark. In default, filter push down enabled. If filters are desired to be applied in Spark, `usePushDownFilters` should be set as `false`. + +Connector uses SQL, as a result all sql filters are supported. + +### Segment Pruning + +Connector receives routing table of given query to get information on which Pinot servers to will be queried and which segments will be scan. If partitioning is enabled for given Pinot table, and created query in Spark will be scan the specific partitions, only required Pinot server and segment informations will be got(that means segment pruning operation will be applied before data reading like Pinot brokers). For more information; [Optimizing Scatter and Gather in Pinot](https://docs.pinot.apache.org/operators/operating-pinot/tuning/routing#optimizing-scatter-and-gather) + +### Table Querying +Connector uses SQL to query Pinot tables. + +Connector creates realtime and offline queries based on the filters and required columns. +- If queried table type is `OFFLINE` or `REALTIME`, routing table information will be got for specific table type. +- If queried table type is `HYBRID`, realtime and offline routing table information will be got. Also, connector receives `TimeBoundary` information for given table, and use it in query to ensure that the overlap between realtime and offline segment data is queried exactly once. For more information; [Pinot Broker](https://docs.pinot.apache.org/basics/components/broker) + +### Query Generation + +Example generated queries for given usages(assume that `airlineStats` table is hybrid and TimeBoundary information is `DaysSinceEpoch, 16084`); + +```scala +val df = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "hybrid") + .load() +``` + +For above usage, realtime and offline SQL queries will be created; + +- Offline query: `select * from airlineStats_OFFLINE where DaysSinceEpoch < 16084 LIMIT {Int.MaxValue}` + +- Realtime query: `select * from airlineStats_REALTIME where DaysSinceEpoch >= 16084 LIMIT {Int.MaxValue}` + +```scala +val df = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "offline") + .load() + .filter($"DestStateName" === "Florida") + .filter($"Origin" === "ORD") + .select($"DestStateName", $"Origin", $"Carrier") +``` + +- Offline query: `select DestStateName, Origin, Carrier from airlineStats_OFFLINE where DestStateName = 'Florida and Origin = 'ORD' LIMIT {Int.MaxValue}` + +**Note: Limit is added to every query. Because, generated queries will be converted to the Pinot `BrokerRequest` class. In this operation, pinot sets limit to `10` automatically. Therefore, `LIMIT` was set to `Int.MaxValue` to prevent this issue.** + +### Connector Read Parameters +| Configuration | Description | Required | Default Value | +| ------------- | ------------- | ------------- | ------------- | +| table | Pinot table name without table type | Yes | - | +| tableType | Pinot table type(`realtime`, `offline` or `hybrid`) | Yes | - | +| controller | Pinot controller url and port. Input should be `url:port` format without schema. Connector does not support `https` schema for now. | No | localhost:9000 | +| broker | Pinot broker url and port. Input should be `url:port` format without schema. If not specified, connector will find broker instances of table automatically. Connector does not support `https` schema for now | No | Fetch broker instances of table from Pinot Controller | +| usePushDownFilters | Push filters to pinot servers or not. If true, data exchange between pinot server and spark will be minimized. | No | true | +| segmentsPerSplit | Represents the maximum segment count that will be scanned by pinot server in one connection | No | 3 | +| pinotServerTimeoutMs | The maximum timeout(ms) to get data from pinot server | No | 10 mins | +| useGrpcServer | Boolean value to enable reads via gRPC. This option is more memory efficient both on Pinot server and Spark executor side because it utilizes streaming. Requires gRPC to be enabled on Pinot server. | No | false | +| queryOptions | Comma separated list of Pinot query options (e.g. "enableNullHandling=true,skipUpsert=true") | No | "" | diff --git a/pinot-connectors/pinot-spark-3-connector/pom.xml b/pinot-connectors/pinot-spark-3-connector/pom.xml new file mode 100644 index 00000000000..7178465aee6 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/pom.xml @@ -0,0 +1,303 @@ + + + + 4.0.0 + + pinot-connectors + org.apache.pinot + 0.13.0-SNAPSHOT + .. + + pinot-spark-3-connector + Pinot Spark 3 Connector + https://pinot.apache.org/ + + ${basedir}/../.. + 3.4.0 + 4.8 + 3.1.1 + org.apache.pinot.\$internal + + + + + scala-2.12 + + true + + + 2.12.11 + 2.12 + + + + org.apache.spark + spark-sql_${scala.compat.version} + ${spark.version} + provided + + + org.antlr + antlr4-runtime + + + org.apache.curator + curator-recipes + + + com.thoughtworks.paranamer + paranamer + + + org.scala-lang.modules + scala-xml_${scala.compat.version} + + + org.scala-lang + scala-library + + + com.zaxxer + HikariCP-java7 + + + + + + org.scala-lang + scala-library + ${scala.version} + provided + + + + org.scalatest + scalatest_${scala.compat.version} + ${scalatest.version} + test + + + org.scala-lang.modules + scala-xml_${scala.compat.version} + + + org.scala-lang + scala-library + + + org.scala-lang + scala-reflect + + + + + + + + + maven-shade-plugin + + + package + + shade + + + + + com + ${shadeBase}.com + + com.google.protobuf.** + com.google.common.** + + + + + + + + + net.alchim31.maven + scala-maven-plugin + 3.2.2 + + + eclipse-add-source + + add-source + + + + scala-compile-first + process-resources + + compile + + + + scala-test-compile-first + process-test-resources + + testCompile + + + + attach-scaladocs + verify + + doc-jar + + + + + ${scala.version} + + -unchecked + -deprecation + -feature + + + -Xms1024m + -Xmx1024m + + + -source + ${jdk.version} + -target + ${jdk.version} + -Xlint:all,-serial,-path + + + + + + + + release-sign-artifacts + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + + + + + + src/main/scala + src/test/scala + + + src/main/resources + + + + + src/test/resources + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.7 + + true + + + + + org.scalatest + scalatest-maven-plugin + 1.0 + + ${project.build.directory}/surefire-reports + . + false + + + + test + + test + + + + + + + + + + org.apache.pinot + pinot-common + + + org.apache.zookeeper + zookeeper + + + org.antlr + antlr4-runtime + + + + + org.apache.pinot + pinot-spark-common + ${project.parent.version} + + + org.apache.pinot + pinot-core + ${project.parent.version} + + + org.antlr + antlr4-runtime + + + + + provided + org.antlr + antlr4-runtime + ${antlr-runtime.version} + + + test + javax.servlet + javax.servlet-api + 3.0.1 + + + + diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister b/pinot-connectors/pinot-spark-3-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister new file mode 100644 index 00000000000..6bf835b475e --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister @@ -0,0 +1 @@ +org.apache.pinot.connector.spark.v3.datasource.PinotDataSource diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotDataSource.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotDataSource.scala new file mode 100644 index 00000000000..a551218cbbc --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotDataSource.scala @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.{PinotClusterClient, PinotDataSourceReadOptions} +import org.apache.spark.sql.connector.catalog.{Table, TableProvider} +import org.apache.spark.sql.connector.expressions.Transform +import org.apache.spark.sql.sources.DataSourceRegister +import org.apache.spark.sql.types.StructType +import org.apache.spark.sql.util.CaseInsensitiveStringMap + +import java.util + +/** + * PinotDataSource implements TableProvider interface of Spark DataSourceV2 API + * to provide read access to Pinot tables. + */ +class PinotDataSource extends TableProvider with DataSourceRegister { + override def shortName(): String = "pinot" + + override def inferSchema(options: CaseInsensitiveStringMap): StructType = { + val readParameters = PinotDataSourceReadOptions.from(options) + val tableName = readParameters.tableName + val controller = readParameters.controller + + val pinotTableSchema = + PinotClusterClient.getTableSchema(controller, tableName) + TypeConverter.pinotSchemaToSparkSchema(pinotTableSchema) + } + + override def getTable(schema: StructType, + partitioning: Array[Transform], + properties: util.Map[String, String]): Table = { + val tableName = properties.get(PinotDataSourceReadOptions.CONFIG_TABLE_NAME) + new PinotTable(tableName, schema) + } + + override def supportsExternalMetadata(): Boolean = true +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotInputPartition.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotInputPartition.scala new file mode 100644 index 00000000000..b9b4596c3e1 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotInputPartition.scala @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.PinotDataSourceReadOptions +import org.apache.pinot.connector.spark.common.partition.PinotSplit +import org.apache.spark.sql.connector.read.InputPartition +import org.apache.spark.sql.types.StructType + +/** + * PinotInputPartition: Implements Spark's InputPartition which convey partition related information + * from Spark master to executors. This class is serialized and sent across the network so it should + * be kept lean for good performance. + * + * @param schema Schema for the scan/read operation. This can be a subset of the tables schema + * @param partitionId Integer which is used as requestId when sending query to Pinot servers + * @param pinotSplit An instance of PinotSplit which encapsulates segment and query information + * @param dataSourceOptions PinotDataSourceReadOptions instance created for the read + */ +case class PinotInputPartition( + schema: StructType, + partitionId: Int, + pinotSplit: PinotSplit, + dataSourceOptions: PinotDataSourceReadOptions) + extends InputPartition { +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScan.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScan.scala new file mode 100644 index 00000000000..a50967fe5e5 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScan.scala @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.common.datatable.DataTable +import org.apache.pinot.connector.spark.common.partition.{PinotSplit, PinotSplitter} +import org.apache.pinot.connector.spark.common.query.ScanQuery +import org.apache.pinot.connector.spark.common.reader.PinotAbstractPartitionReader +import org.apache.pinot.connector.spark.common.{InstanceInfo, PinotClusterClient, PinotDataSourceReadOptions} +import org.apache.spark.sql.catalyst.InternalRow +import org.apache.spark.sql.connector.read.{ + Batch, + InputPartition, + PartitionReader, + PartitionReaderFactory, + Scan +} +import org.apache.spark.sql.types.StructType + +import scala.collection.mutable.Map + +/** + * PinotScan implements Spark's 'Scan', which is a logical representation of a scan operation, + * and also adds the 'Batch' mixin to support batch reads. + * + * @param query An instance of ScanQuery which encapsulates SQL queries to be executed + * @param schema + * @param readParameters + */ +class PinotScan( + query: ScanQuery, + schema: StructType, + readParameters: PinotDataSourceReadOptions) + extends Scan with Batch { + + override def readSchema(): StructType = schema + + override def toBatch: Batch = this + + override def planInputPartitions(): Array[InputPartition] = { + val routingTable = PinotClusterClient.getRoutingTable(readParameters.broker, query) + + val instanceInfo : Map[String, InstanceInfo] = Map() + val instanceInfoReader = (instance:String) => { // cached reader to reduce network round trips + instanceInfo.getOrElseUpdate( + instance, + PinotClusterClient.getInstanceInfo(readParameters.controller, instance) + ) + } + + PinotSplitter + .generatePinotSplits(query, routingTable, instanceInfoReader, readParameters) + .zipWithIndex + .map { + case (pinotSplit, partitionId) => + PinotInputPartition(readSchema(), partitionId, pinotSplit, readParameters) + .asInstanceOf[InputPartition] + } + .toArray + } + + override def createReaderFactory(): PartitionReaderFactory = { + // necessary to make PartitionReaderFactory serializable + val _schema = this.schema + + (partition: InputPartition) => { + partition match { + case p: PinotInputPartition => + new PartitionReader[InternalRow] with PinotAbstractPartitionReader[InternalRow] { + override def _partitionId: Int = p.partitionId + override def _pinotSplit: PinotSplit = p.pinotSplit + override def _dataSourceOptions: PinotDataSourceReadOptions = p.dataSourceOptions + override def _translator: DataTable => Seq[InternalRow] = + TypeConverter.pinotDataTableToInternalRows(_, _schema) + } + case _ => + throw new Exception("Unknown InputPartition type. Expecting PinotInputPartition") + } + } + } +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanBuilder.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanBuilder.scala new file mode 100644 index 00000000000..ff2c0b68981 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanBuilder.scala @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.query.ScanQueryGenerator +import org.apache.pinot.connector.spark.common.{PinotClusterClient, PinotDataSourceReadOptions} +import org.apache.pinot.connector.spark.v3.datasource.query.FilterPushDown +import org.apache.spark.sql.connector.read.{Scan, ScanBuilder, SupportsPushDownFilters, SupportsPushDownRequiredColumns} +import org.apache.spark.sql.sources.Filter +import org.apache.spark.sql.types.StructType + +/** + * PinotScanBuilder: Implementation of Spark DataSourceV2 API's ScanBuilder interface. + * This is the main class in which various push down functionality is implemented. + * We currently support pushing down Filters (where clause) and RequiredColumns (selection). + * + * @param readParameters PinotDataSourceReadOptions instance for the read + */ +class PinotScanBuilder(readParameters: PinotDataSourceReadOptions) + extends ScanBuilder with SupportsPushDownFilters with SupportsPushDownRequiredColumns { + + private var acceptedFilters: Array[Filter] = Array.empty + private var currentSchema: StructType = _ + + override def build(): Scan = { + // Time boundary is used when table is hybrid to ensure that the overlap + // between realtime and offline segment data is queried exactly once + val timeBoundaryInfo = + if (readParameters.tableType.isDefined) { + None + } else { + PinotClusterClient.getTimeBoundaryInfo(readParameters.broker, readParameters.tableName) + } + + val whereCondition = FilterPushDown.compileFiltersToSqlWhereClause(this.acceptedFilters) + val scanQuery = ScanQueryGenerator.generate( + readParameters.tableName, + readParameters.tableType, + timeBoundaryInfo, + currentSchema.fieldNames, + whereCondition, + readParameters.queryOptions + ) + + new PinotScan(scanQuery, currentSchema, readParameters) + } + + override def pushFilters(filters: Array[Filter]): Array[Filter] = { + if (readParameters.usePushDownFilters) { + val (acceptedFilters, postScanFilters) = FilterPushDown.acceptFilters(filters) + this.acceptedFilters = acceptedFilters + postScanFilters + } else { + filters + } + } + + override def pushedFilters(): Array[Filter] = { + this.acceptedFilters + } + + override def pruneColumns(requiredSchema: StructType): Unit = { + this.currentSchema = requiredSchema + } + +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotTable.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotTable.scala new file mode 100644 index 00000000000..28cced506e8 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/PinotTable.scala @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.PinotDataSourceReadOptions +import org.apache.spark.sql.connector.catalog.{SupportsRead, Table, TableCapability} +import org.apache.spark.sql.connector.read.ScanBuilder +import org.apache.spark.sql.types.StructType +import org.apache.spark.sql.util.CaseInsensitiveStringMap + +import java.util + +/** + * PinotTable implements Spark's Table interface to expose a logical representation of a + * Pinot table. This is the interface where Spark discovers table capabilities such as + * 'SupportsRead'. For now Pinot tables only support batch reads. + * + * @param name Pinot table name + * @param schema Schema provided by Spark. This can be different than table schema + */ +class PinotTable(name: String, schema: StructType) extends Table with SupportsRead { + override def name(): String = name + + override def schema(): StructType = schema + + override def capabilities(): util.Set[TableCapability] = { + util.EnumSet.of(TableCapability.BATCH_READ) + } + + override def newScanBuilder(options: CaseInsensitiveStringMap): ScanBuilder = { + val readParameters = PinotDataSourceReadOptions.from(options) + new PinotScanBuilder(readParameters) + } +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/TypeConverter.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/TypeConverter.scala new file mode 100644 index 00000000000..a36fe6adc9d --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/TypeConverter.scala @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.common.datatable.DataTable +import org.apache.pinot.common.utils.DataSchema.ColumnDataType +import org.apache.pinot.connector.spark.common.PinotException +import org.apache.pinot.spi.data.{FieldSpec, Schema} +import org.apache.spark.sql.catalyst.InternalRow +import org.apache.spark.sql.catalyst.util.ArrayData +import org.apache.spark.sql.types._ +import org.apache.spark.unsafe.types.UTF8String + +import scala.collection.JavaConverters._ + +/** + * Helper methods for spark-pinot conversions + */ +private[pinot] object TypeConverter { + + /** Convert a Pinot schema to Spark schema. */ + def pinotSchemaToSparkSchema(schema: Schema): StructType = { + val structFields = schema.getAllFieldSpecs.asScala.map { field => + val sparkDataType = pinotDataTypeToSparkDataType(field.getDataType) + if (field.isSingleValueField) { + StructField(field.getName, sparkDataType) + } else { + StructField(field.getName, ArrayType(sparkDataType)) + } + } + StructType(structFields.toList) + } + + private def pinotDataTypeToSparkDataType(dataType: FieldSpec.DataType): DataType = + dataType match { + case FieldSpec.DataType.INT => IntegerType + case FieldSpec.DataType.LONG => LongType + case FieldSpec.DataType.FLOAT => FloatType + case FieldSpec.DataType.DOUBLE => DoubleType + case FieldSpec.DataType.STRING => StringType + case FieldSpec.DataType.BYTES => ArrayType(ByteType) + case FieldSpec.DataType.TIMESTAMP => LongType + case FieldSpec.DataType.BOOLEAN => BooleanType + case _ => + throw PinotException(s"Unsupported pinot data type '$dataType") + } + + /** Convert Pinot DataTable to Seq of InternalRow */ + def pinotDataTableToInternalRows( + dataTable: DataTable, + sparkSchema: StructType): Seq[InternalRow] = { + val dataTableColumnNames = dataTable.getDataSchema.getColumnNames + val nullRowIdsByColumn = (0 until dataTable.getDataSchema.size()).map{ col => + dataTable.getNullRowIds(col) + } + (0 until dataTable.getNumberOfRows).map { rowIndex => + // spark schema is used to ensure columns order + val columns = sparkSchema.fields.map { field => + val colIndex = dataTableColumnNames.indexOf(field.name) + if (colIndex < 0) { + throw PinotException(s"'${field.name}' not found in Pinot server response") + } else { + if (nullRowIdsByColumn(colIndex) != null + && nullRowIdsByColumn(colIndex).contains(rowIndex)) { + null + } else { + val columnDataType = dataTable.getDataSchema.getColumnDataType(colIndex) + readPinotColumnData(dataTable, columnDataType, rowIndex, colIndex) + } + } + } + InternalRow.fromSeq(columns) + } + } + + private def readPinotColumnData( + dataTable: DataTable, + columnDataType: ColumnDataType, + rowIndex: Int, + colIndex: Int): Any = columnDataType match { + // single column types + case ColumnDataType.STRING => + UTF8String.fromString(dataTable.getString(rowIndex, colIndex)) + case ColumnDataType.INT => + dataTable.getInt(rowIndex, colIndex) + case ColumnDataType.LONG => + dataTable.getLong(rowIndex, colIndex) + case ColumnDataType.FLOAT => + dataTable.getFloat(rowIndex, colIndex) + case ColumnDataType.DOUBLE => + dataTable.getDouble(rowIndex, colIndex) + case ColumnDataType.TIMESTAMP => + dataTable.getLong(rowIndex, colIndex) + case ColumnDataType.BOOLEAN => + dataTable.getInt(rowIndex, colIndex) == 1 + + // array column types + case ColumnDataType.STRING_ARRAY => + ArrayData.toArrayData( + dataTable.getStringArray(rowIndex, colIndex).map(UTF8String.fromString).toSeq + ) + case ColumnDataType.INT_ARRAY => + ArrayData.toArrayData(dataTable.getIntArray(rowIndex, colIndex).toSeq) + case ColumnDataType.LONG_ARRAY => + ArrayData.toArrayData(dataTable.getLongArray(rowIndex, colIndex).toSeq) + case ColumnDataType.FLOAT_ARRAY => + ArrayData.toArrayData(dataTable.getFloatArray(rowIndex, colIndex).toSeq) + case ColumnDataType.DOUBLE_ARRAY => + ArrayData.toArrayData(dataTable.getDoubleArray(rowIndex, colIndex).toSeq) + case ColumnDataType.BYTES => + ArrayData.toArrayData(dataTable.getBytes(rowIndex, colIndex).getBytes) + case ColumnDataType.TIMESTAMP_ARRAY => + ArrayData.toArrayData(dataTable.getLongArray(rowIndex, colIndex).toSeq) + case ColumnDataType.BOOLEAN_ARRAY => + ArrayData.toArrayData( + dataTable.getIntArray(rowIndex, colIndex).map(i => i == 1).toSeq + ) + + case _ => + throw PinotException(s"'$columnDataType' is not supported") + } +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/query/FilterPushDown.scala b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/query/FilterPushDown.scala new file mode 100644 index 00000000000..cac50ec0312 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/main/scala/org/apache/pinot/connector/spark/v3/datasource/query/FilterPushDown.scala @@ -0,0 +1,127 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource.query + +import java.sql.{Date, Timestamp} + +import org.apache.spark.sql.sources._ + +/** + * Helper methods to find valid filters, and convert spark filters to SQL where clause. + */ +private[pinot] object FilterPushDown { + + /** + * Create SQL 'where clause' from Spark filters. + * + * @param filters Supported spark filters + * @return where clause, or None if filters does not exists + */ + def compileFiltersToSqlWhereClause(filters: Array[Filter]): Option[String] = { + if (filters.isEmpty) { + None + } else { + Option(filters.flatMap(compileFilter).map(filter => s"($filter)").mkString(" AND ")) + } + } + + /** + * Accept only filters that supported in SQL. + * + * @param filters Spark filters that contains valid and/or invalid filters + * @return Supported and unsupported filters + */ + def acceptFilters(filters: Array[Filter]): (Array[Filter], Array[Filter]) = { + filters.partition(isFilterSupported) + } + + private def isFilterSupported(filter: Filter): Boolean = filter match { + case _: EqualTo => true + case _: EqualNullSafe => true + case _: In => true + case _: LessThan => true + case _: LessThanOrEqual => true + case _: GreaterThan => true + case _: GreaterThanOrEqual => true + case _: IsNull => true + case _: IsNotNull => true + case _: StringStartsWith => true + case _: StringEndsWith => true + case _: StringContains => true + case _: Not => true + case _: Or => true + case _: And => true + case _ => false + } + + private def escapeSql(value: String): String = + if (value == null) null else value.replace("'", "''") + + private def compileValue(value: Any): Any = value match { + case stringValue: String => s"'${escapeSql(stringValue)}'" + case timestampValue: Timestamp => "'" + timestampValue + "'" + case dateValue: Date => "'" + dateValue + "'" + case arrayValue: Array[Any] => arrayValue.map(compileValue).mkString(", ") + case _ => value + } + + private def escapeAttr(attr: String): String = { + if (attr.contains("\"")) attr else s""""$attr"""" + } + + private def compileFilter(filter: Filter): Option[String] = { + val whereCondition = filter match { + case EqualTo(attr, value) => s"${escapeAttr(attr)} = ${compileValue(value)}" + case EqualNullSafe(attr, value) => + s"NOT (${escapeAttr(attr)} != ${compileValue(value)} OR ${escapeAttr(attr)} IS NULL OR " + + s"${compileValue(value)} IS NULL) OR " + + s"(${escapeAttr(attr)} IS NULL AND ${compileValue(value)} IS NULL)" + case LessThan(attr, value) => s"${escapeAttr(attr)} < ${compileValue(value)}" + case GreaterThan(attr, value) => s"${escapeAttr(attr)} > ${compileValue(value)}" + case LessThanOrEqual(attr, value) => s"${escapeAttr(attr)} <= ${compileValue(value)}" + case GreaterThanOrEqual(attr, value) => s"${escapeAttr(attr)} >= ${compileValue(value)}" + case IsNull(attr) => s"${escapeAttr(attr)} IS NULL" + case IsNotNull(attr) => s"${escapeAttr(attr)} IS NOT NULL" + case StringStartsWith(attr, value) => s"${escapeAttr(attr)} LIKE '$value%'" + case StringEndsWith(attr, value) => s"${escapeAttr(attr)} LIKE '%$value'" + case StringContains(attr, value) => s"${escapeAttr(attr)} LIKE '%$value%'" + case In(attr, value) if value.isEmpty => + s"CASE WHEN ${escapeAttr(attr)} IS NULL THEN NULL ELSE FALSE END" + case In(attr, value) => s"${escapeAttr(attr)} IN (${compileValue(value)})" + case Not(f) => compileFilter(f).map(p => s"NOT ($p)").orNull + case Or(f1, f2) => + val or = Seq(f1, f2).flatMap(compileFilter) + if (or.size == 2) { + or.map(p => s"($p)").mkString(" OR ") + } else { + null + } + case And(f1, f2) => + val and = Seq(f1, f2).flatMap(compileFilter) + if (and.size == 2) { + and.map(p => s"($p)").mkString(" AND ") + } else { + null + } + case _ => null + } + Option(whereCondition) + } + +} diff --git a/pinot-connectors/pinot-spark-connector/src/test/resources/log4j2.xml b/pinot-connectors/pinot-spark-3-connector/src/test/resources/log4j2.xml similarity index 100% rename from pinot-connectors/pinot-spark-connector/src/test/resources/log4j2.xml rename to pinot-connectors/pinot-spark-3-connector/src/test/resources/log4j2.xml diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/resources/schema/pinot-schema.json b/pinot-connectors/pinot-spark-3-connector/src/test/resources/schema/pinot-schema.json new file mode 100644 index 00000000000..1cf62b28741 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/resources/schema/pinot-schema.json @@ -0,0 +1,75 @@ +{ + "schemaName" : "schemaName", + "dimensionFieldSpecs" : [ { + "name" : "stringDim", + "dataType" : "STRING" + }, { + "name" : "intDim", + "dataType" : "INT" + }, { + "name" : "floatDim", + "dataType" : "FLOAT", + "defaultNullValue" : 0.0 + }, { + "name" : "doubleDim", + "dataType" : "DOUBLE" + }, { + "name" : "longDim", + "dataType" : "LONG" + }, { + "name": "boolDim", + "dataType": "BOOLEAN" + }, { + "name" : "stringArrayDim", + "dataType" : "STRING", + "singleValueField" : false + }, { + "name" : "floatArrayDim", + "dataType" : "FLOAT", + "singleValueField" : false + }, { + "name": "boolArrayDim", + "dataType": "BOOLEAN", + "singleValueField": false + } ], + "metricFieldSpecs" : [ { + "name" : "floatMetric", + "dataType" : "FLOAT" + }, { + "name" : "doubleMetric", + "dataType" : "DOUBLE" + }, { + "name" : "longMetric", + "dataType" : "LONG" + }, { + "name" : "intMetric", + "dataType" : "INT", + "defaultNullValue" : 10 + }, { + "name" : "stringMetric", + "dataType" : "STRING" + }, { + "name" : "byteDim", + "dataType" : "BYTES" + } ], + "timeFieldSpec" : { + "incomingGranularitySpec" : { + "name" : "incomingTimeField", + "dataType" : "LONG", + "timeType" : "SECONDS" + }, + "outgoingGranularitySpec" : { + "name" : "outgoingTimeField", + "dataType" : "INT", + "timeType" : "DAYS" + } + }, + "dateTimeFieldSpecs": [ + { + "name": "timestampField", + "dataType": "TIMESTAMP", + "format": "1:MILLISECONDS:EPOCH", + "granularity": "1:SECONDS" + } + ] +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/resources/schema/spark-schema.json b/pinot-connectors/pinot-spark-3-connector/src/test/resources/schema/spark-schema.json new file mode 100644 index 00000000000..a8187fcf0fc --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/resources/schema/spark-schema.json @@ -0,0 +1,105 @@ +{ + "type" : "struct", + "fields" : [ { + "name" : "floatMetric", + "type" : "float", + "nullable" : true, + "metadata" : { } + }, { + "name" : "doubleMetric", + "type" : "double", + "nullable" : true, + "metadata" : { } + }, { + "name" : "longMetric", + "type" : "long", + "nullable" : true, + "metadata" : { } + }, { + "name" : "intMetric", + "type" : "integer", + "nullable" : true, + "metadata" : { } + }, { + "name" : "stringMetric", + "type" : "string", + "nullable" : true, + "metadata" : { } + }, { + "name" : "stringDim", + "type" : "string", + "nullable" : true, + "metadata" : { } + }, { + "name" : "intDim", + "type" : "integer", + "nullable" : true, + "metadata" : { } + }, { + "name" : "floatDim", + "type" : "float", + "nullable" : true, + "metadata" : { } + }, { + "name" : "doubleDim", + "type" : "double", + "nullable" : true, + "metadata" : { } + }, { + "name" : "longDim", + "type" : "long", + "nullable" : true, + "metadata" : { } + }, { + "name" : "boolDim", + "type" : "boolean", + "nullable" : true, + "metadata" : { } + }, { + "name" : "stringArrayDim", + "type" : { + "type" : "array", + "elementType" : "string", + "containsNull" : true + }, + "nullable" : true, + "metadata" : { } + }, { + "name" : "floatArrayDim", + "type" : { + "type" : "array", + "elementType" : "float", + "containsNull" : true + }, + "nullable" : true, + "metadata" : { } + }, { + "name" : "boolArrayDim", + "type" : { + "type" : "array", + "elementType" : "boolean", + "containsNull" : true + }, + "nullable" : true, + "metadata" : { } + }, { + "name" : "byteDim", + "type" : { + "type" : "array", + "elementType" : "byte", + "containsNull" : true + }, + "nullable" : true, + "metadata" : { } + }, { + "name" : "outgoingTimeField", + "type" : "integer", + "nullable" : true, + "metadata" : { } + }, { + "name" : "timestampField", + "type" : "long", + "nullable" : true, + "metadata" : { } + } ] +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/BaseTest.scala b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/BaseTest.scala new file mode 100644 index 00000000000..30747a9b80b --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/BaseTest.scala @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +trait BaseTest extends AnyFunSuite with Matchers diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/ExampleSparkPinotConnectorTest.scala b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/ExampleSparkPinotConnectorTest.scala new file mode 100644 index 00000000000..3c2755baf72 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/ExampleSparkPinotConnectorTest.scala @@ -0,0 +1,213 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.Logging +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.types.{DataTypes, StructField, StructType} + +/** + * Example object to test connector with all of features. + * To run this class, first of all, + * run pinot locally(https://docs.pinot.apache.org/basics/getting-started/running-pinot-locally) + */ +object ExampleSparkPinotConnectorTest extends Logging { + + def main(args: Array[String]): Unit = { + implicit val spark: SparkSession = SparkSession + .builder() + .appName("spark-pinot-connector-test") + .master("local") + .getOrCreate() + + readOffline() + readHybrid() + readHybridWithSpecificSchema() + readHybridWithFilters() + readHybridViaGrpc() + readRealtimeViaGrpc() + readRealtimeWithFilterViaGrpc() + readHybridWithFiltersViaGrpc() + readRealtimeWithSelectionColumns() + applyJustSomeFilters() + } + + def readOffline()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_OFFLINE` table... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "offline") + .load() + + data.show() + } + + def readHybrid()(implicit spark: SparkSession): Unit = { + log.info("## Reading `airlineStats_OFFLINE and airlineStats_REALTIME` tables... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "hybrid") + .load() + + data.show() + } + + def readHybridWithSpecificSchema()(implicit spark: SparkSession): Unit = { + log.info("## Reading `airlineStats_OFFLINE and airlineStats_REALTIME` tables with specific schema... ##") + val schema = StructType( + Seq( + StructField("Distance", DataTypes.IntegerType), + StructField("AirlineID", DataTypes.IntegerType), + StructField("DaysSinceEpoch", DataTypes.IntegerType), + StructField("DestStateName", DataTypes.StringType), + StructField("Origin", DataTypes.StringType), + StructField("Carrier", DataTypes.StringType) + ) + ) + + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "HYBRID") + .schema(schema) + .load() + + data.show() + } + + def readOfflineWithFilters()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_OFFLINE` table with filter push down... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "OFFLINE") + .load() + .filter($"AirlineID" === 19805) + .filter($"DestStateName" === "Florida") + .filter($"DaysSinceEpoch".isin(16101, 16084, 16074)) + .filter($"Origin" === "ORD") + + data.show() + } + + def readHybridWithFilters()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_OFFLINE and airlineStats_REALTIME` tables with filter push down... ##") + // should return 1 data, because connector ensure that the overlap + // between realtime and offline segment data is queried exactly once + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "hybrid") + .load() + .filter($"AirlineID" === 19805) + .filter($"DestStateName" === "Florida") + .filter($"DaysSinceEpoch".isin(16101, 16084, 16074)) + .filter($"Origin" === "ORD") + + data.show() + } + + def readRealtimeWithSelectionColumns()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_REALTIME` table with column push down... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "realtime") + .load() + .select($"FlightNum", $"Origin", $"DestStateName") + + data.show() + } + + def readHybridViaGrpc()(implicit spark: SparkSession): Unit = { + log.info("## Reading `airlineStats` table... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "hybrid") + .option("useGrpcServer", "true") + .load() + + data.show() + } + + def readRealtimeViaGrpc()(implicit spark: SparkSession): Unit = { + log.info("## Reading `airlineStats_REALTIME` table... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "realtime") + .option("useGrpcServer", "true") + .load() + + data.show() + } + + def readRealtimeWithFilterViaGrpc()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_REALTIME` table... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "realtime") + .option("useGrpcServer", "true") + .load() + .filter($"DestWac" === 5) + .select($"FlightNum", $"Origin", $"DestStateName") + + data.show() + } + + def readHybridWithFiltersViaGrpc()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_OFFLINE` table with filter push down... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "hybrid") + .option("useGrpcServer", "true") + .load() + .filter($"DestStateName" === "Florida") + + data.show() + } + + + def applyJustSomeFilters()(implicit spark: SparkSession): Unit = { + import spark.implicits._ + log.info("## Reading `airlineStats_OFFLINE and airlineStats_REALTIME` tables with filter push down... ##") + val data = spark.read + .format("pinot") + .option("table", "airlineStats") + .option("tableType", "hybrid") + .load() + .filter($"DestStateName" === "Florida") + .filter($"Origin" === "ORD") + .select($"DestStateName", $"Origin", $"Distance", $"AirlineID") + + data.show() + } + +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanBuilderTest.scala b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanBuilderTest.scala new file mode 100644 index 00000000000..bbcfd6fcffe --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanBuilderTest.scala @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.PinotDataSourceReadOptions +import org.apache.spark.sql.sources.{Filter, LessThan} +import org.apache.spark.sql.types.{IntegerType, StructField, StructType} + + +class PinotScanBuilderTest extends BaseTest { + test("Builder should build a PinotScan with given filters") { + val optMap = new java.util.HashMap[String, String]() + optMap.put("table", "myTable") + optMap.put("tableType", "REALTIME") + optMap.put("broker", "localhost:7177") + val readOptions = PinotDataSourceReadOptions.from(optMap) + + // create a scan builder with custom schema + val builder = new PinotScanBuilder(readOptions) + builder.pruneColumns(StructType( + Seq(StructField("myCol",IntegerType)) + )) + + // push a filter + val lessThan = LessThan("myCol", 100) + builder.pushFilters(Array[Filter]{lessThan}) + val pushedFilters = builder.pushedFilters() + pushedFilters.length shouldEqual 1 + pushedFilters(0) shouldBe a [LessThan] + + // run the builder + val scan = builder.build() + scan shouldBe a [PinotScan] + } +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanTest.scala b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanTest.scala new file mode 100644 index 00000000000..717003ba4c9 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/PinotScanTest.scala @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.connector.spark.common.PinotDataSourceReadOptions +import org.apache.pinot.connector.spark.common.query.ScanQuery +import org.apache.pinot.spi.config.table.TableType +import org.apache.spark.sql.connector.read.PartitionReaderFactory +import org.apache.spark.sql.types.{IntegerType, StructField, StructType} + +class PinotScanTest extends BaseTest { + test("Scan should build and return a partition reader") { + val optMap = new java.util.HashMap[String, String]() + optMap.put("table", "myTable") + optMap.put("tableType", "REALTIME") + optMap.put("broker", "localhost:7177") + val readOptions = PinotDataSourceReadOptions.from(optMap) + val schema = StructType( + Seq(StructField("myCol", IntegerType)) + ) + val scanQuery = ScanQuery( + "myTable", + Some(TableType.OFFLINE), + "select * from myTable", + "") + + val scan = new PinotScan(scanQuery, schema, readOptions) + val readerFactory = scan.createReaderFactory() + + + + // assert PinotScan creates a PartitionReaderFactory + readerFactory shouldBe a [PartitionReaderFactory] + } +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/TypeConverterTest.scala b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/TypeConverterTest.scala new file mode 100644 index 00000000000..caee51d0a0f --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/TypeConverterTest.scala @@ -0,0 +1,203 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource + +import org.apache.pinot.common.datatable.DataTableFactory +import org.apache.pinot.common.utils.DataSchema +import org.apache.pinot.common.utils.DataSchema.ColumnDataType +import org.apache.pinot.connector.spark.common.PinotException +import org.apache.pinot.core.common.datatable.DataTableBuilderFactory +import org.apache.pinot.spi.data.Schema +import org.apache.pinot.spi.utils.ByteArray +import org.apache.spark.sql.catalyst.util.ArrayData +import org.apache.spark.sql.types._ +import org.apache.spark.unsafe.types.UTF8String +import org.roaringbitmap.RoaringBitmap + +import scala.io.Source + +/** + * Test pinot/spark conversions like schema, data table etc. + */ +class TypeConverterTest extends BaseTest { + + test("Pinot DataTable should be converted to Spark InternalRows") { + val columnNames = Array( + "strCol", + "intCol", + "longCol", + "floatCol", + "doubleCol", + "strArrayCol", + "intArrayCol", + "longArrayCol", + "floatArrayCol", + "doubleArrayCol", + "byteType", + "timestampArrayCol", + "timestampCol", + "booleanArrayCol", + "booleanCol", + ) + val columnTypes = Array( + ColumnDataType.STRING, + ColumnDataType.INT, + ColumnDataType.LONG, + ColumnDataType.FLOAT, + ColumnDataType.DOUBLE, + ColumnDataType.STRING_ARRAY, + ColumnDataType.INT_ARRAY, + ColumnDataType.LONG_ARRAY, + ColumnDataType.FLOAT_ARRAY, + ColumnDataType.DOUBLE_ARRAY, + ColumnDataType.BYTES, + ColumnDataType.TIMESTAMP_ARRAY, + ColumnDataType.TIMESTAMP, + ColumnDataType.BOOLEAN_ARRAY, + ColumnDataType.BOOLEAN, + ) + val dataSchema = new DataSchema(columnNames, columnTypes) + + val dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema) + dataTableBuilder.startRow() + dataTableBuilder.setColumn(0, "strValueDim") + dataTableBuilder.setColumn(1, 5) + dataTableBuilder.setColumn(2, 3L) + dataTableBuilder.setColumn(3, 10.05f) + dataTableBuilder.setColumn(4, 2.3d) + dataTableBuilder.setColumn(5, Array[String]("strArr1", "null")) + dataTableBuilder.setColumn(6, Array[Int](1, 2, 0)) + dataTableBuilder.setColumn(7, Array[Long](10L, 0)) + dataTableBuilder.setColumn(8, Array[Float](0, 15.20f)) + dataTableBuilder.setColumn(9, Array[Double](0, 10.3d)) + dataTableBuilder.setColumn(10, new ByteArray("byte_test".getBytes)) + dataTableBuilder.setColumn(11, Array[Long](123L,456L)) + dataTableBuilder.setColumn(12, 123L) + dataTableBuilder.setColumn(13, Array[Int](1,0,1,0)) + dataTableBuilder.setColumn(14, 1) + + dataTableBuilder.finishRow() + val dataTable = dataTableBuilder.build() + + val schema = StructType( + Seq( + StructField("intArrayCol", ArrayType(IntegerType)), + StructField("intCol", IntegerType), + StructField("doubleArrayCol", ArrayType(DoubleType)), + StructField("doubleCol", DoubleType), + StructField("strArrayCol", ArrayType(StringType)), + StructField("longCol", LongType), + StructField("longArrayCol", ArrayType(LongType)), + StructField("strCol", StringType), + StructField("floatArrayCol", ArrayType(FloatType)), + StructField("floatCol", FloatType), + StructField("byteType", ArrayType(ByteType)), + StructField("timestampArrayCol", ArrayType(LongType)), + StructField("timestampCol", LongType), + StructField("booleanArrayCol", ArrayType(BooleanType)), + StructField("booleanCol", BooleanType), + ) + ) + + val result = TypeConverter.pinotDataTableToInternalRows(dataTable, schema).head + result.getArray(0) shouldEqual ArrayData.toArrayData(Seq(1, 2, 0)) + result.getInt(1) shouldEqual 5 + result.getArray(2) shouldEqual ArrayData.toArrayData(Seq(0d, 10.3d)) + result.getDouble(3) shouldEqual 2.3d + result.getArray(4) shouldEqual ArrayData.toArrayData( + Seq("strArr1", "null").map(UTF8String.fromString) + ) + result.getLong(5) shouldEqual 3L + result.getArray(6) shouldEqual ArrayData.toArrayData(Seq(10L, 0L)) + result.getString(7) shouldEqual "strValueDim" + result.getArray(8) shouldEqual ArrayData.toArrayData(Seq(0f, 15.20f)) + result.getFloat(9) shouldEqual 10.05f + result.getArray(10) shouldEqual ArrayData.toArrayData("byte_test".getBytes) + result.getArray(11) shouldEqual ArrayData.toArrayData(Seq(123L,456L)) + result.getLong(12) shouldEqual 123L + result.getArray(13) shouldEqual ArrayData.toArrayData(Seq(true, false, true, false)) + result.getBoolean(14) shouldEqual true + } + + test("Method should throw field not found exception while converting pinot data table") { + val columnNames = Array("strCol", "intCol") + val columnTypes = Array(ColumnDataType.STRING, ColumnDataType.INT) + val dataSchema = new DataSchema(columnNames, columnTypes) + + val dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema) + dataTableBuilder.startRow() + dataTableBuilder.setColumn(0, "strValueDim") + dataTableBuilder.setColumn(1, 5) + dataTableBuilder.finishRow() + val dataTable = dataTableBuilder.build() + + val schema = StructType( + Seq( + StructField("strCol", StringType), + StructField("intCol", IntegerType), + StructField("longCol", LongType) + ) + ) + + val exception = intercept[PinotException] { + TypeConverter.pinotDataTableToInternalRows(dataTable, schema) + } + + exception.getMessage shouldEqual s"'longCol' not found in Pinot server response" + } + + test("Converter should identify and correctly return null rows") { + val columnNames = Array("strCol", "intCol") + val columnTypes = Array(ColumnDataType.STRING, ColumnDataType.INT) + val dataSchema = new DataSchema(columnNames, columnTypes) + DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4) + + val dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema) + dataTableBuilder.startRow() + dataTableBuilder.setColumn(0, "null") + dataTableBuilder.setColumn(1, 5) + dataTableBuilder.finishRow() + + val nullRowIds = new RoaringBitmap() + nullRowIds.add(0) + dataTableBuilder.setNullRowIds(nullRowIds) + dataTableBuilder.setNullRowIds(null) + + + val dataTable = dataTableBuilder.build() + + val schema = StructType( + Seq( + StructField("strCol", StringType, true), + StructField("intCol", IntegerType, true) + ) + ) + + val result = TypeConverter.pinotDataTableToInternalRows(dataTable, schema).head + result.get(0, StringType) shouldEqual null + } + + test("Pinot schema should be converted to spark schema") { + val pinotSchemaAsString = Source.fromResource("schema/pinot-schema.json").mkString + val resultSchema = TypeConverter.pinotSchemaToSparkSchema(Schema.fromString(pinotSchemaAsString)) + val sparkSchemaAsString = Source.fromResource("schema/spark-schema.json").mkString + val sparkSchema = DataType.fromJson(sparkSchemaAsString).asInstanceOf[StructType] + resultSchema.fields should contain theSameElementsAs sparkSchema.fields + } +} diff --git a/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/query/FilterPushDownTest.scala b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/query/FilterPushDownTest.scala new file mode 100644 index 00000000000..1bf889ddee7 --- /dev/null +++ b/pinot-connectors/pinot-spark-3-connector/src/test/scala/org/apache/pinot/connector/spark/v3/datasource/query/FilterPushDownTest.scala @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.v3.datasource.query + +import java.sql.{Date, Timestamp} + +import org.apache.pinot.connector.spark.v3.datasource.BaseTest +import org.apache.spark.sql.sources._ + +/** + * Test filter conversions => Spark filter to SQL where clause + */ +class FilterPushDownTest extends BaseTest { + + private val filters: Array[Filter] = Array( + EqualTo("attr1", 1), + In("attr2", Array("1", "2", "'5'")), + LessThan("attr3", 1), + LessThanOrEqual("attr4", 3), + GreaterThan("attr5", 10), + GreaterThanOrEqual("attr6", 15), + Not(EqualTo("attr7", "1")), + And(LessThan("attr8", 10), LessThanOrEqual("attr9", 3)), + Or(EqualTo("attr10", "hello"), GreaterThanOrEqual("attr11", 13)), + StringContains("attr12", "pinot"), + In("attr13", Array(10, 20)), + EqualNullSafe("attr20", "123"), + IsNull("attr14"), + IsNotNull("attr15"), + StringStartsWith("attr16", "pinot1"), + StringEndsWith("attr17", "pinot2"), + EqualTo("attr18", Timestamp.valueOf("2020-01-01 00:00:15")), + LessThan("attr19", Date.valueOf("2020-01-01")), + EqualTo("attr21", Seq(1, 2)), + EqualTo("attr22", 10.5d) + ) + + test("Unsupported filters should be filtered") { + val (accepted, postScan) = FilterPushDown.acceptFilters(filters) + + accepted should contain theSameElementsAs filters + postScan should contain theSameElementsAs Seq.empty + } + + test("SQL query should be created from spark filters") { + val whereClause = FilterPushDown.compileFiltersToSqlWhereClause(filters) + val expectedOutput = + s"""("attr1" = 1) AND ("attr2" IN ('1', '2', '''5''')) AND ("attr3" < 1) AND ("attr4" <= 3) AND ("attr5" > 10) AND """ + + s"""("attr6" >= 15) AND (NOT ("attr7" = '1')) AND (("attr8" < 10) AND ("attr9" <= 3)) AND """ + + s"""(("attr10" = 'hello') OR ("attr11" >= 13)) AND ("attr12" LIKE '%pinot%') AND ("attr13" IN (10, 20)) AND """ + + s"""(NOT ("attr20" != '123' OR "attr20" IS NULL OR '123' IS NULL) OR ("attr20" IS NULL AND '123' IS NULL)) AND """ + + s"""("attr14" IS NULL) AND ("attr15" IS NOT NULL) AND ("attr16" LIKE 'pinot1%') AND ("attr17" LIKE '%pinot2') AND """ + + s"""("attr18" = '2020-01-01 00:00:15.0') AND ("attr19" < '2020-01-01') AND ("attr21" = List(1, 2)) AND """ + + s"""("attr22" = 10.5)""" + + whereClause.get shouldEqual expectedOutput + } + + test("Shouldn't escape column names which are already escaped") { + val whereClause = FilterPushDown.compileFiltersToSqlWhereClause( + Array(EqualTo("\"some\".\"nested\".\"column\"", 1))) + val expectedOutput = "(\"some\".\"nested\".\"column\" = 1)" + + whereClause.get shouldEqual expectedOutput + } +} diff --git a/pinot-connectors/pinot-spark-connector/pom.xml b/pinot-connectors/pinot-spark-common/pom.xml similarity index 83% rename from pinot-connectors/pinot-spark-connector/pom.xml rename to pinot-connectors/pinot-spark-common/pom.xml index d6bf1dd57eb..030248871d9 100644 --- a/pinot-connectors/pinot-spark-connector/pom.xml +++ b/pinot-connectors/pinot-spark-common/pom.xml @@ -28,20 +28,15 @@ 0.13.0-SNAPSHOT .. - pinot-spark-connector - Pinot Spark Connector + pinot-spark-common + Pinot Spark Common https://pinot.apache.org/ ${basedir}/../.. - 2.4.5 - 0.13.0 + 0.14.2 2.8 1.3.0 3.1.1 - org.apache.pinot.\$internal - - - false @@ -85,34 +80,6 @@ - - org.apache.spark - spark-sql_${scala.compat.version} - ${spark.version} - provided - - - org.apache.curator - curator-recipes - - - com.thoughtworks.paranamer - paranamer - - - org.scala-lang.modules - scala-xml_${scala.compat.version} - - - org.scala-lang - scala-library - - - com.zaxxer - HikariCP-java7 - - - io.circe circe-parser_${scala.compat.version} @@ -156,6 +123,10 @@ org.scala-lang scala-library + + org.scala-lang + scala-reflect + @@ -180,39 +151,20 @@ - - maven-shade-plugin - - - package - - shade - - - - - com - ${shadeBase}.com - - com.google.protobuf.** - com.google.common.** - - - - - - - net.alchim31.maven scala-maven-plugin 3.2.2 - eclipse-add-source + add-source + generate-sources add-source + + src/main/java + scala-compile-first diff --git a/pinot-connectors/pinot-spark-common/src/main/java/org/apache/pinot/connector/spark/common/CaseInsensitiveStringMap.java b/pinot-connectors/pinot-spark-common/src/main/java/org/apache/pinot/connector/spark/common/CaseInsensitiveStringMap.java new file mode 100644 index 00000000000..4c8d804a6b6 --- /dev/null +++ b/pinot-connectors/pinot-spark-common/src/main/java/org/apache/pinot/connector/spark/common/CaseInsensitiveStringMap.java @@ -0,0 +1,201 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.common; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Copied from Apache Spark repository org.apache.spark.sql.util package in order to re-use + * for Spark2 connectors besides Spark3. + * This class is compatible with DataSourceOptions class used in Spark2. + * + * Case-insensitive map of string keys to string values. + *

+ * This is used to pass options to v2 implementations to ensure consistent case insensitivity. + *

+ * Methods that return keys in this map, like {@link #entrySet()} and {@link #keySet()}, return + * keys converted to lower case. This map doesn't allow null key. + */ +public class CaseInsensitiveStringMap implements Map { + private final Logger _logger = LoggerFactory.getLogger(CaseInsensitiveStringMap.class); + + private String _unsupportedOperationMsg = "CaseInsensitiveStringMap is read-only."; + + public static CaseInsensitiveStringMap empty() { + return new CaseInsensitiveStringMap(new HashMap<>(0)); + } + + private final Map _original; + + private final Map _delegate; + + public CaseInsensitiveStringMap(Map originalMap) { + _original = new HashMap<>(originalMap); + _delegate = new HashMap<>(originalMap.size()); + for (Map.Entry entry : originalMap.entrySet()) { + String key = toLowerCase(entry.getKey()); + if (_delegate.containsKey(key)) { + _logger.warn("Converting duplicated key " + entry.getKey() + + " into CaseInsensitiveStringMap."); + } + _delegate.put(key, entry.getValue()); + } + } + + @Override + public int size() { + return _delegate.size(); + } + + @Override + public boolean isEmpty() { + return _delegate.isEmpty(); + } + + private String toLowerCase(Object key) { + return key.toString().toLowerCase(Locale.ROOT); + } + + @Override + public boolean containsKey(Object key) { + return _delegate.containsKey(toLowerCase(key)); + } + + @Override + public boolean containsValue(Object value) { + return _delegate.containsValue(value); + } + + @Override + public String get(Object key) { + return _delegate.get(toLowerCase(key)); + } + + @Override + public String put(String key, String value) { + throw new UnsupportedOperationException(_unsupportedOperationMsg); + } + + @Override + public String remove(Object key) { + throw new UnsupportedOperationException(_unsupportedOperationMsg); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(_unsupportedOperationMsg); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(_unsupportedOperationMsg); + } + + @Override + public Set keySet() { + return _delegate.keySet(); + } + + @Override + public Collection values() { + return _delegate.values(); + } + + @Override + public Set> entrySet() { + return _delegate.entrySet(); + } + + /** + * Returns the boolean value to which the specified key is mapped, + * or defaultValue if there is no mapping for the key. The key match is case-insensitive. + */ + public boolean getBoolean(String key, boolean defaultValue) { + String value = get(key); + // We can't use `Boolean.parseBoolean` here, as it returns false for invalid strings. + if (value == null) { + return defaultValue; + } else if (value.equalsIgnoreCase("true")) { + return true; + } else if (value.equalsIgnoreCase("false")) { + return false; + } else { + throw new IllegalArgumentException(value + " is not a boolean string."); + } + } + + /** + * Returns the integer value to which the specified key is mapped, + * or defaultValue if there is no mapping for the key. The key match is case-insensitive. + */ + public int getInt(String key, int defaultValue) { + String value = get(key); + return value == null ? defaultValue : Integer.parseInt(value); + } + + /** + * Returns the long value to which the specified key is mapped, + * or defaultValue if there is no mapping for the key. The key match is case-insensitive. + */ + public long getLong(String key, long defaultValue) { + String value = get(key); + return value == null ? defaultValue : Long.parseLong(value); + } + + /** + * Returns the double value to which the specified key is mapped, + * or defaultValue if there is no mapping for the key. The key match is case-insensitive. + */ + public double getDouble(String key, double defaultValue) { + String value = get(key); + return value == null ? defaultValue : Double.parseDouble(value); + } + + /** + * Returns the original case-sensitive map. + */ + public Map asCaseSensitiveMap() { + return Collections.unmodifiableMap(_original); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CaseInsensitiveStringMap that = (CaseInsensitiveStringMap) o; + return _delegate.equals(that._delegate); + } + + @Override + public int hashCode() { + return Objects.hash(_delegate); + } +} diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/utils/HttpUtils.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/HttpUtils.scala similarity index 95% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/utils/HttpUtils.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/HttpUtils.scala index 20a72518635..03abe071f1f 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/utils/HttpUtils.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/HttpUtils.scala @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.utils +package org.apache.pinot.connector.spark.common import java.net.URI @@ -24,7 +24,6 @@ import org.apache.http.client.config.RequestConfig import org.apache.http.client.methods.{HttpUriRequest, RequestBuilder} import org.apache.http.impl.client.HttpClients import org.apache.http.util.EntityUtils -import org.apache.pinot.connector.spark.exceptions.HttpStatusCodeException /** * Helper Http methods to get metadata information from Pinot controller/broker. diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/utils/Logging.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/Logging.scala similarity index 97% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/utils/Logging.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/Logging.scala index 2bc6a096ab5..15f6001960d 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/utils/Logging.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/Logging.scala @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.utils +package org.apache.pinot.connector.spark.common import org.slf4j.{Logger, LoggerFactory} diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotClusterClient.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/PinotClusterClient.scala similarity index 88% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotClusterClient.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/PinotClusterClient.scala index b7642e1df8c..1c5dafe2a5b 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotClusterClient.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/PinotClusterClient.scala @@ -16,15 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.common import java.net.{URI, URLEncoder} import io.circe.Decoder import io.circe.generic.auto._ -import org.apache.pinot.connector.spark.connector.query.GeneratedSQLs -import org.apache.pinot.connector.spark.decodeTo -import org.apache.pinot.connector.spark.exceptions.{HttpStatusCodeException, PinotException} -import org.apache.pinot.connector.spark.utils.{HttpUtils, Logging} +import org.apache.pinot.connector.spark.common.query.ScanQuery import org.apache.pinot.spi.config.table.TableType import org.apache.pinot.spi.data.Schema import org.apache.pinot.spi.utils.builder.TableNameBuilder @@ -32,7 +29,7 @@ import org.apache.pinot.spi.utils.builder.TableNameBuilder import scala.util.{Failure, Success, Try} /** - * Client that read/write/prepare required data from/to Pinot. + * PinotCusterClient reads metadata from Pinot controller. */ private[pinot] object PinotClusterClient extends Logging { private val TABLE_SCHEMA_TEMPLATE = "http://%s/tables/%s/schema" @@ -144,24 +141,23 @@ private[pinot] object PinotClusterClient extends Logging { * * @return realtime and/or offline routing table(s) */ - def getRoutingTable( - brokerUrl: String, - generatedSQLs: GeneratedSQLs): Map[TableType, Map[String, List[String]]] = { + def getRoutingTable(brokerUrl: String, + scanQuery: ScanQuery): Map[TableType, Map[String, List[String]]] = { val routingTables = - if (generatedSQLs.isTableOffline) { + if (scanQuery.isTableOffline) { val offlineRoutingTable = - getRoutingTableForQuery(brokerUrl, generatedSQLs.offlineSelectQuery) + getRoutingTableForQuery(brokerUrl, scanQuery.offlineSelectQuery) Map(TableType.OFFLINE -> offlineRoutingTable) - } else if (generatedSQLs.isTableRealtime) { + } else if (scanQuery.isTableRealtime) { val realtimeRoutingTable = - getRoutingTableForQuery(brokerUrl, generatedSQLs.realtimeSelectQuery) + getRoutingTableForQuery(brokerUrl, scanQuery.realtimeSelectQuery) Map(TableType.REALTIME -> realtimeRoutingTable) } else { // hybrid table val offlineRoutingTable = - getRoutingTableForQuery(brokerUrl, generatedSQLs.offlineSelectQuery) + getRoutingTableForQuery(brokerUrl, scanQuery.offlineSelectQuery) val realtimeRoutingTable = - getRoutingTableForQuery(brokerUrl, generatedSQLs.realtimeSelectQuery) + getRoutingTableForQuery(brokerUrl, scanQuery.realtimeSelectQuery) Map( TableType.OFFLINE -> offlineRoutingTable, TableType.REALTIME -> realtimeRoutingTable @@ -217,9 +213,9 @@ private[pinot] object PinotClusterClient extends Logging { private[pinot] case class TimeBoundaryInfo(timeColumn: String, timeValue: String) { - def getOfflinePredicate: String = s"$timeColumn < $timeValue" + def getOfflinePredicate: String = s""""$timeColumn" < $timeValue""" - def getRealtimePredicate: String = s"$timeColumn >= $timeValue" + def getRealtimePredicate: String = s""""$timeColumn" >= $timeValue""" } private[pinot] case class InstanceInfo(instanceName: String, diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReadOptions.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/PinotDataSourceReadOptions.scala similarity index 64% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReadOptions.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/PinotDataSourceReadOptions.scala index 1aa643f99ae..271cb5d7cfb 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReadOptions.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/PinotDataSourceReadOptions.scala @@ -16,13 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.datasource +package org.apache.pinot.connector.spark.common + +import java.util -import org.apache.pinot.connector.spark.connector.PinotClusterClient -import org.apache.pinot.connector.spark.exceptions.PinotException -import org.apache.pinot.connector.spark.scalafyOptional import org.apache.pinot.spi.config.table.TableType -import org.apache.spark.sql.sources.v2.DataSourceOptions import scala.util.Random @@ -30,6 +28,7 @@ import scala.util.Random * To create serializable datasource reader options from spark datasource options. */ object PinotDataSourceReadOptions { + val CONFIG_TABLE_NAME = "table" val CONFIG_TABLE_TYPE = "tableType" val CONFIG_CONTROLLER = "controller" val CONFIG_BROKER = "broker" @@ -37,6 +36,8 @@ object PinotDataSourceReadOptions { val CONFIG_SEGMENTS_PER_SPLIT = "segmentsPerSplit" val CONFIG_PINOT_SERVER_TIMEOUT_MS = "pinotServerTimeoutMs" var CONFIG_USE_GRPC_SERVER = "useGrpcServer" + val CONFIG_QUERY_OPTIONS = "queryOptions" + val QUERY_OPTIONS_DELIMITER = "," private[pinot] val DEFAULT_CONTROLLER: String = "localhost:9000" private[pinot] val DEFAULT_USE_PUSH_DOWN_FILTERS: Boolean = true private[pinot] val DEFAULT_SEGMENTS_PER_SPLIT: Int = 3 @@ -45,31 +46,37 @@ object PinotDataSourceReadOptions { private[pinot] val tableTypes = Seq("OFFLINE", "REALTIME", "HYBRID") - private[pinot] def from(options: DataSourceOptions): PinotDataSourceReadOptions = { - // tableName option - if (!options.tableName().isPresent) { + private[pinot] def from(optionsMap: util.Map[String, String]): PinotDataSourceReadOptions = { + this.from(new CaseInsensitiveStringMap(optionsMap)) + } + + private[pinot] def from(options: CaseInsensitiveStringMap): PinotDataSourceReadOptions = { + if (!options.containsKey(CONFIG_TABLE_NAME)) { throw new IllegalStateException( "Table name must be specified. eg: tbl_OFFLINE, tbl_REALTIME or tbl" ) } - val tableName = options.tableName().get() - // tableType option - val tableType = scalafyOptional(options.get(CONFIG_TABLE_TYPE)) - .map(_.toUpperCase) - .map { inputTableType => - if (tableTypes.contains(inputTableType)) { - if (inputTableType == "HYBRID") None - else Some(TableType.valueOf(inputTableType)) - } else { - throw PinotException(s"Unknown `tableType`: $inputTableType") - } - } - .getOrElse { throw PinotException("`tableType` should be specified") } + if (!options.containsKey(CONFIG_TABLE_TYPE)) { + throw PinotException("`tableType` should be specified") + } + + val tableName = options.get(CONFIG_TABLE_NAME) + val tableTypeInput = options.get(CONFIG_TABLE_TYPE).toUpperCase() + val tableType = tableTypeInput match { + case "REALTIME" => Some(TableType.REALTIME) + case "OFFLINE" => Some(TableType.OFFLINE) + case "HYBRID" => None + case _ => + throw PinotException(s"Unknown `tableType`: $tableTypeInput") + } + // pinot cluster options - val controller = scalafyOptional(options.get(CONFIG_CONTROLLER)).getOrElse(DEFAULT_CONTROLLER) - val broker = scalafyOptional(options.get(PinotDataSourceReadOptions.CONFIG_BROKER)).getOrElse { - val brokerInstances = PinotClusterClient.getBrokerInstances(controller, tableName) - Random.shuffle(brokerInstances).head + val controller = options.getOrDefault(CONFIG_CONTROLLER, DEFAULT_CONTROLLER) + val broker = options.get(PinotDataSourceReadOptions.CONFIG_BROKER) match { + case s if s == null || s.isEmpty => + val brokerInstances = PinotClusterClient.getBrokerInstances(controller, tableName) + Random.shuffle(brokerInstances).head + case s => s } // connector related options val usePushDownFilters = @@ -78,6 +85,8 @@ object PinotDataSourceReadOptions { val pinotServerTimeoutMs = options.getLong(CONFIG_PINOT_SERVER_TIMEOUT_MS, DEFAULT_PINOT_SERVER_TIMEOUT_MS) val useGrpcServer = options.getBoolean(CONFIG_USE_GRPC_SERVER, DEFAULT_USE_GRPC_SERVER) + val queryOptions = options.getOrDefault(CONFIG_QUERY_OPTIONS, "") + .split(QUERY_OPTIONS_DELIMITER).filter(_.nonEmpty).toSet PinotDataSourceReadOptions( tableName, @@ -87,7 +96,8 @@ object PinotDataSourceReadOptions { usePushDownFilters, segmentsPerSplit, pinotServerTimeoutMs, - useGrpcServer + useGrpcServer, + queryOptions ) } } @@ -101,4 +111,6 @@ private[pinot] case class PinotDataSourceReadOptions( usePushDownFilters: Boolean, segmentsPerSplit: Int, pinotServerTimeoutMs: Long, - useGrpcServer: Boolean) + useGrpcServer: Boolean, + queryOptions: Set[String]) + diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/exceptions/exceptions.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/exceptions.scala similarity index 95% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/exceptions/exceptions.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/exceptions.scala index 91ac44a4f26..d4b7e9ee051 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/exceptions/exceptions.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/exceptions.scala @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.exceptions +package org.apache.pinot.connector.spark.common private[pinot] case class HttpStatusCodeException(message: String, statusCode: Int) extends Exception(message) { diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/package.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/package.scala similarity index 95% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/package.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/package.scala index 46ef45dcac4..0c3fecb80dd 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/package.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/package.scala @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector +package org.apache.pinot.connector.spark import java.util.Optional import io.circe.{Decoder, parser} -package object spark { +package object common { /** Parse json string to given model. */ def decodeTo[A: Decoder](jsonString: String): A = { diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotSplitter.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/partition/PinotSplitter.scala similarity index 86% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotSplitter.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/partition/PinotSplitter.scala index 1445b3c7672..5b8b392c738 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotSplitter.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/partition/PinotSplitter.scala @@ -16,11 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.common.partition -import org.apache.pinot.connector.spark.connector.query.GeneratedSQLs -import org.apache.pinot.connector.spark.datasource.PinotDataSourceReadOptions -import org.apache.pinot.connector.spark.utils.Logging +import org.apache.pinot.connector.spark.common.query.ScanQuery +import org.apache.pinot.connector.spark.common.{InstanceInfo, Logging, PinotDataSourceReadOptions} import org.apache.pinot.spi.config.table.TableType /** @@ -36,7 +35,7 @@ import org.apache.pinot.spi.config.table.TableType * - offline -> * - offlineServer10 -> (segment10, segment20) * - * If `segmentsPerSplit` is equal to 1, total 6 spark partition will be created like below, + * If `segmentsPerSplit` is equal to 1, total 6 spark partition will be created like below, * - partition1: realtimeServer1 -> segment1 * - partition2: realtimeServer1 -> segment2 * - partition3: realtimeServer1 -> segment3 @@ -47,7 +46,7 @@ import org.apache.pinot.spi.config.table.TableType private[pinot] object PinotSplitter extends Logging { def generatePinotSplits( - generatedSQLs: GeneratedSQLs, + query: ScanQuery, routingTable: Map[TableType, Map[String, List[String]]], instanceInfoReader: String => InstanceInfo, readParameters: PinotDataSourceReadOptions): List[PinotSplit] = { @@ -59,7 +58,7 @@ private[pinot] object PinotSplitter extends Logging { case (instanceInfo, segments) => createPinotSplitsFromSubSplits( tableType, - generatedSQLs, + query, instanceInfo, segments, readParameters.segmentsPerSplit) @@ -69,7 +68,7 @@ private[pinot] object PinotSplitter extends Logging { private def createPinotSplitsFromSubSplits( tableType: TableType, - generatedSQLs: GeneratedSQLs, + query: ScanQuery, instanceInfo: InstanceInfo, segments: List[String], segmentsPerSplit: Int): Iterator[PinotSplit] = { @@ -82,13 +81,13 @@ private[pinot] object PinotSplitter extends Logging { subSegments, tableType) } - PinotSplit(generatedSQLs, serverAndSegments) + PinotSplit(query, serverAndSegments) } } } private[pinot] case class PinotSplit( - generatedSQLs: GeneratedSQLs, + query: ScanQuery, serverAndSegments: PinotServerAndSegments) private[pinot] case class PinotServerAndSegments( @@ -97,5 +96,6 @@ private[pinot] case class PinotServerAndSegments( serverGrpcPort: Int, segments: List[String], serverType: TableType) { + override def toString: String = s"$serverHost:$serverPort($serverType)" } diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/query/GeneratedSQLs.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/query/ScanQuery.scala similarity index 92% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/query/GeneratedSQLs.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/query/ScanQuery.scala index b7e7823a92a..008e6777850 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/query/GeneratedSQLs.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/query/ScanQuery.scala @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector.query +package org.apache.pinot.connector.spark.common.query import org.apache.pinot.spi.config.table.TableType -private[pinot] case class GeneratedSQLs( +private[pinot] case class ScanQuery( rawTableName: String, tableType: Option[TableType], offlineSelectQuery: String, diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/query/SQLSelectionQueryGenerator.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/query/ScanQueryGenerator.scala similarity index 74% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/query/SQLSelectionQueryGenerator.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/query/ScanQueryGenerator.scala index 513951cd6f8..e6c1afb9c89 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/query/SQLSelectionQueryGenerator.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/query/ScanQueryGenerator.scala @@ -16,27 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector.query +package org.apache.pinot.connector.spark.common.query -import org.apache.pinot.connector.spark.connector.{PinotUtils, TimeBoundaryInfo} +import org.apache.pinot.connector.spark.common.TimeBoundaryInfo import org.apache.pinot.spi.config.table.TableType -import org.apache.pinot.spi.utils.builder.TableNameBuilder /** * Generate realtime and offline SQL queries for specified table with given columns and filters. */ -private[pinot] class SQLSelectionQueryGenerator( +private[pinot] class ScanQueryGenerator( tableName: String, tableType: Option[TableType], timeBoundaryInfo: Option[TimeBoundaryInfo], columns: Array[String], - whereClause: Option[String]) { + whereClause: Option[String], + queryOptions: Set[String]) { private val columnsExpression = columnsAsExpression() - def generateSQLs(): GeneratedSQLs = { + def generateSQLs(): ScanQuery = { val offlineSelectQuery = buildSelectQuery(TableType.OFFLINE) val realtimeSelectQuery = buildSelectQuery(TableType.REALTIME) - GeneratedSQLs( + ScanQuery( tableName, tableType, offlineSelectQuery, @@ -46,7 +46,11 @@ private[pinot] class SQLSelectionQueryGenerator( /** Get all columns if selecting columns empty(eg: resultDataFrame.count()) */ private def columnsAsExpression(): String = { - if (columns.isEmpty) "*" else columns.mkString(",") + if (columns.isEmpty) "*" else columns.map(escapeCol).mkString(",") + } + + private def escapeCol(col: String): String = { + if (col.contains("\"")) col else s""""$col"""" } /** Build realtime or offline SQL selection query. */ @@ -57,7 +61,11 @@ private[pinot] class SQLSelectionQueryGenerator( } val tableNameWithType = s"${tableName}_${tableType.toString}" - val queryBuilder = new StringBuilder(s"SELECT $columnsExpression FROM $tableNameWithType") + val queryBuilder = new StringBuilder() + + // add Query Options and SELECT clause + queryOptions.foreach(opt => queryBuilder.append(s"SET $opt;")) + queryBuilder.append(s"SELECT $columnsExpression FROM $tableNameWithType") // add where clause if exists whereClause.foreach(c => queryBuilder.append(s" WHERE $c")) @@ -79,16 +87,15 @@ private[pinot] class SQLSelectionQueryGenerator( } -private[pinot] object SQLSelectionQueryGenerator { - +private[pinot] object ScanQueryGenerator { def generate( tableName: String, tableType: Option[TableType], timeBoundaryInfo: Option[TimeBoundaryInfo], columns: Array[String], - whereClause: Option[String]): GeneratedSQLs = { - new SQLSelectionQueryGenerator(tableName, tableType, timeBoundaryInfo, columns, whereClause) + whereClause: Option[String], + queryOptions: Set[String]): ScanQuery = { + new ScanQueryGenerator(tableName, tableType, timeBoundaryInfo, columns, whereClause, queryOptions) .generateSQLs() } - } diff --git a/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotAbstractPartitionReader.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotAbstractPartitionReader.scala new file mode 100644 index 00000000000..c321edadb17 --- /dev/null +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotAbstractPartitionReader.scala @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.connector.spark.common.reader + +import org.apache.pinot.common.datatable.DataTable +import org.apache.pinot.connector.spark.common.PinotDataSourceReadOptions +import org.apache.pinot.connector.spark.common.partition.PinotSplit + +import java.io.Closeable + +/** + * Abstract partition reader is designed to be shared between two concrete reader implementations + * for Spark2 and Spark3 connectors. + * + * @tparam RowType + */ +trait PinotAbstractPartitionReader[RowType] { + def _partitionId: Int + def _pinotSplit: PinotSplit + def _dataSourceOptions: PinotDataSourceReadOptions + def _translator: (DataTable) => Seq[RowType] + + private val (responseIterator: Iterator[RowType], source: Closeable) = getIteratorAndSource() + private[this] var currentRow: RowType = _ + + def next(): Boolean = { + if (!responseIterator.hasNext) { + return false + } + currentRow = responseIterator.next() + true + } + + def get(): RowType = { + currentRow + } + + def close(): Unit = { + source.close() + } + + private def getIteratorAndSource(): (Iterator[RowType], Closeable) = { + if (_dataSourceOptions.useGrpcServer) { + val dataFetcher = PinotGrpcServerDataFetcher(_pinotSplit) + val iterable = dataFetcher.fetchData() + .flatMap(_translator) + (iterable, dataFetcher) + } else { + (PinotServerDataFetcher(_partitionId, _pinotSplit, _dataSourceOptions) + .fetchData() + .flatMap(_translator) + .toIterator, + () => {}) + } + } +} diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotGrpcServerDataFetcher.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotGrpcServerDataFetcher.scala similarity index 80% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotGrpcServerDataFetcher.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotGrpcServerDataFetcher.scala index f543b5b7c99..ecd7b5e0ec3 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotGrpcServerDataFetcher.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotGrpcServerDataFetcher.scala @@ -16,23 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.common.reader import io.grpc.ManagedChannelBuilder import org.apache.pinot.common.datatable.{DataTable, DataTableFactory} import org.apache.pinot.common.proto.PinotQueryServerGrpc import org.apache.pinot.common.proto.Server.ServerRequest -import org.apache.pinot.connector.spark.utils.Logging +import org.apache.pinot.connector.spark.common.Logging +import org.apache.pinot.connector.spark.common.partition.PinotSplit import org.apache.pinot.spi.config.table.TableType +import java.io.Closeable import scala.collection.JavaConverters._ /** - * Data fetcher from Pinot Grpc server with specific segments. + * Data fetcher from Pinot Grpc server for specific segments. * Eg: offline-server1: segment1, segment2, segment3 */ -private[pinot] class PinotGrpcServerDataFetcher(pinotSplit: PinotSplit) - extends Logging { +private[reader] class PinotGrpcServerDataFetcher(pinotSplit: PinotSplit) + extends Logging with Closeable { private val channel = ManagedChannelBuilder .forAddress(pinotSplit.serverAndSegments.serverHost, pinotSplit.serverAndSegments.serverGrpcPort) @@ -41,35 +43,37 @@ private[pinot] class PinotGrpcServerDataFetcher(pinotSplit: PinotSplit) .asInstanceOf[ManagedChannelBuilder[_]].build() private val pinotServerBlockingStub = PinotQueryServerGrpc.newBlockingStub(channel) - def fetchData(): List[DataTable] = { - val requestStartTime = System.nanoTime() + def fetchData(): Iterator[DataTable] = { val request = ServerRequest.newBuilder() .putMetadata("enableStreaming", "true") .addAllSegments(pinotSplit.serverAndSegments.segments.asJava) .setSql( pinotSplit.serverAndSegments.serverType match { case TableType.OFFLINE => - pinotSplit.generatedSQLs.offlineSelectQuery + pinotSplit.query.offlineSelectQuery case TableType.REALTIME => - pinotSplit.generatedSQLs.realtimeSelectQuery + pinotSplit.query.realtimeSelectQuery } ) .build() val serverResponse = pinotServerBlockingStub.submit(request) - logInfo(s"Pinot server total response time in millis: ${System.nanoTime() - requestStartTime}") - try { val dataTables = for { - serverResponse <- serverResponse.asScala.toList + serverResponse <- serverResponse.asScala if serverResponse.getMetadataMap.get("responseType") == "data" } yield DataTableFactory.getDataTable(serverResponse.getPayload.toByteArray) dataTables.filter(_.getNumberOfRows > 0) + } catch { case e: io.grpc.StatusRuntimeException => logError(s"Caught exception when reading data from ${pinotSplit.serverAndSegments.serverHost}:${pinotSplit.serverAndSegments.serverGrpcPort}: ${e}") throw e - } finally { + } + } + + def close(): Unit = { + if (!channel.isShutdown) { channel.shutdown() logInfo("Pinot server connection closed") } diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotServerDataFetcher.scala b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotServerDataFetcher.scala similarity index 89% rename from pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotServerDataFetcher.scala rename to pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotServerDataFetcher.scala index 81bb405d5ef..e6d88755c5f 100644 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/connector/PinotServerDataFetcher.scala +++ b/pinot-connectors/pinot-spark-common/src/main/scala/org/apache/pinot/connector/spark/common/reader/PinotServerDataFetcher.scala @@ -16,15 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.common.reader import org.apache.helix.model.InstanceConfig import org.apache.pinot.common.datatable.DataTable import org.apache.pinot.common.metrics.BrokerMetrics import org.apache.pinot.common.request.BrokerRequest -import org.apache.pinot.connector.spark.datasource.PinotDataSourceReadOptions -import org.apache.pinot.connector.spark.exceptions.PinotException -import org.apache.pinot.connector.spark.utils.Logging +import org.apache.pinot.connector.spark.common.partition.PinotSplit +import org.apache.pinot.connector.spark.common.{Logging, PinotDataSourceReadOptions, PinotException} import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager import org.apache.pinot.core.transport.{AsyncQueryResponse, QueryRouter, ServerInstance} import org.apache.pinot.spi.config.table.TableType @@ -39,7 +38,7 @@ import scala.collection.JavaConverters._ * Actual data fetcher from Pinot server with specific segments. * Eg: offline-server1: segment1, segment2, segment3 */ -private[pinot] class PinotServerDataFetcher( +private[reader] class PinotServerDataFetcher( partitionId: Int, pinotSplit: PinotSplit, dataSourceOptions: PinotDataSourceReadOptions) @@ -59,11 +58,11 @@ private[pinot] class PinotServerDataFetcher( val pinotServerAsyncQueryResponse = pinotSplit.serverAndSegments.serverType match { case TableType.REALTIME => val realtimeBrokerRequest = - CalciteSqlCompiler.compileToBrokerRequest(pinotSplit.generatedSQLs.realtimeSelectQuery) + CalciteSqlCompiler.compileToBrokerRequest(pinotSplit.query.realtimeSelectQuery) submitRequestToPinotServer(null, null, realtimeBrokerRequest, routingTableForRequest) case TableType.OFFLINE => val offlineBrokerRequest = - CalciteSqlCompiler.compileToBrokerRequest(pinotSplit.generatedSQLs.offlineSelectQuery) + CalciteSqlCompiler.compileToBrokerRequest(pinotSplit.query.offlineSelectQuery) submitRequestToPinotServer(offlineBrokerRequest, routingTableForRequest, null, null) } @@ -110,10 +109,10 @@ private[pinot] class PinotServerDataFetcher( offlineRoutingTable: JMap[ServerInstance, JList[String]], realtimeBrokerRequest: BrokerRequest, realtimeRoutingTable: JMap[ServerInstance, JList[String]]): AsyncQueryResponse = { - logInfo(s"Request is sending to the ${pinotSplit.serverAndSegments.toString}") + logInfo(s"Sending request to ${pinotSplit.serverAndSegments.toString}") queryRouter.submitQuery( partitionId, - pinotSplit.generatedSQLs.rawTableName, + pinotSplit.query.rawTableName, offlineBrokerRequest, offlineRoutingTable, realtimeBrokerRequest, diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-0.9/src/test/resources/log4j2.xml b/pinot-connectors/pinot-spark-common/src/test/resources/log4j2.xml similarity index 100% rename from pinot-plugins/pinot-stream-ingestion/pinot-kafka-0.9/src/test/resources/log4j2.xml rename to pinot-connectors/pinot-spark-common/src/test/resources/log4j2.xml diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/BaseTest.scala b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/BaseTest.scala similarity index 95% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/BaseTest.scala rename to pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/BaseTest.scala index f06ed45ecb4..e110c00954b 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/BaseTest.scala +++ b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/BaseTest.scala @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark +package org.apache.pinot.connector.spark.common import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReadOptionsTest.scala b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/PinotDataSourceReadOptionsTest.scala similarity index 80% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReadOptionsTest.scala rename to pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/PinotDataSourceReadOptionsTest.scala index f395e0c5430..7c08351557b 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/datasource/PinotDataSourceReadOptionsTest.scala +++ b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/PinotDataSourceReadOptionsTest.scala @@ -16,11 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.datasource - -import org.apache.pinot.connector.spark.{BaseTest, datasource} -import org.apache.pinot.connector.spark.exceptions.PinotException -import org.apache.spark.sql.sources.v2.DataSourceOptions +package org.apache.pinot.connector.spark.common import scala.collection.JavaConverters._ @@ -31,17 +27,17 @@ class PinotDataSourceReadOptionsTest extends BaseTest { test("Spark DataSourceOptions should be converted to the PinotDataSourceReadOptions") { val options = Map( - DataSourceOptions.TABLE_KEY -> "tbl", + PinotDataSourceReadOptions.CONFIG_TABLE_NAME -> "tbl", PinotDataSourceReadOptions.CONFIG_TABLE_TYPE -> "hybrid", PinotDataSourceReadOptions.CONFIG_CONTROLLER -> "localhost:9000", PinotDataSourceReadOptions.CONFIG_BROKER -> "localhost:8000", PinotDataSourceReadOptions.CONFIG_SEGMENTS_PER_SPLIT -> "1", PinotDataSourceReadOptions.CONFIG_USE_PUSH_DOWN_FILTERS -> "false", PinotDataSourceReadOptions.CONFIG_USE_GRPC_SERVER -> "false", + PinotDataSourceReadOptions.CONFIG_QUERY_OPTIONS -> "a=1,b=2" ) - val datasourceOptions = new DataSourceOptions(options.asJava) - val pinotDataSourceReadOptions = PinotDataSourceReadOptions.from(datasourceOptions) + val pinotDataSourceReadOptions = PinotDataSourceReadOptions.from(options.asJava) val expected = PinotDataSourceReadOptions( @@ -52,7 +48,8 @@ class PinotDataSourceReadOptionsTest extends BaseTest { false, 1, 10000, - false + false, + Set("a=1", "b=2") ) pinotDataSourceReadOptions shouldEqual expected @@ -61,25 +58,25 @@ class PinotDataSourceReadOptionsTest extends BaseTest { test("Method should throw exception if `tableType` option is missing or wrong") { // missing val missingOption = Map( - DataSourceOptions.TABLE_KEY -> "tbl", + PinotDataSourceReadOptions.CONFIG_TABLE_NAME -> "tbl", PinotDataSourceReadOptions.CONFIG_CONTROLLER -> "localhost:9000", PinotDataSourceReadOptions.CONFIG_BROKER -> "localhost:8000" ) // wrong input val wrongOption = Map( - DataSourceOptions.TABLE_KEY -> "tbl", + PinotDataSourceReadOptions.CONFIG_TABLE_NAME -> "tbl", PinotDataSourceReadOptions.CONFIG_TABLE_TYPE -> "offlinee", PinotDataSourceReadOptions.CONFIG_CONTROLLER -> "localhost:9000", PinotDataSourceReadOptions.CONFIG_BROKER -> "localhost:8000" ) val missingException = intercept[PinotException] { - PinotDataSourceReadOptions.from(new DataSourceOptions(missingOption.asJava)) + PinotDataSourceReadOptions.from(missingOption.asJava) } val wrongException = intercept[PinotException] { - PinotDataSourceReadOptions.from(new DataSourceOptions(wrongOption.asJava)) + PinotDataSourceReadOptions.from(wrongOption.asJava) } missingException.getMessage shouldEqual "`tableType` should be specified" diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/PinotSplitterTest.scala b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/PinotSplitterTest.scala similarity index 80% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/PinotSplitterTest.scala rename to pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/PinotSplitterTest.scala index 43ca40cd23c..9b5a057c7ae 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/PinotSplitterTest.scala +++ b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/PinotSplitterTest.scala @@ -16,19 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector +package org.apache.pinot.connector.spark.common -import org.apache.pinot.connector.spark.BaseTest -import org.apache.pinot.connector.spark.connector.query.GeneratedSQLs -import org.apache.pinot.connector.spark.datasource.PinotDataSourceReadOptions +import org.apache.pinot.connector.spark.common.partition.{PinotServerAndSegments, PinotSplit, PinotSplitter} +import org.apache.pinot.connector.spark.common.query.ScanQuery import org.apache.pinot.spi.config.table.TableType + import java.util.regex.Pattern /** * Test num of Spark partitions by routing table and input configs. */ class PinotSplitterTest extends BaseTest { - private val generatedSql = GeneratedSQLs("tbl", None, "", "") + private val query = ScanQuery("tbl", None, "", "") private val mockInstanceInfoReader = (server: String) => { val matcher = Pattern.compile("Server_(.*)_(\\d+)").matcher(server) matcher.matches() @@ -56,13 +56,14 @@ class PinotSplitterTest extends BaseTest { false, segmentsPerSplit, 1000, - false) + false, + Set()) } test("Total 5 partition splits should be created for maxNumSegmentPerServerRequest = 3") { val readOptions = getReadOptionsWithSegmentsPerSplit(3) val splitResults = - PinotSplitter.generatePinotSplits(generatedSql, routingTable, mockInstanceInfoReader, readOptions) + PinotSplitter.generatePinotSplits(query, routingTable, mockInstanceInfoReader, readOptions) splitResults.size shouldEqual 5 } @@ -70,7 +71,7 @@ class PinotSplitterTest extends BaseTest { test("Total 5 partition splits should be created for maxNumSegmentPerServerRequest = 90") { val readOptions = getReadOptionsWithSegmentsPerSplit(90) val splitResults = - PinotSplitter.generatePinotSplits(generatedSql, routingTable, mockInstanceInfoReader, readOptions) + PinotSplitter.generatePinotSplits(query, routingTable, mockInstanceInfoReader, readOptions) splitResults.size shouldEqual 5 } @@ -78,7 +79,7 @@ class PinotSplitterTest extends BaseTest { test("Total 10 partition splits should be created for maxNumSegmentPerServerRequest = 1") { val readOptions = getReadOptionsWithSegmentsPerSplit(1) val splitResults = - PinotSplitter.generatePinotSplits(generatedSql, routingTable, mockInstanceInfoReader, readOptions) + PinotSplitter.generatePinotSplits(query, routingTable, mockInstanceInfoReader, readOptions) splitResults.size shouldEqual 10 } @@ -89,10 +90,10 @@ class PinotSplitterTest extends BaseTest { ) val readOptions = getReadOptionsWithSegmentsPerSplit(5) - val splitResults = PinotSplitter.generatePinotSplits(generatedSql, inputRoutingTable, mockInstanceInfoReader, readOptions) + val splitResults = PinotSplitter.generatePinotSplits(query, inputRoutingTable, mockInstanceInfoReader, readOptions) val expectedOutput = List( PinotSplit( - generatedSql, + query, PinotServerAndSegments("192.168.1.100", "9000", -1, List("segment1"), TableType.REALTIME) ) ) @@ -112,17 +113,18 @@ class PinotSplitterTest extends BaseTest { false, 1, 1000, - true) + true, + Set()) val inputGrpcPortReader = (server: String) => { InstanceInfo(server, "192.168.1.100", "9000", 8090) } val splitResults = - PinotSplitter.generatePinotSplits(generatedSql, inputRoutingTable, inputGrpcPortReader, inputReadOptions) + PinotSplitter.generatePinotSplits(query, inputRoutingTable, inputGrpcPortReader, inputReadOptions) val expectedOutput = List( PinotSplit( - generatedSql, + query, PinotServerAndSegments("192.168.1.100", "9000", 8090, List("segment1"), TableType.REALTIME) ) ) diff --git a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/query/SQLSelectionQueryGeneratorTest.scala b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/query/ScanQueryGeneratorTest.scala similarity index 55% rename from pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/query/SQLSelectionQueryGeneratorTest.scala rename to pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/query/ScanQueryGeneratorTest.scala index 608d18e3f80..73fc9d85848 100644 --- a/pinot-connectors/pinot-spark-connector/src/test/scala/org/apache/pinot/connector/spark/connector/query/SQLSelectionQueryGeneratorTest.scala +++ b/pinot-connectors/pinot-spark-common/src/test/scala/org/apache/pinot/connector/spark/common/query/ScanQueryGeneratorTest.scala @@ -16,29 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.connector.spark.connector.query +package org.apache.pinot.connector.spark.common.query -import org.apache.pinot.connector.spark.BaseTest -import org.apache.pinot.connector.spark.connector.TimeBoundaryInfo +import org.apache.pinot.connector.spark.common.{BaseTest, TimeBoundaryInfo} import org.apache.pinot.spi.config.table.TableType /** * Test SQL query generation from spark push down filters, selection columns etc. */ -class SQLSelectionQueryGeneratorTest extends BaseTest { - private val columns = Array("c1, c2") +class ScanQueryGeneratorTest extends BaseTest { + private val columns = Array("c1","c2") private val tableName = "tbl" private val tableType = Some(TableType.OFFLINE) private val whereClause = Some("c1 = 5 OR c2 = 'hello'") - private val limit = s"LIMIT ${Int.MaxValue}" + private val limit = s"""LIMIT ${Int.MaxValue}""" test("Queries should be created with given filters") { val pinotQueries = - SQLSelectionQueryGenerator.generate(tableName, tableType, None, columns, whereClause) + ScanQueryGenerator.generate(tableName, tableType, None, columns, whereClause, Set()) val expectedRealtimeQuery = - s"SELECT c1, c2 FROM ${tableName}_REALTIME WHERE ${whereClause.get} $limit" + s"""SELECT "c1","c2" FROM ${tableName}_REALTIME WHERE ${whereClause.get} $limit""" val expectedOfflineQuery = - s"SELECT c1, c2 FROM ${tableName}_OFFLINE WHERE ${whereClause.get} $limit" + s"""SELECT "c1","c2" FROM ${tableName}_OFFLINE WHERE ${whereClause.get} $limit""" pinotQueries.realtimeSelectQuery shouldEqual expectedRealtimeQuery pinotQueries.offlineSelectQuery shouldEqual expectedOfflineQuery @@ -46,15 +45,15 @@ class SQLSelectionQueryGeneratorTest extends BaseTest { test("Time boundary info should be added to existing where clause") { val timeBoundaryInfo = TimeBoundaryInfo("timeCol", "12345") - val pinotQueries = SQLSelectionQueryGenerator - .generate(tableName, tableType, Some(timeBoundaryInfo), columns, whereClause) + val pinotQueries = ScanQueryGenerator + .generate(tableName, tableType, Some(timeBoundaryInfo), columns, whereClause, Set()) - val realtimeWhereClause = s"${whereClause.get} AND timeCol >= 12345" - val offlineWhereClause = s"${whereClause.get} AND timeCol < 12345" + val realtimeWhereClause = s"""${whereClause.get} AND "timeCol" >= 12345""" + val offlineWhereClause = s"""${whereClause.get} AND "timeCol" < 12345""" val expectedRealtimeQuery = - s"SELECT c1, c2 FROM ${tableName}_REALTIME WHERE $realtimeWhereClause $limit" + s"""SELECT "c1","c2" FROM ${tableName}_REALTIME WHERE $realtimeWhereClause $limit""" val expectedOfflineQuery = - s"SELECT c1, c2 FROM ${tableName}_OFFLINE WHERE $offlineWhereClause $limit" + s"""SELECT "c1","c2" FROM ${tableName}_OFFLINE WHERE $offlineWhereClause $limit""" pinotQueries.realtimeSelectQuery shouldEqual expectedRealtimeQuery pinotQueries.offlineSelectQuery shouldEqual expectedOfflineQuery @@ -62,23 +61,23 @@ class SQLSelectionQueryGeneratorTest extends BaseTest { test("Time boundary info should be added to where clause") { val timeBoundaryInfo = TimeBoundaryInfo("timeCol", "12345") - val pinotQueries = SQLSelectionQueryGenerator - .generate(tableName, tableType, Some(timeBoundaryInfo), columns, None) + val pinotQueries = ScanQueryGenerator + .generate(tableName, tableType, Some(timeBoundaryInfo), columns, None, Set()) - val realtimeWhereClause = s"timeCol >= 12345" - val offlineWhereClause = s"timeCol < 12345" + val realtimeWhereClause = s""""timeCol" >= 12345""" + val offlineWhereClause = s""""timeCol" < 12345""" val expectedRealtimeQuery = - s"SELECT c1, c2 FROM ${tableName}_REALTIME WHERE $realtimeWhereClause $limit" + s"""SELECT "c1","c2" FROM ${tableName}_REALTIME WHERE $realtimeWhereClause $limit""" val expectedOfflineQuery = - s"SELECT c1, c2 FROM ${tableName}_OFFLINE WHERE $offlineWhereClause $limit" + s"""SELECT "c1","c2" FROM ${tableName}_OFFLINE WHERE $offlineWhereClause $limit""" pinotQueries.realtimeSelectQuery shouldEqual expectedRealtimeQuery pinotQueries.offlineSelectQuery shouldEqual expectedOfflineQuery } test("Selection query should be created with '*' column expressions without filters") { - val pinotQueries = SQLSelectionQueryGenerator - .generate(tableName, tableType, None, Array.empty, None) + val pinotQueries = ScanQueryGenerator + .generate(tableName, tableType, None, Array.empty, None, Set()) val expectedRealtimeQuery = s"SELECT * FROM ${tableName}_REALTIME $limit" @@ -89,4 +88,18 @@ class SQLSelectionQueryGeneratorTest extends BaseTest { pinotQueries.offlineSelectQuery shouldEqual expectedOfflineQuery } + test("Query options should be added to the beginning of the query") { + val queryOptions = Set("enableNullHandling=true","skipUpsert=true") + val pinotQueries = ScanQueryGenerator + .generate(tableName, tableType, None, Array.empty, None, queryOptions) + + val expectedRealtimeQuery = + s"SET enableNullHandling=true;SET skipUpsert=true;SELECT * FROM ${tableName}_REALTIME $limit" + val expectedOfflineQuery = + s"SET enableNullHandling=true;SET skipUpsert=true;SELECT * FROM ${tableName}_OFFLINE $limit" + + pinotQueries.realtimeSelectQuery shouldEqual expectedRealtimeQuery + pinotQueries.offlineSelectQuery shouldEqual expectedOfflineQuery + } + } diff --git a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartitionReader.scala b/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartitionReader.scala deleted file mode 100644 index de42b7a6cf8..00000000000 --- a/pinot-connectors/pinot-spark-connector/src/main/scala/org/apache/pinot/connector/spark/datasource/PinotInputPartitionReader.scala +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.connector.spark.datasource - -import org.apache.pinot.connector.spark.connector.{PinotGrpcServerDataFetcher, PinotServerDataFetcher, PinotSplit, PinotUtils} -import org.apache.spark.sql.catalyst.InternalRow -import org.apache.spark.sql.sources.v2.reader.InputPartitionReader -import org.apache.spark.sql.types.StructType - -/** - * Actual data reader on spark worker side. - * Represents a spark partition, and is receive data from specified pinot server-segment list. - */ -class PinotInputPartitionReader( - schema: StructType, - partitionId: Int, - pinotSplit: PinotSplit, - dataSourceOptions: PinotDataSourceReadOptions) - extends InputPartitionReader[InternalRow] { - private val responseIterator: Iterator[InternalRow] = fetchDataAndConvertToInternalRows() - private[this] var currentRow: InternalRow = _ - - override def next(): Boolean = { - if (!responseIterator.hasNext) { - return false - } - currentRow = responseIterator.next() - true - } - - override def get(): InternalRow = { - currentRow - } - - override def close(): Unit = {} - - private def fetchDataAndConvertToInternalRows(): Iterator[InternalRow] = { - if (dataSourceOptions.useGrpcServer) - PinotGrpcServerDataFetcher(pinotSplit) - .fetchData() - .flatMap(PinotUtils.pinotDataTableToInternalRows(_, schema)) - .toIterator - else - PinotServerDataFetcher(partitionId, pinotSplit, dataSourceOptions) - .fetchData() - .flatMap(PinotUtils.pinotDataTableToInternalRows(_, schema)) - .toIterator - } -} diff --git a/pinot-connectors/pom.xml b/pinot-connectors/pom.xml index f78958803fe..4d1cc66c2db 100644 --- a/pinot-connectors/pom.xml +++ b/pinot-connectors/pom.xml @@ -38,7 +38,9 @@ - pinot-spark-connector + pinot-spark-common + pinot-spark-2-connector + pinot-spark-3-connector pinot-flink-connector diff --git a/pinot-connectors/prestodb-pinot-dependencies/pinot-java-client-jdk8/pom.xml b/pinot-connectors/prestodb-pinot-dependencies/pinot-java-client-jdk8/pom.xml deleted file mode 100644 index 0da8b1f8c48..00000000000 --- a/pinot-connectors/prestodb-pinot-dependencies/pinot-java-client-jdk8/pom.xml +++ /dev/null @@ -1,118 +0,0 @@ - - - - 4.0.0 - - - prestodb-pinot-dependencies - org.apache.pinot - 0.13.0-SNAPSHOT - .. - - - pinot-java-client-jdk8 - Pinot Java Client JDK 8 - https://pinot.apache.org/ - - ${basedir}/../../.. - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - 1 - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - - - - org.apache.pinot - pinot-spi-jdk8 - ${project.version} - - - org.apache.pinot - pinot-common-jdk8 - ${project.version} - - - org.testng - testng - test - - - com.fasterxml.jackson.core - jackson-databind - - - org.asynchttpclient - async-http-client - - - io.netty - netty - - - io.netty - netty-transport-native-kqueue - - - io.netty - netty-transport-native-unix-common - - - - - com.101tec - zkclient - - - io.netty - netty - - - - - org.slf4j - slf4j-api - - - com.google.code.findbugs - jsr305 - - - org.mockito - mockito-core - test - - - diff --git a/pinot-connectors/prestodb-pinot-dependencies/pinot-java-client-jdk8/src b/pinot-connectors/prestodb-pinot-dependencies/pinot-java-client-jdk8/src deleted file mode 120000 index 6aa1a0560f9..00000000000 --- a/pinot-connectors/prestodb-pinot-dependencies/pinot-java-client-jdk8/src +++ /dev/null @@ -1 +0,0 @@ -../../../pinot-clients/pinot-java-client/src \ No newline at end of file diff --git a/pinot-connectors/prestodb-pinot-dependencies/pinot-segment-spi-jdk8/pom.xml b/pinot-connectors/prestodb-pinot-dependencies/pinot-segment-spi-jdk8/pom.xml index 576b286d55c..732b9017fca 100644 --- a/pinot-connectors/prestodb-pinot-dependencies/pinot-segment-spi-jdk8/pom.xml +++ b/pinot-connectors/prestodb-pinot-dependencies/pinot-segment-spi-jdk8/pom.xml @@ -60,6 +60,18 @@ org.xerial.larray larray-mmap + + net.openhft + posix + + + net.openhft + chronicle-core + + + org.apache.calcite + calcite-core + diff --git a/pinot-connectors/prestodb-pinot-dependencies/pom.xml b/pinot-connectors/prestodb-pinot-dependencies/pom.xml index b1ad5b6d7bb..54e6ec54fda 100644 --- a/pinot-connectors/prestodb-pinot-dependencies/pom.xml +++ b/pinot-connectors/prestodb-pinot-dependencies/pom.xml @@ -43,9 +43,7 @@ pinot-spi-jdk8 pinot-common-jdk8 - pinot-java-client-jdk8 pinot-segment-spi-jdk8 - presto-pinot-driver diff --git a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/pom.xml b/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/pom.xml deleted file mode 100644 index bfc1a1b6226..00000000000 --- a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/pom.xml +++ /dev/null @@ -1,460 +0,0 @@ - - - - 4.0.0 - jar - - - prestodb-pinot-dependencies - org.apache.pinot - 0.13.0-SNAPSHOT - .. - - - Presto Pinot Driver - org.apache.pinot - presto-pinot-driver - - - ${basedir}/../../.. - org.apache.pinot.\$internal - 8 - - - - - - org.antlr - antlr4-runtime - - - - org.apache.pinot - pinot-spi-jdk8 - ${project.version} - - - org.slf4j - slf4j-api - - - org.reflections - reflections - - - org.slf4j - slf4j-log4j12 - - - log4j - log4j - - - log4j - log4j - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.apache.logging.log4j - log4j-1.2-api - - - org.antlr - antlr4-annotations - - - it.unimi.dsi - fastutil - - - org.apache.kafka - kafka-clients - - - org.apache.kafka - kafka_2.10 - - - org.codehaus.jackson - jackson-mapper-asl - - - javax.validation - validation-api - - - joda-time - joda-time - - - org.apache.httpcomponents - httpcore - - - org.codehaus.jackson - jackson-core-asl - - - org.apache.zookeeper - zookeeper - - - javax.servlet - servlet-api - - - - - org.apache.pinot - pinot-common-jdk8 - ${project.version} - - - org.checkerframework - checker-compat-qual - - - org.reflections - reflections - - - org.slf4j - slf4j-api - - - jline - jline - - - org.slf4j - slf4j-log4j12 - - - log4j - log4j - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.apache.logging.log4j - log4j-1.2-api - - - org.antlr - antlr4-annotations - - - org.apache.kafka - kafka-clients - - - org.codehaus.jackson - jackson-mapper-asl - - - org.apache.kafka - kafka_2.10 - - - commons-beanutils - commons-beanutils-core - - - log4j - log4j - - - javax.validation - validation-api - - - javax.servlet - javax.servlet-api - - - org.glassfish.hk2.external - jakarta.inject - - - jakarta.ws.rs - jakarta.ws.rs-api - - - jakarta.annotation - jakarta.annotation-api - - - com.google.guava - guava - - - joda-time - joda-time - - - org.apache.httpcomponents - httpcore - - - org.apache.zookeeper - zookeeper - - - commons-codec - commons-codec - - - org.glassfish.jersey.core - jersey-server - - - javax.servlet - servlet-api - - - org.osgi - org.osgi.core - - - - - - org.apache.helix - helix-core - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-log4j12 - - - log4j - log4j - - - org.antlr - antlr4-annotations - - - it.unimi.dsi - fastutil - - - org.apache.kafka - kafka-clients - - - org.apache.kafka - kafka_2.10 - - - org.codehaus.jackson - jackson-mapper-asl - - - javax.validation - validation-api - - - joda-time - joda-time - - - org.apache.httpcomponents - httpcore - - - org.codehaus.jackson - jackson-core-asl - - - org.apache.zookeeper - zookeeper - - - commons-codec - commons-codec - - - javax.servlet - servlet-api - - - org.osgi - org.osgi.core - - - - - org.apache.commons - commons-lang3 - - - org.reflections - reflections - - - com.google.code.findbugs - annotations - - - - - io.grpc - grpc-api - 1.41.0 - - - com.google.guava - guava - - - - - com.google.protobuf - protobuf-java - - - com.google.guava - guava - - - - - org.osgi - org.osgi.core - 4.2.0 - provided - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.1 - - - package - - shade - - - true - true - ${project.build.directory}/pom.xml - true - - - com.google.guava:* - com.google.protobuf:* - com.yammer.metrics:* - commons-*:* - io.grpc:* - io.netty:* - org.apache.commons:* - org.apache.pinot:* - org.apache.http:* - org.antlr:* - org.apache.thrift:* - org.reflections:* - - - - - - com.google - ${shadeBase}.com.google - - - org.antlr - ${shadeBase}.org.antlr - - - org.apache.commons - ${shadeBase}.org.apache.commons - - - org.apache.http - ${shadeBase}.org.apache.http - - - org.apache.thrift - ${shadeBase}.org.apache.thrift - - - io.netty - ${shadeBase}.io.netty - - - org.jboss - ${shadeBase}.org.jboss - - - com.yammer - ${shadeBase}.com.yammer - - - org.reflections - ${shadeBase}.org.reflections - - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - META-INF/maven/** - - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.0.0 - - - org.apache.maven.plugins - maven-install-plugin - - - maven-surefire-plugin - - - - diff --git a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/grpc/PinotStreamingQueryClient.java b/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/grpc/PinotStreamingQueryClient.java deleted file mode 100644 index f6a9ccceb31..00000000000 --- a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/grpc/PinotStreamingQueryClient.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.connector.presto.grpc; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import org.apache.pinot.common.config.GrpcConfig; -import org.apache.pinot.common.proto.Server; -import org.apache.pinot.common.utils.grpc.GrpcQueryClient; -import org.apache.pinot.common.utils.grpc.GrpcRequestBuilder; - - -/** - * Grpc based Pinot query client. - */ -public class PinotStreamingQueryClient { - private final Map _grpcQueryClientMap = new HashMap<>(); - private final GrpcConfig _config; - - public PinotStreamingQueryClient(GrpcConfig config) { - _config = config; - } - - public Iterator submit(String host, int port, GrpcRequestBuilder requestBuilder) { - GrpcQueryClient client = getOrCreateGrpcQueryClient(host, port); - return client.submit(requestBuilder.build()); - } - - private GrpcQueryClient getOrCreateGrpcQueryClient(String host, int port) { - String key = String.format("%s_%d", host, port); - if (!_grpcQueryClientMap.containsKey(key)) { - _grpcQueryClientMap.put(key, new GrpcQueryClient(host, port, _config)); - } - return _grpcQueryClientMap.get(key); - } -} diff --git a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/grpc/Utils.java b/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/grpc/Utils.java deleted file mode 100644 index f90e9ba8c76..00000000000 --- a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/grpc/Utils.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -/* - * 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. - */ -package org.apache.pinot.connector.presto.grpc; - -import com.google.protobuf.ByteString; -import org.apache.helix.model.InstanceConfig; - - -/** - * Utils used in Presto code to avoid the reference to internal shaded pinot class/objects. - * E.g. protobuf, helix. - * - */ -public class Utils { - private Utils() { - } - - public static ByteString toByteString(byte[] bytes) { - return ByteString.copyFrom(bytes); - } - - public static InstanceConfig toInstanceConfig(String instanceId) { - return InstanceConfig.toInstanceConfig(instanceId); - } -} diff --git a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/plugin/metrics/NoopPinotMetricFactory.java b/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/plugin/metrics/NoopPinotMetricFactory.java deleted file mode 100644 index b7899139057..00000000000 --- a/pinot-connectors/prestodb-pinot-dependencies/presto-pinot-driver/src/main/java/org/apache/pinot/connector/presto/plugin/metrics/NoopPinotMetricFactory.java +++ /dev/null @@ -1,292 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.connector.presto.plugin.metrics; - -import com.google.common.collect.ImmutableMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import org.apache.pinot.spi.annotations.metrics.MetricsFactory; -import org.apache.pinot.spi.annotations.metrics.PinotMetricsFactory; -import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotCounter; -import org.apache.pinot.spi.metrics.PinotGauge; -import org.apache.pinot.spi.metrics.PinotHistogram; -import org.apache.pinot.spi.metrics.PinotJmxReporter; -import org.apache.pinot.spi.metrics.PinotMeter; -import org.apache.pinot.spi.metrics.PinotMetric; -import org.apache.pinot.spi.metrics.PinotMetricName; -import org.apache.pinot.spi.metrics.PinotMetricsRegistry; -import org.apache.pinot.spi.metrics.PinotMetricsRegistryListener; -import org.apache.pinot.spi.metrics.PinotTimer; - - -/** - * This package name has to match the regex pattern: ".*\\.plugin\\.metrics\\..*" - */ -@MetricsFactory -public class NoopPinotMetricFactory implements PinotMetricsFactory { - private PinotMetricsRegistry _pinotMetricsRegistry; - - @Override - public void init(PinotConfiguration pinotConfiguration) { - } - - @Override - public PinotMetricsRegistry getPinotMetricsRegistry() { - if (_pinotMetricsRegistry == null) { - _pinotMetricsRegistry = new NoopPinotMetricsRegistry(); - } - return _pinotMetricsRegistry; - } - - @Override - public PinotMetricName makePinotMetricName(Class aClass, String s) { - return new NoopPinotMetricName(); - } - - @Override - public PinotGauge makePinotGauge(Function function) { - return new NoopPinotGauge(); - } - - @Override - public PinotJmxReporter makePinotJmxReporter(PinotMetricsRegistry pinotMetricsRegistry) { - return new NoopPinotJmxReporter(); - } - - @Override - public String getMetricsFactoryName() { - return "noop"; - } - - public static class NoopPinotMetricsRegistry implements PinotMetricsRegistry { - @Override - public void removeMetric(PinotMetricName pinotMetricName) { - } - - @Override - public PinotGauge newGauge(PinotMetricName pinotMetricName, PinotGauge pinotGauge) { - return new NoopPinotGauge(); - } - - @Override - public PinotMeter newMeter(PinotMetricName pinotMetricName, String s, TimeUnit timeUnit) { - return new NoopPinotMeter(); - } - - @Override - public PinotCounter newCounter(PinotMetricName pinotMetricName) { - return new NoopPinotCounter(); - } - - @Override - public PinotTimer newTimer(PinotMetricName pinotMetricName, TimeUnit timeUnit, TimeUnit timeUnit1) { - return new NoopPinotTimer(); - } - - @Override - public PinotHistogram newHistogram(PinotMetricName pinotMetricName, boolean b) { - return new NoopPinotHistogram(); - } - - @Override - public Map allMetrics() { - return ImmutableMap.of(); - } - - @Override - public void addListener(PinotMetricsRegistryListener pinotMetricsRegistryListener) { - } - - @Override - public Object getMetricsRegistry() { - return this; - } - - @Override - public void shutdown() { - } - } - - private static class NoopPinotJmxReporter implements PinotJmxReporter { - @Override - public void start() { - } - } - - private static class NoopPinotMeter implements PinotMeter { - @Override - public void mark() { - } - - @Override - public void mark(long l) { - } - - @Override - public Object getMetered() { - return null; - } - - @Override - public TimeUnit rateUnit() { - return null; - } - - @Override - public String eventType() { - return null; - } - - @Override - public long count() { - return 0; - } - - @Override - public double fifteenMinuteRate() { - return 0; - } - - @Override - public double fiveMinuteRate() { - return 0; - } - - @Override - public double meanRate() { - return 0; - } - - @Override - public double oneMinuteRate() { - return 0; - } - - @Override - public Object getMetric() { - return null; - } - } - - private static class NoopPinotMetricName implements PinotMetricName { - @Override - public Object getMetricName() { - return null; - } - } - - private static class NoopPinotGauge implements PinotGauge { - @Override - public Object getGauge() { - return null; - } - - @Override - public T value() { - return null; - } - - @Override - public Object getMetric() { - return null; - } - } - - private static class NoopPinotCounter implements PinotCounter { - @Override - public Object getCounter() { - return null; - } - - @Override - public Object getMetric() { - return null; - } - } - - private static class NoopPinotTimer implements PinotTimer { - @Override - public Object getMetered() { - return null; - } - - @Override - public TimeUnit rateUnit() { - return null; - } - - @Override - public String eventType() { - return null; - } - - @Override - public long count() { - return 0; - } - - @Override - public double fifteenMinuteRate() { - return 0; - } - - @Override - public double fiveMinuteRate() { - return 0; - } - - @Override - public double meanRate() { - return 0; - } - - @Override - public double oneMinuteRate() { - return 0; - } - - @Override - public Object getMetric() { - return null; - } - - @Override - public void update(long duration, TimeUnit unit) { - } - - @Override - public Object getTimer() { - return null; - } - } - - private static class NoopPinotHistogram implements PinotHistogram { - @Override - public Object getHistogram() { - return null; - } - - @Override - public Object getMetric() { - return null; - } - } -} diff --git a/pinot-controller/pom.xml b/pinot-controller/pom.xml index d1acd2f61b4..e374a87c325 100644 --- a/pinot-controller/pom.xml +++ b/pinot-controller/pom.xml @@ -33,7 +33,7 @@ https://pinot.apache.org/ ${basedir}/.. - build + build-ci @@ -157,12 +157,6 @@ org.apache.helix helix-core - - - io.netty - netty - - com.101tec @@ -186,6 +180,10 @@ org.quartz-scheduler quartz + + org.apache.pinot + pinot-query-planner + @@ -235,13 +233,13 @@ - npm install + npm ci npm generate-resources - install + ci @@ -277,4 +275,13 @@ + + + + bin-dist + + build + + + diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java b/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java index bc2284032c1..e32a419ecfd 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java @@ -60,6 +60,7 @@ import org.apache.pinot.common.minion.InMemoryTaskManagerStatusCache; import org.apache.pinot.common.minion.TaskGeneratorMostRecentRunInfo; import org.apache.pinot.common.minion.TaskManagerStatusCache; +import org.apache.pinot.common.utils.PinotAppConfigs; import org.apache.pinot.common.utils.ServiceStartableUtils; import org.apache.pinot.common.utils.ServiceStatus; import org.apache.pinot.common.utils.TlsUtils; @@ -97,6 +98,7 @@ import org.apache.pinot.core.periodictask.PeriodicTask; import org.apache.pinot.core.periodictask.PeriodicTaskScheduler; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; +import org.apache.pinot.core.segment.processing.lifecycle.PinotSegmentLifecycleEventListenerManager; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.util.ListenerConfigUtil; import org.apache.pinot.spi.crypt.PinotCrypterFactory; @@ -179,6 +181,7 @@ public void init(PinotConfiguration pinotConfiguration) ServiceStartableUtils.applyClusterConfig(_config, _helixZkURL, _helixClusterName, ServiceRole.CONTROLLER); setupHelixSystemProperties(); + HelixHelper.setMinNumCharsInISToTurnOnCompression(_config.getMinNumCharsInISToTurnOnCompression()); _listenerConfigs = ListenerConfigUtil.buildControllerConfigs(_config); _controllerMode = _config.getControllerMode(); inferHostnameIfNeeded(_config); @@ -242,19 +245,19 @@ private void setupHelixSystemProperties() { // NOTE: Helix will disconnect the manager and disable the instance if it detects flapping (too frequent disconnect // from ZooKeeper). Setting flapping time window to a small value can avoid this from happening. Helix ignores the // non-positive value, so set the default value as 1. - System.setProperty(SystemPropertyKeys.FLAPPING_TIME_WINDOW, _config - .getProperty(CommonConstants.Helix.CONFIG_OF_CONTROLLER_FLAPPING_TIME_WINDOW_MS, + System.setProperty(SystemPropertyKeys.FLAPPING_TIME_WINDOW, + _config.getProperty(CommonConstants.Helix.CONFIG_OF_CONTROLLER_FLAPPING_TIME_WINDOW_MS, CommonConstants.Helix.DEFAULT_FLAPPING_TIME_WINDOW_MS)); } private void setupHelixClusterConstraints() { - String maxStateTransitions = _config - .getProperty(CommonConstants.Helix.CONFIG_OF_HELIX_INSTANCE_MAX_STATE_TRANSITIONS, + String maxStateTransitions = + _config.getProperty(CommonConstants.Helix.CONFIG_OF_HELIX_INSTANCE_MAX_STATE_TRANSITIONS, CommonConstants.Helix.DEFAULT_HELIX_INSTANCE_MAX_STATE_TRANSITIONS); Map constraintAttributes = new HashMap<>(); constraintAttributes.put(ClusterConstraints.ConstraintAttribute.INSTANCE, ".*"); - constraintAttributes - .put(ClusterConstraints.ConstraintAttribute.MESSAGE_TYPE, Message.MessageType.STATE_TRANSITION.name()); + constraintAttributes.put(ClusterConstraints.ConstraintAttribute.MESSAGE_TYPE, + Message.MessageType.STATE_TRANSITION.name()); ConstraintItem constraintItem = new ConstraintItem(constraintAttributes, maxStateTransitions); _helixControllerManager.getClusterManagmentTool() @@ -319,6 +322,7 @@ public PinotConfiguration getConfig() { @Override public void start() { LOGGER.info("Starting Pinot controller in mode: {}. (Version: {})", _controllerMode.name(), PinotVersion.VERSION); + LOGGER.info("Controller configs: {}", new PinotAppConfigs(getConfig()).toJSONString()); Utils.logVersions(); // Set up controller metrics @@ -367,8 +371,8 @@ private void setUpHelixController() { private void setUpPinotController() { // install default SSL context if necessary (even if not force-enabled everywhere) TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(_config, ControllerConf.CONTROLLER_TLS_PREFIX); - if (StringUtils.isNotBlank(tlsDefaults.getKeyStorePath()) || StringUtils - .isNotBlank(tlsDefaults.getTrustStorePath())) { + if (StringUtils.isNotBlank(tlsDefaults.getKeyStorePath()) || StringUtils.isNotBlank( + tlsDefaults.getTrustStorePath())) { LOGGER.info("Installing default SSL context for any client requests"); TlsUtils.installDefaultSSLSocketFactory(tlsDefaults); } @@ -388,8 +392,9 @@ private void setUpPinotController() { _config.getProperty(CommonConstants.Controller.CONFIG_OF_CONTROLLER_QUERY_REWRITER_CLASS_NAMES)); LOGGER.info("Initializing Helix participant manager"); - _helixParticipantManager = HelixManagerFactory - .getZKHelixManager(_helixClusterName, _helixParticipantInstanceId, InstanceType.PARTICIPANT, _helixZkURL); + _helixParticipantManager = + HelixManagerFactory.getZKHelixManager(_helixClusterName, _helixParticipantInstanceId, InstanceType.PARTICIPANT, + _helixZkURL); // LeadControllerManager needs to be initialized before registering as Helix participant. LOGGER.info("Initializing lead controller manager"); @@ -407,6 +412,9 @@ private void setUpPinotController() { LOGGER.info("Starting Pinot Helix resource manager and connecting to Zookeeper"); _helixResourceManager.start(_helixParticipantManager); + // Initialize segment lifecycle event listeners + PinotSegmentLifecycleEventListenerManager.getInstance().init(_helixParticipantManager); + LOGGER.info("Starting task resource manager"); _helixTaskResourceManager = new PinotHelixTaskResourceManager(_helixResourceManager, new TaskDriver(_helixParticipantManager), @@ -459,7 +467,7 @@ private void setUpPinotController() { } final MetadataEventNotifierFactory metadataEventNotifierFactory = - MetadataEventNotifierFactory.loadFactory(_config.subset(METADATA_EVENT_NOTIFIER_PREFIX)); + MetadataEventNotifierFactory.loadFactory(_config.subset(METADATA_EVENT_NOTIFIER_PREFIX), _helixResourceManager); LOGGER.info("Controller download url base: {}", _config.generateVipUrl()); LOGGER.info("Injecting configuration and resource managers to the API context"); @@ -665,7 +673,7 @@ protected List setupControllerPeriodicTasks() { _taskManagerStatusCache = getTaskManagerStatusCache(); _taskManager = new PinotTaskManager(_helixTaskResourceManager, _helixResourceManager, _leadControllerManager, _config, - _controllerMetrics, _taskManagerStatusCache); + _controllerMetrics, _taskManagerStatusCache, _executorService, _connectionManager); periodicTasks.add(_taskManager); _retentionManager = new RetentionManager(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics); @@ -685,8 +693,9 @@ protected List setupControllerPeriodicTasks() { new SegmentStatusChecker(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics, _executorService); periodicTasks.add(_segmentStatusChecker); - _realtimeConsumerMonitor = new RealtimeConsumerMonitor(_config, _helixResourceManager, _leadControllerManager, - _controllerMetrics, _executorService); + _realtimeConsumerMonitor = + new RealtimeConsumerMonitor(_config, _helixResourceManager, _leadControllerManager, _controllerMetrics, + _executorService); periodicTasks.add(_realtimeConsumerMonitor); _segmentRelocator = new SegmentRelocator(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics, _executorService, _connectionManager); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java index bf6e9adc81a..e519f2b8f8a 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java @@ -27,6 +27,7 @@ import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang3.StringUtils; import org.apache.helix.controller.rebalancer.strategy.AutoRebalanceStrategy; import org.apache.pinot.common.protocols.SegmentCompletionProtocol; import org.apache.pinot.spi.env.PinotConfiguration; @@ -60,6 +61,7 @@ public class ControllerConf extends PinotConfiguration { public static final String ZK_STR = "controller.zk.str"; // boolean: Update the statemodel on boot? public static final String UPDATE_SEGMENT_STATE_MODEL = "controller.update_segment_state_model"; + public static final String MIN_NUM_CHARS_IN_IS_TO_TURN_ON_COMPRESSION = "controller.min_is_size_for_compression"; public static final String HELIX_CLUSTER_NAME = "controller.helix.cluster.name"; public static final String CLUSTER_TENANT_ISOLATION_ENABLE = "cluster.tenant.isolation.enable"; public static final String CONSOLE_WEBAPP_ROOT_PATH = "controller.query.console"; @@ -75,6 +77,9 @@ public class ControllerConf extends PinotConfiguration { public static final String CONTROLLER_RESOURCE_PACKAGES = "controller.restlet.api.resource.packages"; public static final String DEFAULT_CONTROLLER_RESOURCE_PACKAGES = "org.apache.pinot.controller.api.resources"; + // Consider tierConfigs when assigning new offline segment + public static final String CONTROLLER_ENABLE_TIERED_SEGMENT_ASSIGNMENT = "controller.segment.enableTieredAssignment"; + public enum ControllerMode { DUAL, PINOT_ONLY, HELIX_ONLY } @@ -208,6 +213,8 @@ public static class ControllerPeriodicTasksConf { // Default value is false. public static final String ENABLE_DEEP_STORE_RETRY_UPLOAD_LLC_SEGMENT = "controller.realtime.segment.deepStoreUploadRetryEnabled"; + public static final String DEEP_STORE_RETRY_UPLOAD_TIMEOUT_MS = + "controller.realtime.segment.deepStoreUploadRetry.timeoutMs"; public static final int MIN_INITIAL_DELAY_IN_SECONDS = 120; public static final int MAX_INITIAL_DELAY_IN_SECONDS = 300; @@ -235,7 +242,6 @@ private static long getRandomInitialDelayInSeconds() { private static final int DEFAULT_SEGMENT_LEVEL_VALIDATION_INTERVAL_IN_SECONDS = 24 * 60 * 60; private static final int DEFAULT_SEGMENT_RELOCATOR_FREQUENCY_IN_SECONDS = 60 * 60; - private static final int DEFAULT_SEGMENT_TIER_ASSIGNER_FREQUENCY_IN_SECONDS = -1; // Disabled // Realtime Consumer Monitor private static final String RT_CONSUMER_MONITOR_FREQUENCY_PERIOD = @@ -257,6 +263,7 @@ private static long getRandomInitialDelayInSeconds() { public static final String ACCESS_CONTROL_FACTORY_CLASS = "controller.admin.access.control.factory.class"; public static final String ACCESS_CONTROL_USERNAME = "access.control.init.username"; public static final String ACCESS_CONTROL_PASSWORD = "access.control.init.password"; + public static final String LINEAGE_MANAGER_CLASS = "controller.lineage.manager.class"; // Amount of the time the segment can take from the beginning of upload to the end of upload. Used when parallel push // protection is enabled. If the upload does not finish within the timeout, next upload can override the previous one. private static final String SEGMENT_UPLOAD_TIMEOUT_IN_MILLIS = "controller.segment.upload.timeoutInMillis"; @@ -284,7 +291,10 @@ private static long getRandomInitialDelayInSeconds() { "org.apache.pinot.controller.api.access.AllowAllAccessFactory"; private static final String DEFAULT_ACCESS_CONTROL_USERNAME = "admin"; private static final String DEFAULT_ACCESS_CONTROL_PASSWORD = "admin"; + private static final String DEFAULT_LINEAGE_MANAGER = + "org.apache.pinot.controller.helix.core.lineage.DefaultLineageManager"; private static final long DEFAULT_SEGMENT_UPLOAD_TIMEOUT_IN_MILLIS = 600_000L; // 10 minutes + private static final int DEFAULT_MIN_NUM_CHARS_IN_IS_TO_TURN_ON_COMPRESSION = -1; private static final int DEFAULT_REALTIME_SEGMENT_METADATA_COMMIT_NUMLOCKS = 64; private static final boolean DEFAULT_ENABLE_STORAGE_QUOTA_CHECK = true; private static final boolean DEFAULT_ENABLE_BATCH_MESSAGE_MODE = false; @@ -374,7 +384,7 @@ public void setControllerPort(String port) { } public void setDataDir(String dataDir) { - setProperty(DATA_DIR, dataDir); + setProperty(DATA_DIR, StringUtils.removeEnd(dataDir, "/")); } public void setRealtimeSegmentCommitTimeoutSeconds(int timeoutSec) { @@ -385,6 +395,10 @@ public void setUpdateSegmentStateModel(String updateStateModel) { setProperty(UPDATE_SEGMENT_STATE_MODEL, updateStateModel); } + public void setMinISSizeForCompression(int minSize) { + setProperty(MIN_NUM_CHARS_IN_IS_TO_TURN_ON_COMPRESSION, minSize); + } + public void setZkStr(String zkStr) { setProperty(CommonConstants.Helix.CONFIG_OF_ZOOKEEPR_SERVER, zkStr); } @@ -667,6 +681,14 @@ public void setSegmentRelocatorFrequencyInSeconds(int segmentRelocatorFrequencyI Integer.toString(segmentRelocatorFrequencyInSeconds)); } + public boolean tieredSegmentAssignmentEnabled() { + return getProperty(CONTROLLER_ENABLE_TIERED_SEGMENT_ASSIGNMENT, false); + } + + public void setTieredSegmentAssignmentEnabled(boolean enabled) { + setProperty(CONTROLLER_ENABLE_TIERED_SEGMENT_ASSIGNMENT, enabled); + } + public boolean tenantIsolationEnabled() { return getProperty(CLUSTER_TENANT_ISOLATION_ENABLE, true); } @@ -832,10 +854,22 @@ public void setAccessControlFactoryClass(String accessControlFactoryClass) { setProperty(ACCESS_CONTROL_FACTORY_CLASS, accessControlFactoryClass); } + public String getLineageManagerClass() { + return getProperty(LINEAGE_MANAGER_CLASS, DEFAULT_LINEAGE_MANAGER); + } + + public void setLineageManagerClass(String lineageModifierClass) { + setProperty(LINEAGE_MANAGER_CLASS, lineageModifierClass); + } + public long getSegmentUploadTimeoutInMillis() { return getProperty(SEGMENT_UPLOAD_TIMEOUT_IN_MILLIS, DEFAULT_SEGMENT_UPLOAD_TIMEOUT_IN_MILLIS); } + public int getMinNumCharsInISToTurnOnCompression() { + return getProperty(MIN_NUM_CHARS_IN_IS_TO_TURN_ON_COMPRESSION, DEFAULT_MIN_NUM_CHARS_IN_IS_TO_TURN_ON_COMPRESSION); + } + public void setSegmentUploadTimeoutInMillis(long segmentUploadTimeoutInMillis) { setProperty(SEGMENT_UPLOAD_TIMEOUT_IN_MILLIS, segmentUploadTimeoutInMillis); } @@ -887,6 +921,10 @@ public boolean isDeepStoreRetryUploadLLCSegmentEnabled() { return getProperty(ControllerPeriodicTasksConf.ENABLE_DEEP_STORE_RETRY_UPLOAD_LLC_SEGMENT, false); } + public int getDeepStoreRetryUploadTimeoutMs() { + return getProperty(ControllerPeriodicTasksConf.DEEP_STORE_RETRY_UPLOAD_TIMEOUT_MS, -1); + } + public long getPinotTaskManagerInitialDelaySeconds() { return getPeriodicTaskInitialDelayInSeconds(); } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java index d1a77d67890..0673d892c8b 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java @@ -122,7 +122,7 @@ private void setupSwagger() { _httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, "/api/"); _httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, "/help/"); - URL swaggerDistLocation = loader.getResource("META-INF/resources/webjars/swagger-ui/3.23.11/"); + URL swaggerDistLocation = loader.getResource(CommonConstants.CONFIG_OF_SWAGGER_RESOURCES_PATH); CLStaticHttpHandler swaggerDist = new CLStaticHttpHandler(new URLClassLoader(new URL[]{swaggerDistLocation})); _httpServer.getServerConfiguration().addHttpHandler(swaggerDist, "/swaggerui-dist/"); } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/DefaultMetadataEventNotifierFactory.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/DefaultMetadataEventNotifierFactory.java index aa148f47cee..b303c8cd23f 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/DefaultMetadataEventNotifierFactory.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/DefaultMetadataEventNotifierFactory.java @@ -18,15 +18,18 @@ */ package org.apache.pinot.controller.api.events; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.spi.env.PinotConfiguration; public class DefaultMetadataEventNotifierFactory extends MetadataEventNotifierFactory { - public MetadataEventNotifier create() { - return new DefaultMetadataEventNotifier(); + @Override + public void init(PinotConfiguration configuration, PinotHelixResourceManager pinotHelixResourceManager) { } - public void init(PinotConfiguration configuration) { + @Override + public MetadataEventNotifier create() { + return new DefaultMetadataEventNotifier(); } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/MetadataEventNotifierFactory.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/MetadataEventNotifierFactory.java index 20ffc2a02b2..68626f9d2da 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/MetadataEventNotifierFactory.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/events/MetadataEventNotifierFactory.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.controller.api.events; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.spi.env.PinotConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,21 +28,21 @@ public abstract class MetadataEventNotifierFactory { public static final Logger LOGGER = LoggerFactory.getLogger(MetadataEventNotifierFactory.class); public static final String METADATA_EVENT_CLASS_CONFIG = "factory.class"; - public abstract void init(PinotConfiguration configuration); + public abstract void init(PinotConfiguration configuration, PinotHelixResourceManager pinotHelixResourceManager); public abstract MetadataEventNotifier create(); - public static MetadataEventNotifierFactory loadFactory(PinotConfiguration configuration) { + public static MetadataEventNotifierFactory loadFactory(PinotConfiguration configuration, PinotHelixResourceManager + helixResourceManager) { MetadataEventNotifierFactory metadataEventNotifierFactory; String metadataEventNotifierClassName = configuration.getProperty(METADATA_EVENT_CLASS_CONFIG, DefaultMetadataEventNotifierFactory.class.getName()); - try { LOGGER.info("Instantiating metadata event notifier factory class {}", metadataEventNotifierClassName); metadataEventNotifierFactory = (MetadataEventNotifierFactory) Class.forName(metadataEventNotifierClassName).newInstance(); - metadataEventNotifierFactory.init(configuration); + metadataEventNotifierFactory.init(configuration, helixResourceManager); return metadataEventNotifierFactory; } catch (Exception e) { throw new RuntimeException(e); diff --git a/pinot-plugins/pinot-batch-ingestion/v0_deprecated/pinot-ingestion-common/src/main/java/org/apache/pinot/ingestion/utils/PushLocation.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/InstanceTagUpdateRequest.java similarity index 54% rename from pinot-plugins/pinot-batch-ingestion/v0_deprecated/pinot-ingestion-common/src/main/java/org/apache/pinot/ingestion/utils/PushLocation.java rename to pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/InstanceTagUpdateRequest.java index 307b3e56f39..c320387a4df 100644 --- a/pinot-plugins/pinot-batch-ingestion/v0_deprecated/pinot-ingestion-common/src/main/java/org/apache/pinot/ingestion/utils/PushLocation.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/InstanceTagUpdateRequest.java @@ -16,40 +16,35 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.ingestion.utils; +package org.apache.pinot.controller.api.resources; -import java.io.Serializable; -import java.util.ArrayList; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import java.util.List; -public class PushLocation implements Serializable { - private final String _host; - private final int _port; +@ApiModel +public class InstanceTagUpdateRequest { + @JsonProperty("instanceName") + @ApiModelProperty(example = "Server_a.b.com_20000") + private String _instanceName; + @JsonProperty("newTags") + private List _newTags; - public PushLocation(String host, int port) { - _host = host; - _port = port; + public String getInstanceName() { + return _instanceName; } - public static List getPushLocations(String[] hosts, int port) { - List pushLocations = new ArrayList<>(hosts.length); - for (String host : hosts) { - pushLocations.add(new PushLocation(host, port)); - } - return pushLocations; + public void setInstanceName(String instanceName) { + _instanceName = instanceName; } - public String getHost() { - return _host; + public List getNewTags() { + return _newTags; } - public int getPort() { - return _port; - } - - @Override - public String toString() { - return _host + ":" + _port; + public void setNewTags(List newTags) { + _newTags = newTags; } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/LLCSegmentCompletionHandlers.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/LLCSegmentCompletionHandlers.java index c11bed4008b..b287f07dfc4 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/LLCSegmentCompletionHandlers.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/LLCSegmentCompletionHandlers.java @@ -448,8 +448,10 @@ private static File extractSegmentFromFormToLocalTempFile(FormDataMultiPart form "Invalid multi-part for segment: %s", segmentName); FormDataBodyPart bodyPart = map.values().iterator().next().get(0); - File localTempFile = new File(ControllerFilePathProvider.getInstance().getFileUploadTempDir(), - getTempSegmentFileName(segmentName)); + File localTempFile = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile( + ControllerFilePathProvider.getInstance().getFileUploadTempDir(), getTempSegmentFileName(segmentName), + "Invalid segment name: %s", segmentName); + try (InputStream inputStream = bodyPart.getValueAs(InputStream.class)) { Files.copy(inputStream, localTempFile.toPath()); } catch (Exception e) { @@ -468,8 +470,10 @@ private static File extractSegmentFromFormToLocalTempFile(FormDataMultiPart form */ private static SegmentMetadataImpl extractMetadataFromLocalSegmentFile(File segmentFile) throws Exception { - File tempIndexDir = - new File(ControllerFilePathProvider.getInstance().getUntarredFileTempDir(), segmentFile.getName()); + File tempIndexDir = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile( + ControllerFilePathProvider.getInstance().getUntarredFileTempDir(), segmentFile.getName(), + "Invalid segment file: %s", segmentFile); + try { FileUtils.forceMkdir(tempIndexDir); @@ -494,8 +498,10 @@ private static SegmentMetadataImpl extractMetadataFromLocalSegmentFile(File segm */ private static SegmentMetadataImpl extractSegmentMetadataFromForm(FormDataMultiPart form, String segmentName) throws IOException { - File tempIndexDir = new File(ControllerFilePathProvider.getInstance().getUntarredFileTempDir(), - getTempSegmentFileName(segmentName)); + File tempIndexDir = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile( + ControllerFilePathProvider.getInstance().getUntarredFileTempDir(), getTempSegmentFileName(segmentName), + "Invalid segment name: %s", segmentName); + try { FileUtils.forceMkdir(tempIndexDir); @@ -532,8 +538,10 @@ private static void extractFileFromForm(FormDataMultiPart form, String fileName, */ private static SegmentMetadataImpl extractMetadataFromSegmentFileURI(URI segmentFileURI, String segmentName) throws Exception { - File localTempFile = - new File(ControllerFilePathProvider.getInstance().getFileUploadTempDir(), getTempSegmentFileName(segmentName)); + File localTempFile = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile( + ControllerFilePathProvider.getInstance().getFileUploadTempDir(), getTempSegmentFileName(segmentName), + "Invalid segment name: %s", segmentName); + try { SegmentFetcherFactory.fetchSegmentToLocal(segmentFileURI, localTempFile); return extractMetadataFromLocalSegmentFile(localTempFile); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/OperationValidationResponse.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/OperationValidationResponse.java new file mode 100644 index 00000000000..6b27e38e63d --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/OperationValidationResponse.java @@ -0,0 +1,107 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.resources; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.List; + + +public class OperationValidationResponse { + private String _instanceName; + private boolean _safe; + private final List _issues = new ArrayList<>(); + + @JsonProperty("instanceName") + public String getInstanceName() { + return _instanceName; + } + + public OperationValidationResponse setInstanceName(String instanceName) { + _instanceName = instanceName; + return this; + } + + @JsonProperty("isSafe") + public boolean isSafe() { + return _safe; + } + + public OperationValidationResponse setSafe(boolean safe) { + _safe = safe; + return this; + } + + @JsonProperty("issues") + public List getIssues() { + return _issues; + } + + public OperationValidationResponse putIssue(ErrorCode code, String... args) { + _issues.add(new ErrorWrapper(code, args)); + return this; + } + + public OperationValidationResponse putAllIssues(List issues) { + _issues.addAll(issues); + return this; + } + + public String getIssueMessage(int index) { + return _issues.get(index).getMessage(); + } + + public static class ErrorWrapper { + @JsonProperty("code") + ErrorCode _code; + @JsonProperty("message") + String _message; + + public ErrorWrapper() { + } + + public ErrorWrapper(ErrorCode code, String... args) { + _code = code; + _message = String.format(code._description, args); + } + + public ErrorCode getCode() { + return _code; + } + + public String getMessage() { + return _message; + } + } + + public enum ErrorCode { + IS_ALIVE("Instance %s is still live"), + CONTAINS_RESOURCE("Instance %s exists in ideal state for %s"), + MINIMUM_INSTANCE_UNSATISFIED( + "Tenant '%s' will not satisfy minimum '%s' requirement if tag '%s' is removed from %s instance '%s'."), + ALREADY_DEFICIENT_TENANT("Tenant '%s' is low on '%s' instances by %s even after allocating instance %s"), + UNRECOGNISED_TAG_TYPE("The tag '%s' does not follow the suffix convention of either broker or server"); + + public final String _description; + + ErrorCode(String description) { + _description = description; + } + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerAuthResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerAuthResource.java index 77b77479ca6..52e518cfed1 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerAuthResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerAuthResource.java @@ -28,6 +28,7 @@ import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; import javax.inject.Inject; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -72,7 +73,7 @@ public class PinotControllerAuthResource { @ApiResponse(code = 500, message = "Verification error") }) public boolean verify(@ApiParam(value = "Table name without type") @QueryParam("tableName") String tableName, - @ApiParam(value = "API access type") @QueryParam("accessType") AccessType accessType, + @ApiParam(value = "API access type") @DefaultValue("READ") @QueryParam("accessType") AccessType accessType, @ApiParam(value = "Endpoint URL") @QueryParam("endpointUrl") String endpointUrl) { AccessControl accessControl = _accessControlFactory.create(); return accessControl.hasAccess(tableName, accessType, _httpHeaders, endpointUrl); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java index ae9c5db3218..d57746b4620 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java @@ -36,6 +36,7 @@ import java.util.Set; import javax.inject.Inject; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -145,7 +146,8 @@ public Response downloadLogFile( @Path("/loggers/instances") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Collect log files from all the instances") - public Map> getLogFilesFromAllInstances() { + public Map> getLogFilesFromAllInstances( + @HeaderParam(HttpHeaders.AUTHORIZATION) String authorization) { if (_logFileServer == null || _logFileServer instanceof DummyLogFileServer) { throw new WebApplicationException("Root directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); } @@ -154,7 +156,7 @@ public Map> getLogFilesFromAllInstances() { onlineInstanceList.forEach( instance -> { try { - instancesToLogFilesMap.put(instance, getLogFilesFromInstance(instance)); + instancesToLogFilesMap.put(instance, getLogFilesFromInstance(authorization, instance)); } catch (Exception e) { // Skip the instance for any exception. } @@ -167,10 +169,15 @@ public Map> getLogFilesFromAllInstances() { @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Collect log files from a given instance") public Set getLogFilesFromInstance( + @HeaderParam(HttpHeaders.AUTHORIZATION) String authorization, @ApiParam(value = "Instance Name", required = true) @PathParam("instanceName") String instanceName) { try { URI uri = new URI(getInstanceBaseUri(instanceName) + "/loggers/files"); - SimpleHttpResponse simpleHttpResponse = _fileUploadDownloadClient.getHttpClient().sendGetRequest(uri); + Map headers = new HashMap<>(); + if (authorization != null) { + headers.put(HttpHeaders.AUTHORIZATION, authorization); + } + SimpleHttpResponse simpleHttpResponse = _fileUploadDownloadClient.getHttpClient().sendGetRequest(uri, headers); if (simpleHttpResponse.getStatusCode() >= 400) { throw new WebApplicationException("Failed to fetch logs from instance name: " + instanceName, Response.Status.fromStatusCode(simpleHttpResponse.getStatusCode())); @@ -189,6 +196,7 @@ public Set getLogFilesFromInstance( @Authenticate(AccessType.DELETE) @ApiOperation(value = "Download a log file from a given instance") public Response downloadLogFileFromInstance( + @HeaderParam(HttpHeaders.AUTHORIZATION) String authorization, @ApiParam(value = "Instance Name", required = true) @PathParam("instanceName") String instanceName, @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath, @Context Map headers) { @@ -201,6 +209,9 @@ public Response downloadLogFileFromInstance( requestBuilder.addHeader(header.getKey(), header.getValue()); } } + if (authorization != null) { + requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, authorization); + } CloseableHttpResponse httpResponse = _fileUploadDownloadClient.getHttpClient().execute(requestBuilder.build()); if (httpResponse.getStatusLine().getStatusCode() >= 400) { throw new WebApplicationException(IOUtils.toString(httpResponse.getEntity().getContent(), "UTF-8"), diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java index dfdaefafaa7..aee7df4e8a6 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java @@ -26,6 +26,7 @@ import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -45,6 +46,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.collections.CollectionUtils; import org.apache.helix.model.InstanceConfig; import org.apache.pinot.common.assignment.InstanceAssignmentConfigUtils; import org.apache.pinot.common.assignment.InstancePartitions; @@ -57,6 +59,7 @@ import org.apache.pinot.controller.helix.core.assignment.instance.InstanceAssignmentDriver; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.TableNameBuilder; @@ -80,39 +83,57 @@ public class PinotInstanceAssignmentRestletResource { @Produces(MediaType.APPLICATION_JSON) @Path("/tables/{tableName}/instancePartitions") @ApiOperation(value = "Get the instance partitions") - public Map getInstancePartitions( + public Map getInstancePartitions( @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, - @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable - InstancePartitionsType instancePartitionsType) { - Map instancePartitionsMap = new TreeMap<>(); + @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED|tier name") @QueryParam("type") @Nullable String type) { + Map instancePartitionsMap = new TreeMap<>(); String rawTableName = TableNameBuilder.extractRawTableName(tableName); TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); if (tableType != TableType.REALTIME) { - if (instancePartitionsType == InstancePartitionsType.OFFLINE || instancePartitionsType == null) { - InstancePartitions offlineInstancePartitions = InstancePartitionsUtils - .fetchInstancePartitions(_resourceManager.getPropertyStore(), + if (InstancePartitionsType.OFFLINE.toString().equals(type) || type == null) { + InstancePartitions offlineInstancePartitions = + InstancePartitionsUtils.fetchInstancePartitions(_resourceManager.getPropertyStore(), InstancePartitionsType.OFFLINE.getInstancePartitionsName(rawTableName)); if (offlineInstancePartitions != null) { - instancePartitionsMap.put(InstancePartitionsType.OFFLINE, offlineInstancePartitions); + instancePartitionsMap.put(InstancePartitionsType.OFFLINE.toString(), offlineInstancePartitions); } } } if (tableType != TableType.OFFLINE) { - if (instancePartitionsType == InstancePartitionsType.CONSUMING || instancePartitionsType == null) { - InstancePartitions consumingInstancePartitions = InstancePartitionsUtils - .fetchInstancePartitions(_resourceManager.getPropertyStore(), + if (InstancePartitionsType.CONSUMING.toString().equals(type) || type == null) { + InstancePartitions consumingInstancePartitions = + InstancePartitionsUtils.fetchInstancePartitions(_resourceManager.getPropertyStore(), InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName)); if (consumingInstancePartitions != null) { - instancePartitionsMap.put(InstancePartitionsType.CONSUMING, consumingInstancePartitions); + instancePartitionsMap.put(InstancePartitionsType.CONSUMING.toString(), consumingInstancePartitions); } } - if (instancePartitionsType == InstancePartitionsType.COMPLETED || instancePartitionsType == null) { - InstancePartitions completedInstancePartitions = InstancePartitionsUtils - .fetchInstancePartitions(_resourceManager.getPropertyStore(), + if (InstancePartitionsType.COMPLETED.toString().equals(type) || type == null) { + InstancePartitions completedInstancePartitions = + InstancePartitionsUtils.fetchInstancePartitions(_resourceManager.getPropertyStore(), InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName)); if (completedInstancePartitions != null) { - instancePartitionsMap.put(InstancePartitionsType.COMPLETED, completedInstancePartitions); + instancePartitionsMap.put(InstancePartitionsType.COMPLETED.toString(), completedInstancePartitions); + } + } + } + + List tableConfigs = Arrays.asList(_resourceManager.getRealtimeTableConfig(tableName), + _resourceManager.getOfflineTableConfig(tableName)); + + for (TableConfig tableConfig : tableConfigs) { + if (tableConfig != null && CollectionUtils.isNotEmpty(tableConfig.getTierConfigsList())) { + for (TierConfig tierConfig : tableConfig.getTierConfigsList()) { + if (type == null || type.equals(tierConfig.getName())) { + InstancePartitions instancePartitions = + InstancePartitionsUtils.fetchInstancePartitions(_resourceManager.getPropertyStore(), + InstancePartitionsUtils.getInstancePartitionsNameForTier(tableConfig.getTableName(), + tierConfig.getName())); + if (instancePartitions != null) { + instancePartitionsMap.put(tierConfig.getName(), instancePartitions); + } + } } } } @@ -130,22 +151,20 @@ public Map getInstancePartitions( @Path("/tables/{tableName}/assignInstances") @Authenticate(AccessType.CREATE) @ApiOperation(value = "Assign server instances to a table") - public Map assignInstances( + public Map assignInstances( @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, - @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable - InstancePartitionsType instancePartitionsType, + @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED|tier name") @QueryParam("type") @Nullable String type, @ApiParam(value = "Whether to do dry-run") @DefaultValue("false") @QueryParam("dryRun") boolean dryRun) { - Map instancePartitionsMap = new TreeMap<>(); + Map instancePartitionsMap = new TreeMap<>(); List instanceConfigs = _resourceManager.getAllHelixInstanceConfigs(); TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); - if (tableType != TableType.REALTIME && (instancePartitionsType == InstancePartitionsType.OFFLINE - || instancePartitionsType == null)) { + if (tableType != TableType.REALTIME && (InstancePartitionsType.OFFLINE.toString().equals(type) || type == null)) { TableConfig offlineTableConfig = _resourceManager.getOfflineTableConfig(tableName); if (offlineTableConfig != null) { try { - if (InstanceAssignmentConfigUtils - .allowInstanceAssignment(offlineTableConfig, InstancePartitionsType.OFFLINE)) { + if (InstanceAssignmentConfigUtils.allowInstanceAssignment(offlineTableConfig, + InstancePartitionsType.OFFLINE)) { assignInstancesForInstancePartitionsType(instancePartitionsMap, offlineTableConfig, instanceConfigs, InstancePartitionsType.OFFLINE); } @@ -158,20 +177,20 @@ public Map assignInstances( } } } - if (tableType != TableType.OFFLINE && instancePartitionsType != InstancePartitionsType.OFFLINE) { + if (tableType != TableType.OFFLINE && !InstancePartitionsType.OFFLINE.toString().equals(type)) { TableConfig realtimeTableConfig = _resourceManager.getRealtimeTableConfig(tableName); if (realtimeTableConfig != null) { try { - if (instancePartitionsType == InstancePartitionsType.CONSUMING || instancePartitionsType == null) { - if (InstanceAssignmentConfigUtils - .allowInstanceAssignment(realtimeTableConfig, InstancePartitionsType.CONSUMING)) { + if (InstancePartitionsType.CONSUMING.toString().equals(type) || type == null) { + if (InstanceAssignmentConfigUtils.allowInstanceAssignment(realtimeTableConfig, + InstancePartitionsType.CONSUMING)) { assignInstancesForInstancePartitionsType(instancePartitionsMap, realtimeTableConfig, instanceConfigs, InstancePartitionsType.CONSUMING); } } - if (instancePartitionsType == InstancePartitionsType.COMPLETED || instancePartitionsType == null) { - if (InstanceAssignmentConfigUtils - .allowInstanceAssignment(realtimeTableConfig, InstancePartitionsType.COMPLETED)) { + if (InstancePartitionsType.COMPLETED.toString().equals(type) || type == null) { + if (InstanceAssignmentConfigUtils.allowInstanceAssignment(realtimeTableConfig, + InstancePartitionsType.COMPLETED)) { assignInstancesForInstancePartitionsType(instancePartitionsMap, realtimeTableConfig, instanceConfigs, InstancePartitionsType.COMPLETED); } @@ -186,6 +205,16 @@ public Map assignInstances( } } + TableConfig realtimeTableConfig = _resourceManager.getRealtimeTableConfig(tableName); + if (realtimeTableConfig != null) { + assignInstancesForTier(instancePartitionsMap, realtimeTableConfig, instanceConfigs, type); + } + + TableConfig offlineTableConfig = _resourceManager.getOfflineTableConfig(tableName); + if (offlineTableConfig != null) { + assignInstancesForTier(instancePartitionsMap, offlineTableConfig, instanceConfigs, type); + } + if (instancePartitionsMap.isEmpty()) { throw new ControllerApplicationException(LOGGER, "Failed to find the instance assignment config", Response.Status.NOT_FOUND); @@ -207,22 +236,43 @@ public Map assignInstances( * @param instanceConfigs list of instance configs * @param instancePartitionsType type of instancePartitions */ - private void assignInstancesForInstancePartitionsType( - Map instancePartitionsMap, TableConfig tableConfig, - List instanceConfigs, InstancePartitionsType instancePartitionsType) { + private void assignInstancesForInstancePartitionsType(Map instancePartitionsMap, + TableConfig tableConfig, List instanceConfigs, InstancePartitionsType instancePartitionsType) { String tableNameWithType = tableConfig.getTableName(); if (TableConfigUtils.hasPreConfiguredInstancePartitions(tableConfig, instancePartitionsType)) { String rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType); - instancePartitionsMap.put(instancePartitionsType, InstancePartitionsUtils.fetchInstancePartitionsWithRename( - _resourceManager.getPropertyStore(), tableConfig.getInstancePartitionsMap().get(instancePartitionsType), - instancePartitionsType.getInstancePartitionsName(rawTableName))); + instancePartitionsMap.put(instancePartitionsType.toString(), + InstancePartitionsUtils.fetchInstancePartitionsWithRename(_resourceManager.getPropertyStore(), + tableConfig.getInstancePartitionsMap().get(instancePartitionsType), + instancePartitionsType.getInstancePartitionsName(rawTableName))); return; } InstancePartitions existingInstancePartitions = InstancePartitionsUtils.fetchInstancePartitions(_resourceManager.getHelixZkManager().getHelixPropertyStore(), InstancePartitionsUtils.getInstancePartitionsName(tableNameWithType, instancePartitionsType.toString())); - instancePartitionsMap.put(instancePartitionsType, new InstanceAssignmentDriver(tableConfig) - .assignInstances(instancePartitionsType, instanceConfigs, existingInstancePartitions)); + instancePartitionsMap.put(instancePartitionsType.toString(), + new InstanceAssignmentDriver(tableConfig).assignInstances(instancePartitionsType, instanceConfigs, + existingInstancePartitions)); + } + + private void assignInstancesForTier(Map instancePartitionsMap, TableConfig tableConfig, + List instanceConfigs, String tierName) { + if (CollectionUtils.isNotEmpty(tableConfig.getTierConfigsList()) + && tableConfig.getInstanceAssignmentConfigMap() != null) { + for (TierConfig tierConfig : tableConfig.getTierConfigsList()) { + if ((tierConfig.getName().equals(tierName) || tierName == null) + && tableConfig.getInstanceAssignmentConfigMap().get(tierConfig.getName()) != null) { + InstancePartitions existingInstancePartitions = InstancePartitionsUtils.fetchInstancePartitions( + _resourceManager.getHelixZkManager().getHelixPropertyStore(), + InstancePartitionsUtils.getInstancePartitionsNameForTier(tableConfig.getTableName(), + tierConfig.getName())); + + instancePartitionsMap.put(tierConfig.getName(), + new InstanceAssignmentDriver(tableConfig).assignInstances(tierConfig.getName(), instanceConfigs, + existingInstancePartitions, tableConfig.getInstanceAssignmentConfigMap().get(tierConfig.getName()))); + } + } + } } private void persistInstancePartitionsHelper(InstancePartitions instancePartitions) { @@ -240,7 +290,7 @@ private void persistInstancePartitionsHelper(InstancePartitions instancePartitio @Path("/tables/{tableName}/instancePartitions") @Authenticate(AccessType.UPDATE) @ApiOperation(value = "Create/update the instance partitions") - public Map setInstancePartitions( + public Map setInstancePartitions( @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, String instancePartitionsStr) { InstancePartitions instancePartitions; try { @@ -256,17 +306,32 @@ public Map setInstancePartitions( if (tableType != TableType.REALTIME) { if (InstancePartitionsType.OFFLINE.getInstancePartitionsName(rawTableName).equals(instancePartitionsName)) { persistInstancePartitionsHelper(instancePartitions); - return Collections.singletonMap(InstancePartitionsType.OFFLINE, instancePartitions); + return Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), instancePartitions); } } if (tableType != TableType.OFFLINE) { if (InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName).equals(instancePartitionsName)) { persistInstancePartitionsHelper(instancePartitions); - return Collections.singletonMap(InstancePartitionsType.CONSUMING, instancePartitions); + return Collections.singletonMap(InstancePartitionsType.CONSUMING.toString(), instancePartitions); } if (InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName).equals(instancePartitionsName)) { persistInstancePartitionsHelper(instancePartitions); - return Collections.singletonMap(InstancePartitionsType.COMPLETED, instancePartitions); + return Collections.singletonMap(InstancePartitionsType.COMPLETED.toString(), instancePartitions); + } + } + + List tableConfigs = Arrays.asList(_resourceManager.getRealtimeTableConfig(tableName), + _resourceManager.getOfflineTableConfig(tableName)); + + for (TableConfig tableConfig : tableConfigs) { + if (tableConfig != null && CollectionUtils.isNotEmpty(tableConfig.getTierConfigsList())) { + for (TierConfig tierConfig : tableConfig.getTierConfigsList()) { + if (InstancePartitionsUtils.getInstancePartitionsNameForTier(tableConfig.getTableName(), tierConfig.getName()) + .equals(instancePartitionsName)) { + persistInstancePartitionsHelper(instancePartitions); + return Collections.singletonMap(tierConfig.getName(), instancePartitions); + } + } } } @@ -281,22 +346,39 @@ public Map setInstancePartitions( @ApiOperation(value = "Remove the instance partitions") public SuccessResponse removeInstancePartitions( @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, - @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable - InstancePartitionsType instancePartitionsType) { + @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED|tier name") @QueryParam("type") @Nullable + String instancePartitionsType) { String rawTableName = TableNameBuilder.extractRawTableName(tableName); TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); - if (tableType != TableType.REALTIME && (instancePartitionsType == InstancePartitionsType.OFFLINE + if (tableType != TableType.REALTIME && (InstancePartitionsType.OFFLINE.toString().equals(instancePartitionsType) || instancePartitionsType == null)) { removeInstancePartitionsHelper(InstancePartitionsType.OFFLINE.getInstancePartitionsName(rawTableName)); } if (tableType != TableType.OFFLINE) { - if (instancePartitionsType == InstancePartitionsType.CONSUMING || instancePartitionsType == null) { + if (InstancePartitionsType.CONSUMING.toString().equals(instancePartitionsType) + || instancePartitionsType == null) { removeInstancePartitionsHelper(InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName)); } - if (instancePartitionsType == InstancePartitionsType.COMPLETED || instancePartitionsType == null) { + if (InstancePartitionsType.COMPLETED.toString().equals(instancePartitionsType) + || instancePartitionsType == null) { removeInstancePartitionsHelper(InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName)); } } + + List tableConfigs = Arrays.asList(_resourceManager.getRealtimeTableConfig(tableName), + _resourceManager.getOfflineTableConfig(tableName)); + + for (TableConfig tableConfig : tableConfigs) { + if (tableConfig != null && CollectionUtils.isNotEmpty(tableConfig.getTierConfigsList())) { + for (TierConfig tierConfig : tableConfig.getTierConfigsList()) { + if (instancePartitionsType == null || instancePartitionsType.equals(tierConfig.getName())) { + removeInstancePartitionsHelper( + InstancePartitionsUtils.getInstancePartitionsNameForTier(tableConfig.getTableName(), + tierConfig.getName())); + } + } + } + } return new SuccessResponse("Instance partitions removed"); } @@ -315,16 +397,16 @@ private void removeInstancePartitionsHelper(String instancePartitionsName) { @Path("/tables/{tableName}/replaceInstance") @Authenticate(AccessType.CREATE) @ApiOperation(value = "Replace an instance in the instance partitions") - public Map replaceInstance( + public Map replaceInstance( @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, - @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable - InstancePartitionsType instancePartitionsType, + @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED|tier name") @QueryParam("type") @Nullable + String type, @ApiParam(value = "Old instance to be replaced", required = true) @QueryParam("oldInstanceId") String oldInstanceId, @ApiParam(value = "New instance to replace with", required = true) @QueryParam("newInstanceId") String newInstanceId) { - Map instancePartitionsMap = - getInstancePartitions(tableName, instancePartitionsType); + Map instancePartitionsMap = + getInstancePartitions(tableName, type); Iterator iterator = instancePartitionsMap.values().iterator(); while (iterator.hasNext()) { InstancePartitions instancePartitions = iterator.next(); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java index cff4a470a4b..06ca9ec92f6 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Sets; import io.swagger.annotations.Api; import io.swagger.annotations.ApiKeyAuthDefinition; import io.swagger.annotations.ApiOperation; @@ -29,8 +30,13 @@ import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.ws.rs.ClientErrorException; import javax.ws.rs.Consumes; @@ -48,6 +54,7 @@ import javax.ws.rs.core.Response; import org.apache.helix.model.InstanceConfig; import org.apache.pinot.common.utils.config.InstanceUtils; +import org.apache.pinot.common.utils.config.TagNameUtils; import org.apache.pinot.controller.api.access.AccessType; import org.apache.pinot.controller.api.access.Authenticate; import org.apache.pinot.controller.api.exception.ControllerApplicationException; @@ -215,6 +222,48 @@ public SuccessResponse addInstance( } } + @PUT + @Path("/instances/{instanceName}/state") + @Authenticate(AccessType.UPDATE) + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Enable/disable an instance", notes = "Enable/disable an instance") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 400, message = "Bad Request"), + @ApiResponse(code = 404, message = "Instance not found"), + @ApiResponse(code = 500, message = "Internal error") + }) + public SuccessResponse toggleInstanceState( + @ApiParam(value = "Instance name", required = true, example = "Server_a.b.com_20000 | Broker_my.broker.com_30000") + @PathParam("instanceName") String instanceName, + @ApiParam(value = "enable|disable", required = true) @QueryParam("state") String state) { + if (!_pinotHelixResourceManager.instanceExists(instanceName)) { + throw new ControllerApplicationException(LOGGER, "Instance '" + instanceName + "' does not exist", + Response.Status.NOT_FOUND); + } + + if (StateType.ENABLE.name().equalsIgnoreCase(state)) { + PinotResourceManagerResponse response = _pinotHelixResourceManager.enableInstance(instanceName); + if (!response.isSuccessful()) { + throw new ControllerApplicationException(LOGGER, + "Failed to enable instance '" + instanceName + "': " + response.getMessage(), + Response.Status.INTERNAL_SERVER_ERROR); + } + } else if (StateType.DISABLE.name().equalsIgnoreCase(state)) { + PinotResourceManagerResponse response = _pinotHelixResourceManager.disableInstance(instanceName); + if (!response.isSuccessful()) { + throw new ControllerApplicationException(LOGGER, + "Failed to disable instance '" + instanceName + "': " + response.getMessage(), + Response.Status.INTERNAL_SERVER_ERROR); + } + } else { + throw new ControllerApplicationException(LOGGER, "Unknown state '" + state + "'", Response.Status.BAD_REQUEST); + } + return new SuccessResponse("Request to " + state + " instance '" + instanceName + "' is successful"); + } + + @Deprecated @POST @Path("/instances/{instanceName}/state") @Authenticate(AccessType.UPDATE) @@ -228,7 +277,7 @@ public SuccessResponse addInstance( @ApiResponse(code = 409, message = "Instance cannot be dropped"), @ApiResponse(code = 500, message = "Internal error") }) - public SuccessResponse toggleInstanceState( + public SuccessResponse toggleInstanceStateDeprecated( @ApiParam(value = "Instance name", required = true, example = "Server_a.b.com_20000 | Broker_my.broker.com_30000") @PathParam("instanceName") String instanceName, String state) { if (!_pinotHelixResourceManager.instanceExists(instanceName)) { @@ -390,4 +439,172 @@ public SuccessResponse updateBrokerResource( Response.Status.INTERNAL_SERVER_ERROR, e); } } + + @GET + @Path("/instances/dropInstance/validate") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Check if it's safe to drop the given instances. If not list all the reasons why its not safe.") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal error") + }) + public List instanceDropSafetyCheck( + @ApiParam(value = "Instance names", required = true, + example = "Broker_my.broker.com_30000") + @QueryParam("instanceNames") List instanceNames) { + LOGGER.info("Performing safety check on drop operation request received for instances: {}", instanceNames); + try { + return instanceNames.stream() + .map(instance -> _pinotHelixResourceManager.instanceDropSafetyCheck(instance)) + .collect(Collectors.toList()); + } catch (ClientErrorException e) { + throw new ControllerApplicationException(LOGGER, e.getMessage(), e.getResponse().getStatus()); + } catch (Exception e) { + throw new ControllerApplicationException(LOGGER, "Failed to check the safety for instance drop operation.", + Response.Status.INTERNAL_SERVER_ERROR, e); + } + } + + /** + * Endpoint to validate the safety of instance tag update requests. + * This is to ensure that any instance tag update operation that user wants to perform is safe and does not create any + * side effect on the cluster and disturb the cluster consistency. + * This operation does not perform any changes to the cluster, but surfaces the possible issues which might occur upon + * applying the intended changes. + * @param requests list if instance tag update requests + * @return list of {@link OperationValidationResponse} which denotes the validity of each request along with listing + * the issues if any. + */ + @POST + @Path("/instances/updateTags/validate") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Check if it's safe to update the tags of the given instances. If not list all the reasons.") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal error") + }) + public List instanceTagUpdateSafetyCheck(List requests) { + LOGGER.info("Performing safety check on tag update request received for instances: {}", + requests.stream().map(InstanceTagUpdateRequest::getInstanceName).collect(Collectors.toList())); + Map tagMinServerMap = _pinotHelixResourceManager.minimumInstancesRequiredForTags(); + Map tagToInstanceCountMap = getUpdatedTagToInstanceCountMap(requests); + Map tagDeficiency = computeTagDeficiency(tagToInstanceCountMap, tagMinServerMap); + + Map> responseMap = new HashMap<>(requests.size()); + List tenantIssues = new ArrayList<>(); + requests.forEach(request -> responseMap.put(request.getInstanceName(), new ArrayList<>())); + for (InstanceTagUpdateRequest request : requests) { + String name = request.getInstanceName(); + Set oldTags; + try { + oldTags = new HashSet<>(_pinotHelixResourceManager.getTagsForInstance(name)); + } catch (NullPointerException exception) { + throw new ControllerApplicationException(LOGGER, + String.format("Instance %s is not a valid instance name.", name), Response.Status.PRECONDITION_FAILED); + } + Set newTags = new HashSet<>(request.getNewTags()); + // tags removed from instance + for (String tag : Sets.difference(oldTags, newTags)) { + Integer deficiency = tagDeficiency.get(tag); + if (deficiency != null && deficiency > 0) { + String tenant = TagNameUtils.getTenantFromTag(tag); + String tagType = getInstanceTypeFromTag(tag); + responseMap.get(name).add(new OperationValidationResponse.ErrorWrapper( + OperationValidationResponse.ErrorCode.MINIMUM_INSTANCE_UNSATISFIED, tenant, tagType, tag, tagType, name)); + tagDeficiency.put(tag, deficiency - 1); + } + } + // newly added tags to instance + for (String tag : newTags) { + String tagType = getInstanceTypeFromTag(tag); + if (tagType == null && (name.startsWith(CommonConstants.Helix.PREFIX_OF_BROKER_INSTANCE) + || name.startsWith(CommonConstants.Helix.PREFIX_OF_SERVER_INSTANCE))) { + responseMap.get(name).add(new OperationValidationResponse.ErrorWrapper( + OperationValidationResponse.ErrorCode.UNRECOGNISED_TAG_TYPE, tag)); + continue; + } + Integer deficiency = tagDeficiency.get(tag); + if (deficiency != null && deficiency > 0) { + tenantIssues.add(new OperationValidationResponse.ErrorWrapper( + OperationValidationResponse.ErrorCode.ALREADY_DEFICIENT_TENANT, TagNameUtils.getTenantFromTag(tag), + tagType, deficiency.toString(), name)); + } + } + } + + // consolidate all the issues based on instances + List response = new ArrayList<>(requests.size()); + responseMap.forEach((instance, issueList) -> response.add(issueList.isEmpty() + ? new OperationValidationResponse().setInstanceName(instance).setSafe(true) + : new OperationValidationResponse().putAllIssues(issueList).setInstanceName(instance).setSafe(false))); + // separate entry to group all the deficient tenant issues as it's not related to any instance + if (!tenantIssues.isEmpty()) { + response.add(new OperationValidationResponse().putAllIssues(tenantIssues).setSafe(false)); + } + return response; + } + + private String getInstanceTypeFromTag(String tag) { + if (TagNameUtils.isServerTag(tag)) { + return "server"; + } else if (TagNameUtils.isBrokerTag(tag)) { + return "broker"; + } else { + return null; + } + } + + /** + * Compute the number of deficient instances for each tag. + * The utility accepts two maps + * - map of tags and count of their intended tagged instances + * - map of tags and their minimum number of instance requirements + * And then compares these two maps to return a map of tags and the number of their deficient instances. + * + * @param tagToInstanceCountMap tags and count of their intended tagged instances + * @param tagToMinInstanceCountMap tags and their minimum number of instance requirements + * @return tags and the number of their deficient instances + */ + private Map computeTagDeficiency(Map tagToInstanceCountMap, + Map tagToMinInstanceCountMap) { + Map tagDeficiency = new HashMap<>(); + Map tagToInstanceCountMapCopy = new HashMap<>(tagToInstanceCountMap); + // compute deficiency for each of the minimum instance requirement entry + tagToMinInstanceCountMap.forEach((tag, minInstances) -> { + Integer updatedInstances = tagToInstanceCountMapCopy.remove(tag); + // if tag is not present in the provided map its considered as if tag is not assigned to any instance + // hence deficiency = minimum instance requirement. + tagDeficiency.put(tag, minInstances - (updatedInstances != null ? updatedInstances : 0)); + }); + // tags for which minimum instance requirement is not specified are assumed to have no deficiency (deficiency = 0) + tagToInstanceCountMapCopy.forEach((tag, updatedInstances) -> tagDeficiency.put(tag, 0)); + return tagDeficiency; + } + + /** + * Utility to fetch the existing tags and count of their respective tagged instances and then apply the changes based + * on the provided list of {@link InstanceTagUpdateRequest} to get the updated map of tags and count of their + * respective tagged instances + * @param requests list of {@link InstanceTagUpdateRequest} + * @return map of tags and updated count of their respective tagged instances + */ + private Map getUpdatedTagToInstanceCountMap(List requests) { + Map updatedTagInstanceMap = new HashMap<>(); + Set visitedInstances = new HashSet<>(); + // build the map of tags and their respective instance counts from the given tag update request list + requests.forEach(instance -> { + instance.getNewTags().forEach(tag -> + updatedTagInstanceMap.put(tag, updatedTagInstanceMap.getOrDefault(tag, 0) + 1)); + visitedInstances.add(instance.getInstanceName()); + }); + // add the instance counts to tags for the rest of the instances apart from the ones mentioned in requests + _pinotHelixResourceManager.getAllInstances().forEach(instance -> { + if (!visitedInstances.contains(instance)) { + _pinotHelixResourceManager.getTagsForInstance(instance).forEach(tag -> + updatedTagInstanceMap.put(tag, updatedTagInstanceMap.getOrDefault(tag, 0) + 1)); + visitedInstances.add(instance); + } + }); + return updatedTagInstanceMap; + } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java index 6dd546c778d..3b3abb79811 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java @@ -30,9 +30,11 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; @@ -42,15 +44,18 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import org.apache.calcite.jdbc.CalciteSchemaBuilder; import org.apache.calcite.sql.SqlNode; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.model.InstanceConfig; import org.apache.pinot.common.Utils; import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.api.access.AccessControl; @@ -59,6 +64,11 @@ import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.core.auth.ManualAuthorization; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; +import org.apache.pinot.query.QueryEnvironment; +import org.apache.pinot.query.catalog.PinotCatalog; +import org.apache.pinot.query.type.TypeFactory; +import org.apache.pinot.query.type.TypeSystem; +import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.apache.pinot.spi.utils.JsonUtils; @@ -66,6 +76,7 @@ import org.apache.pinot.sql.parsers.CalciteSqlCompiler; import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.apache.pinot.sql.parsers.PinotSqlType; +import org.apache.pinot.sql.parsers.SqlCompilationException; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -105,6 +116,12 @@ public String handlePostSql(String requestJsonStr, @Context HttpHeaders httpHead } LOGGER.debug("Trace: {}, Running query: {}", traceEnabled, sqlQuery); return executeSqlQuery(httpHeaders, sqlQuery, traceEnabled, queryOptions, "/sql"); + } catch (ProcessingException pe) { + LOGGER.error("Caught exception while processing post request {}", pe.getMessage()); + return pe.getMessage(); + } catch (WebApplicationException wae) { + LOGGER.error("Caught exception while processing post request", wae); + throw wae; } catch (Exception e) { LOGGER.error("Caught exception while processing post request", e); return QueryException.getException(QueryException.INTERNAL_ERROR, e).toString(); @@ -118,6 +135,12 @@ public String handleGetSql(@QueryParam("sql") String sqlQuery, @QueryParam("trac try { LOGGER.debug("Trace: {}, Running query: {}", traceEnabled, sqlQuery); return executeSqlQuery(httpHeaders, sqlQuery, traceEnabled, queryOptions, "/sql"); + } catch (ProcessingException pe) { + LOGGER.error("Caught exception while processing get request {}", pe.getMessage()); + return pe.getMessage(); + } catch (WebApplicationException wae) { + LOGGER.error("Caught exception while processing get request", wae); + throw wae; } catch (Exception e) { LOGGER.error("Caught exception while processing get request", e); return QueryException.getException(QueryException.INTERNAL_ERROR, e).toString(); @@ -127,7 +150,12 @@ public String handleGetSql(@QueryParam("sql") String sqlQuery, @QueryParam("trac private String executeSqlQuery(@Context HttpHeaders httpHeaders, String sqlQuery, String traceEnabled, @Nullable String queryOptions, String endpointUrl) throws Exception { - SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(sqlQuery); + SqlNodeAndOptions sqlNodeAndOptions; + try { + sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(sqlQuery); + } catch (SqlCompilationException ex) { + throw QueryException.getException(QueryException.SQL_PARSING_ERROR, ex); + } Map options = sqlNodeAndOptions.getOptions(); if (queryOptions != null) { Map optionsFromString = RequestUtils.getOptionsFromString(queryOptions); @@ -138,7 +166,7 @@ private String executeSqlQuery(@Context HttpHeaders httpHeaders, String sqlQuery if (Boolean.parseBoolean(options.get(QueryOptionKey.USE_MULTISTAGE_ENGINE))) { if (_controllerConf.getProperty(CommonConstants.Helix.CONFIG_OF_MULTI_STAGE_ENGINE_ENABLED, CommonConstants.Helix.DEFAULT_MULTI_STAGE_ENGINE_ENABLED)) { - return getMultiStageQueryResponse(sqlQuery, queryOptions, httpHeaders, endpointUrl); + return getMultiStageQueryResponse(sqlQuery, queryOptions, httpHeaders, endpointUrl, traceEnabled); } else { throw new UnsupportedOperationException("V2 Multi-Stage query engine not enabled. " + "Please see https://docs.pinot.apache.org/ for instruction to enable V2 engine."); @@ -161,19 +189,44 @@ private String executeSqlQuery(@Context HttpHeaders httpHeaders, String sqlQuery } private String getMultiStageQueryResponse(String query, String queryOptions, HttpHeaders httpHeaders, - String endpointUrl) { + String endpointUrl, String traceEnabled) { // Validate data access // we don't have a cross table access control rule so only ADMIN can make request to multi-stage engine. AccessControl accessControl = _accessControlFactory.create(); if (!accessControl.hasAccess(null, AccessType.READ, httpHeaders, endpointUrl)) { - return QueryException.ACCESS_DENIED_ERROR.toString(); + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); + } + + QueryEnvironment queryEnvironment = new QueryEnvironment(new TypeFactory(new TypeSystem()), + CalciteSchemaBuilder.asRootSchema(new PinotCatalog(_pinotHelixResourceManager.getTableCache())), null, null); + List tableNames = queryEnvironment.getTableNamesForQuery(query); + String brokerTenant; + if (tableNames.size() != 0) { + List tableConfigList = getListTableConfigs(tableNames); + if (tableConfigList == null || tableConfigList.size() == 0) { + return QueryException.getException(QueryException.TABLE_DOES_NOT_EXIST_ERROR, + new Exception("Unable to find table in cluster, table does not exist")).toString(); + } + + // When routing a query, there should be at least one common broker tenant for the table. However, the server + // tenants can be completely disjoint. The leaf stages which access segments will be processed on the respective + // server tenants for each table. The intermediate stages can be processed in either or all of the server tenants + // belonging to the tables. + brokerTenant = getCommonBrokerTenant(tableConfigList); + if (brokerTenant == null) { + return QueryException.getException(QueryException.BROKER_REQUEST_SEND_ERROR, new Exception( + String.format("Unable to dispatch multistage query with multiple tables : %s " + "on different tenant", + tableNames))).toString(); + } + } else { + // TODO fail these queries going forward. Added this logic to take care of tautologies like BETWEEN 0 and -1. + List allBrokerList = new ArrayList<>(_pinotHelixResourceManager.getAllBrokerTenantNames()); + brokerTenant = allBrokerList.get(RANDOM.nextInt(allBrokerList.size())); + LOGGER.error("Unable to find table name from SQL {} thus dispatching to random broker.", query); } + List instanceIds = new ArrayList<>(_pinotHelixResourceManager.getAllInstancesForBrokerTenant(brokerTenant)); - // Get brokers, only DEFAULT tenant is supported for now. - // TODO: implement logic that only allows executing query where all accessed tables are within the same tenant. - List instanceIds = new ArrayList<>(_pinotHelixResourceManager.getAllInstancesForBrokerTenant( - TagNameUtils.DEFAULT_TENANT_NAME)); if (instanceIds.isEmpty()) { return QueryException.BROKER_RESOURCE_MISSING_ERROR.toString(); } @@ -185,7 +238,7 @@ private String getMultiStageQueryResponse(String query, String queryOptions, Htt // Send query to a random broker. String instanceId = instanceIds.get(RANDOM.nextInt(instanceIds.size())); - return sendRequestToBroker(query, instanceId, "false", queryOptions, httpHeaders); + return sendRequestToBroker(query, instanceId, traceEnabled, queryOptions, httpHeaders); } private String getQueryResponse(String query, @Nullable SqlNode sqlNode, String traceEnabled, String queryOptions, @@ -194,8 +247,8 @@ private String getQueryResponse(String query, @Nullable SqlNode sqlNode, String String tableName; try { String inputTableName = - sqlNode != null ? RequestUtils.getTableName(CalciteSqlParser.compileSqlNodeToPinotQuery(sqlNode)) - : CalciteSqlCompiler.compileToBrokerRequest(query).getQuerySource().getTableName(); + sqlNode != null ? RequestUtils.getTableNames(CalciteSqlParser.compileSqlNodeToPinotQuery(sqlNode)).iterator() + .next() : CalciteSqlCompiler.compileToBrokerRequest(query).getQuerySource().getTableName(); tableName = _pinotHelixResourceManager.getActualTableName(inputTableName); } catch (Exception e) { LOGGER.error("Caught exception while compiling query: {}", query, e); @@ -226,6 +279,37 @@ private String getQueryResponse(String query, @Nullable SqlNode sqlNode, String return sendRequestToBroker(query, instanceId, traceEnabled, queryOptions, httpHeaders); } + // given a list of tables, returns the list of tableConfigs + private List getListTableConfigs(List tableNames) { + List allTableConfigList = new ArrayList<>(); + for (String tableName : tableNames) { + List tableConfigList = new ArrayList<>(); + if (_pinotHelixResourceManager.hasRealtimeTable(tableName)) { + tableConfigList.add(Objects.requireNonNull(_pinotHelixResourceManager.getRealtimeTableConfig(tableName))); + } + if (_pinotHelixResourceManager.hasOfflineTable(tableName)) { + tableConfigList.add(Objects.requireNonNull(_pinotHelixResourceManager.getOfflineTableConfig(tableName))); + } + if (tableConfigList.size() == 0) { + return null; + } + allTableConfigList.addAll(tableConfigList); + } + return allTableConfigList; + } + + // return the brokerTenant if all table configs point to the same broker, else returns null + private String getCommonBrokerTenant(List tableConfigList) { + Set tableBrokers = new HashSet<>(); + for (TableConfig tableConfig : tableConfigList) { + tableBrokers.add(tableConfig.getTenantConfig().getBroker()); + } + if (tableBrokers.size() != 1) { + return null; + } + return (String) (tableBrokers.toArray()[0]); + } + private String sendRequestToBroker(String query, String instanceId, String traceEnabled, String queryOptions, HttpHeaders httpHeaders) { InstanceConfig instanceConfig = _pinotHelixResourceManager.getHelixInstanceConfig(instanceId); @@ -314,9 +398,13 @@ public String sendPostRaw(String urlStr, String requestStr, Map /*if (LOG.isInfoEnabled()){ LOGGER.info("The http response code is " + responseCode); }*/ - if (responseCode != HttpURLConnection.HTTP_OK) { - throw new IOException("Failed : HTTP error code : " + responseCode + ". Root Cause: " - + IOUtils.toString(conn.getErrorStream(), StandardCharsets.UTF_8)); + if (responseCode == HttpURLConnection.HTTP_FORBIDDEN) { + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); + } else if (responseCode != HttpURLConnection.HTTP_OK) { + InputStream errorStream = conn.getErrorStream(); + throw new IOException( + "Failed : HTTP error code : " + responseCode + ". Root Cause: " + (errorStream != null ? IOUtils.toString( + errorStream, StandardCharsets.UTF_8) : "Unknown")); } final byte[] bytes = drain(new BufferedInputStream(conn.getInputStream())); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotRealtimeTableResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotRealtimeTableResource.java index 1b16fbc1c9a..26bd7256dd0 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotRealtimeTableResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotRealtimeTableResource.java @@ -47,6 +47,7 @@ import javax.ws.rs.core.Response; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.helix.model.IdealState; +import org.apache.pinot.common.metadata.controllerjob.ControllerJobType; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.api.exception.ControllerApplicationException; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; @@ -165,7 +166,8 @@ public JsonNode getForceCommitJobStatus( @ApiParam(value = "Force commit job id", required = true) @PathParam("jobId") String forceCommitJobId) throws Exception { Map controllerJobZKMetadata = - _pinotHelixResourceManager.getControllerJobZKMetadata(forceCommitJobId); + _pinotHelixResourceManager.getControllerJobZKMetadata(forceCommitJobId, + ControllerJobType.FORCE_COMMIT); if (controllerJobZKMetadata == null) { throw new ControllerApplicationException(LOGGER, "Failed to find controller job id: " + forceCommitJobId, Response.Status.NOT_FOUND); @@ -227,8 +229,8 @@ public ConsumingSegmentInfoReader.ConsumingSegmentsInfoMap getConsumingSegmentsI String tableNameWithType = TableNameBuilder.forType(TableType.REALTIME).tableNameWithType(realtimeTableName); ConsumingSegmentInfoReader consumingSegmentInfoReader = new ConsumingSegmentInfoReader(_executor, _connectionManager, _pinotHelixResourceManager); - return consumingSegmentInfoReader - .getConsumingSegmentsInfo(tableNameWithType, _controllerConf.getServerAdminRequestTimeoutSeconds() * 1000); + return consumingSegmentInfoReader.getConsumingSegmentsInfo(tableNameWithType, + _controllerConf.getServerAdminRequestTimeoutSeconds() * 1000); } catch (Exception e) { throw new ControllerApplicationException(LOGGER, String.format("Failed to get consuming segments info for table %s. %s", realtimeTableName, e.getMessage()), diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java index 41042561401..20bec1ba609 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java @@ -68,8 +68,10 @@ import org.apache.pinot.core.auth.ManualAuthorization; import org.apache.pinot.segment.local.utils.SchemaUtils; import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.glassfish.grizzly.http.server.Request; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataMultiPart; @@ -330,6 +332,22 @@ public String validateSchema(String schemaJsonString, @Context HttpHeaders httpH } } + /** + * Gets the metadata on the valid {@link org.apache.pinot.spi.data.FieldSpec.DataType} for each + * {@link org.apache.pinot.spi.data.FieldSpec.FieldType} and the default null values for each combination + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/schemas/fieldSpec") + @ApiOperation(value = "Get fieldSpec metadata", notes = "Get fieldSpec metadata") + public String getFieldSpecMetadata() { + try { + return JsonUtils.objectToString(FieldSpec.FIELD_SPEC_METADATA); + } catch (Exception e) { + throw new ControllerApplicationException(LOGGER, e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR, e); + } + } + private void validateSchemaName(String schemaName) { if (StringUtils.isBlank(schemaName)) { throw new ControllerApplicationException(LOGGER, "Invalid schema. Reason: 'schemaName' should not be null", @@ -341,7 +359,8 @@ private void validateSchemaInternal(Schema schema) { validateSchemaName(schema.getSchemaName()); try { List tableConfigs = _pinotHelixResourceManager.getTableConfigsForSchema(schema.getSchemaName()); - SchemaUtils.validate(schema, tableConfigs); + boolean isIgnoreCase = _pinotHelixResourceManager.getTableCache().isIgnoreCase(); + SchemaUtils.validate(schema, tableConfigs, isIgnoreCase); } catch (Exception e) { throw new ControllerApplicationException(LOGGER, "Invalid schema: " + schema.getSchemaName() + ". Reason: " + e.getMessage(), Response.Status.BAD_REQUEST, e); @@ -396,7 +415,7 @@ private SuccessResponse updateSchema(String schemaName, Schema schema, boolean r } try { - _pinotHelixResourceManager.updateSchema(schema, reload); + _pinotHelixResourceManager.updateSchema(schema, reload, false); // Best effort notification. If controller fails at this point, no notification is given. LOGGER.info("Notifying metadata event for updating schema: {}", schemaName); _metadataEventNotifierFactory.create().notifyOnSchemaEvents(schema, SchemaEventType.UPDATE); @@ -450,20 +469,28 @@ private void deleteSchemaInternal(String schemaName) { } // If the schema is associated with a table, we should not delete it. - List tableNames = _pinotHelixResourceManager.getAllRealtimeTables(); - for (String tableName : tableNames) { - TableConfig config = _pinotHelixResourceManager.getRealtimeTableConfig(tableName); - String tableSchema = config.getValidationConfig().getSchemaName(); - - if (schemaName.equals(tableSchema)) { + // TODO: Check OFFLINE tables as well. There are 2 side effects: + // - Increases ZK read when there are lots of OFFLINE tables + // - Behavior change since we don't allow deleting schema for OFFLINE tables + List realtimeTables = _pinotHelixResourceManager.getAllRealtimeTables(); + for (String realtimeTableName : realtimeTables) { + if (schemaName.equals(TableNameBuilder.extractRawTableName(realtimeTableName))) { throw new ControllerApplicationException(LOGGER, - String.format("Cannot delete schema %s, as it is associated with table %s", schemaName, tableName), + String.format("Cannot delete schema %s, as it is associated with table %s", schemaName, realtimeTableName), Response.Status.CONFLICT); } + TableConfig tableConfig = _pinotHelixResourceManager.getTableConfig(realtimeTableName); + if (tableConfig != null) { + if (schemaName.equals(tableConfig.getValidationConfig().getSchemaName())) { + throw new ControllerApplicationException(LOGGER, + String.format("Cannot delete schema %s, as it is associated with table %s", schemaName, + realtimeTableName), Response.Status.CONFLICT); + } + } } LOGGER.info("Trying to delete schema {}", schemaName); - if (_pinotHelixResourceManager.deleteSchema(schema)) { + if (_pinotHelixResourceManager.deleteSchema(schemaName)) { LOGGER.info("Notifying metadata event for deleting schema: {}", schemaName); _metadataEventNotifierFactory.create().notifyOnSchemaEvents(schema, SchemaEventType.DELETE); LOGGER.info("Success: Deleted schema {}", schemaName); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java index c467c3f8166..37b3bd1fa05 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Executor; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; import javax.ws.rs.Consumes; @@ -103,6 +104,7 @@ *

  • "/segments/{tableName}/crc": get a map from segment to CRC of the segment (OFFLINE table only)
  • *
  • "/segments/{tableName}/{segmentName}/metadata: get the metadata for a segment
  • *
  • "/segments/{tableName}/metadata: get the metadata for all segments from the server
  • + *
  • "/segments/{tableName}/zkmetadata: get the zk metadata for all segments of a table
  • *
  • "/segments/{tableName}/{segmentName}/tiers": get storage tier for the segment in the table
  • *
  • "/segments/{tableName}/tiers": get storage tier for all segments in the table
  • * @@ -196,19 +198,19 @@ public List>> getSegments( @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr, @ApiParam(value = "Whether to exclude replaced segments in the response, which have been replaced" + " specified in the segment lineage entries and cannot be queried from the table") - @QueryParam("excludeReplacedSegments") String excludeReplacedSegments) { - List tableNamesWithType = ResourceUtils - .getExistingTableNamesWithType(_pinotHelixResourceManager, tableName, Constants.validateTableType(tableTypeStr), - LOGGER); + @QueryParam("excludeReplacedSegments") String excludeReplacedSegments, + @ApiParam(value = "Start timestamp (inclusive)") @QueryParam("startTimestamp") @DefaultValue("") + String startTimestampStr, + @ApiParam(value = "End timestamp (exclusive)") @QueryParam("endTimestamp") @DefaultValue("") + String endTimestampStr, + @ApiParam(value = "Whether to exclude the segments overlapping with the timestamps, false by default") + @QueryParam("excludeOverlapping") @DefaultValue("false") boolean excludeOverlapping) { boolean shouldExcludeReplacedSegments = Boolean.parseBoolean(excludeReplacedSegments); - List>> resultList = new ArrayList<>(tableNamesWithType.size()); - for (String tableNameWithType : tableNamesWithType) { - TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableNameWithType); - List segments = - _pinotHelixResourceManager.getSegmentsFor(tableNameWithType, shouldExcludeReplacedSegments); - resultList.add(Collections.singletonMap(tableType, segments)); - } - return resultList; + return selectSegments(tableName, tableTypeStr, shouldExcludeReplacedSegments, + startTimestampStr, endTimestampStr, excludeOverlapping) + .stream() + .map(pair -> Collections.singletonMap(pair.getKey(), pair.getValue())) + .collect(Collectors.toList()); } @GET @@ -516,7 +518,7 @@ private String getExistingTable(String tableName, String segmentName) { @Produces(MediaType.APPLICATION_JSON) @ApiOperation( value = "Resets a segment by first disabling it, waiting for external view to stabilize, and finally enabling " - + "it again", notes = "Resets a segment by disabling and then enabling the segment") + + "it again", notes = "Resets a segment by disabling and then enabling it") public SuccessResponse resetSegment( @ApiParam(value = "Name of the table with type", required = true) @PathParam("tableNameWithType") String tableNameWithType, @@ -542,7 +544,7 @@ public SuccessResponse resetSegment( } /** - * Resets all segments of the given table + * Resets all segments or segments with Error state of the given table * This API will take segments to OFFLINE state, wait for External View to stabilize, and then back to * ONLINE/CONSUMING state, * thus effective in resetting segments or consumers in error states. @@ -552,18 +554,21 @@ public SuccessResponse resetSegment( @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation( - value = "Resets all segments of the table, by first disabling them, waiting for external view to stabilize, and" - + " finally enabling the segments", notes = "Resets a segment by disabling and then enabling a segment") - public SuccessResponse resetAllSegments( + value = "Resets all segments (when errorSegmentsOnly = false) or segments with Error state (when " + + "errorSegmentsOnly = true) of the table, by first disabling them, waiting for external view to stabilize," + + " and finally enabling them", notes = "Resets segments by disabling and then enabling them") + public SuccessResponse resetSegments( @ApiParam(value = "Name of the table with type", required = true) @PathParam("tableNameWithType") String tableNameWithType, @ApiParam(value = "Name of the target instance to reset") @QueryParam("targetInstance") @Nullable - String targetInstance) { + String targetInstance, + @ApiParam(value = "Whether to reset only segments with error state") @QueryParam("errorSegmentsOnly") + @DefaultValue("false") boolean errorSegmentsOnly) { TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableNameWithType); try { Preconditions.checkState(tableType != null, "Must provide table name with type: %s", tableNameWithType); - _pinotHelixResourceManager.resetAllSegments(tableNameWithType, targetInstance); - return new SuccessResponse(String.format("Successfully reset all segments of table: %s", tableNameWithType)); + _pinotHelixResourceManager.resetSegments(tableNameWithType, targetInstance, errorSegmentsOnly); + return new SuccessResponse(String.format("Successfully reset segments of table: %s", tableNameWithType)); } catch (IllegalStateException e) { throw new ControllerApplicationException(LOGGER, String.format("Failed to reset segments in table: %s. %s", tableNameWithType, e.getMessage()), @@ -619,7 +624,8 @@ public SuccessResponse reloadSegmentDeprecated2( public ServerReloadControllerJobStatusResponse getReloadJobStatus( @ApiParam(value = "Reload job id", required = true) @PathParam("jobId") String reloadJobId) throws Exception { - Map controllerJobZKMetadata = _pinotHelixResourceManager.getControllerJobZKMetadata(reloadJobId); + Map controllerJobZKMetadata = _pinotHelixResourceManager. + getControllerJobZKMetadata(reloadJobId, ControllerJobType.RELOAD_SEGMENT); if (controllerJobZKMetadata == null) { throw new ControllerApplicationException(LOGGER, "Failed to find controller job id: " + reloadJobId, Status.NOT_FOUND); @@ -629,11 +635,10 @@ public ServerReloadControllerJobStatusResponse getReloadJobStatus( controllerJobZKMetadata.get(CommonConstants.ControllerJob.TABLE_NAME_WITH_TYPE); Map> serverToSegments; - String singleSegmentName = null; - if (controllerJobZKMetadata.get(CommonConstants.ControllerJob.JOB_TYPE) - .equals(ControllerJobType.RELOAD_SEGMENT.toString())) { + String singleSegmentName = + controllerJobZKMetadata.get(CommonConstants.ControllerJob.SEGMENT_RELOAD_JOB_SEGMENT_NAME); + if (singleSegmentName != null) { // No need to query servers where this segment is not supposed to be hosted - singleSegmentName = controllerJobZKMetadata.get(CommonConstants.ControllerJob.SEGMENT_RELOAD_JOB_SEGMENT_NAME); serverToSegments = new HashMap<>(); List segmentList = Arrays.asList(singleSegmentName); _pinotHelixResourceManager.getServers(tableNameWithType, singleSegmentName).forEach(server -> { @@ -860,6 +865,52 @@ public SuccessResponse deleteSegments( } } + @DELETE + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/segments/{tableName}/choose") + @Authenticate(AccessType.DELETE) + @ApiOperation(value = "Delete selected segments. An optional 'excludeReplacedSegments' parameter is used to get the" + + " list of segments which has not yet been replaced (determined by segment lineage entries) and can be queried" + + " from the table. The value is false by default.", + // TODO: more and more filters can be added later on, like excludeErrorSegments, excludeConsumingSegments, etc. + notes = "List all segments") + public SuccessResponse deleteSegmentsWithTimeWindow( + @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, + @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr, + @ApiParam(value = "Whether to ignore replaced segments for deletion, which have been replaced" + + " specified in the segment lineage entries and cannot be queried from the table, false by default") + @QueryParam("excludeReplacedSegments") @DefaultValue("false") boolean excludeReplacedSegments, + @ApiParam(value = "Start timestamp (inclusive) in milliseconds", required = true) @QueryParam("startTimestamp") + String startTimestampStr, + @ApiParam(value = "End timestamp (exclusive) in milliseconds", required = true) @QueryParam("endTimestamp") + String endTimestampStr, + @ApiParam(value = "Whether to ignore segments that are partially overlapping with the [start, end)" + + "for deletion, true by default") + @QueryParam("excludeOverlapping") @DefaultValue("true") boolean excludeOverlapping, + @ApiParam(value = "Retention period for the table segments (e.g. 12h, 3d); If not set, the retention period " + + "will default to the first config that's not null: the table config, then to cluster setting, then '7d'. " + + "Using 0d or -1d will instantly delete segments without retention") + @QueryParam("retention") String retentionPeriod) { + if (Strings.isNullOrEmpty(startTimestampStr) || Strings.isNullOrEmpty(endTimestampStr)) { + throw new ControllerApplicationException(LOGGER, "start and end timestamp must by non empty", Status.BAD_REQUEST); + } + + int numSegments = 0; + for (Pair> tableTypeSegments : selectSegments( + tableName, tableTypeStr, excludeReplacedSegments, startTimestampStr, endTimestampStr, excludeOverlapping)) { + TableType tableType = tableTypeSegments.getLeft(); + List segments = tableTypeSegments.getRight(); + numSegments += segments.size(); + if (segments.isEmpty()) { + continue; + } + String tableNameWithType = TableNameBuilder.forType(tableType).tableNameWithType(tableName); + deleteSegmentsInternal(tableNameWithType, segments, retentionPeriod); + } + return new SuccessResponse("Deleted " + numSegments + " segments from table: " + tableName); + } + private void deleteSegmentsInternal(String tableNameWithType, List segments, String retentionPeriod) { PinotResourceManagerResponse response = _pinotHelixResourceManager.deleteSegments(tableNameWithType, segments, retentionPeriod); @@ -898,6 +949,30 @@ public String getServerMetadata( return segmentsMetadata; } + @GET + @Path("segments/{tableName}/zkmetadata") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get the zookeeper metadata for all table segments", notes = "Get the zookeeper metadata for " + + "all table segments") + public Map> getZookeeperMetadata( + @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, + @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr) + throws JsonProcessingException { + LOGGER.info("Received a request to fetch zookeeper metadata for all segments for table {}", tableName); + TableType tableType = Constants.validateTableType(tableTypeStr); + + String tableNameWithType = + ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager, tableName, tableType, LOGGER).get(0); + Map> segmentToMetadataMap = new HashMap<>(); + List segmentZKMetadataList = + _pinotHelixResourceManager.getSegmentsZKMetadata(tableNameWithType); + + for (SegmentZKMetadata segmentZKMetadata : segmentZKMetadataList) { + segmentToMetadataMap.put(segmentZKMetadata.getSegmentName(), segmentZKMetadata.toMap()); + } + return segmentToMetadataMap; + } + @GET @Path("segments/{tableName}/tiers") @Produces(MediaType.APPLICATION_JSON) @@ -955,6 +1030,7 @@ private TableTierReader.TableTierDetails getTableTierInternal(String tableName, return tableTierDetails; } + @Deprecated @GET @Path("segments/{tableName}/select") @Produces(MediaType.APPLICATION_JSON) @@ -969,14 +1045,22 @@ private TableTierReader.TableTierDetails getTableTierInternal(String tableName, public List>> getSelectedSegments( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr, - @ApiParam(value = "Start timestamp (inclusive)") @QueryParam("startTimestamp") @DefaultValue("") + @ApiParam(value = "Start timestamp (inclusive) in milliseconds") @QueryParam("startTimestamp") @DefaultValue("") String startTimestampStr, - @ApiParam(value = "End timestamp (exclusive)") @QueryParam("endTimestamp") @DefaultValue("") + @ApiParam(value = "End timestamp (exclusive) in milliseconds") @QueryParam("endTimestamp") @DefaultValue("") String endTimestampStr, @ApiParam(value = "Whether to exclude the segments overlapping with the timestamps, false by default") @QueryParam("excludeOverlapping") @DefaultValue("false") boolean excludeOverlapping) { - long startTimestamp = Strings.isNullOrEmpty(startTimestampStr) ? Long.MIN_VALUE : Long.parseLong(startTimestampStr); - long endTimestamp = Strings.isNullOrEmpty(endTimestampStr) ? Long.MAX_VALUE : Long.parseLong(endTimestampStr); + long startTimestamp; + long endTimestamp; + try { + startTimestamp = Strings.isNullOrEmpty(startTimestampStr) ? Long.MIN_VALUE : Long.parseLong(startTimestampStr); + endTimestamp = Strings.isNullOrEmpty(endTimestampStr) ? Long.MAX_VALUE : Long.parseLong(endTimestampStr); + } catch (NumberFormatException e) { + throw new ControllerApplicationException(LOGGER, + "Failed to parse the start/end timestamp. Please make sure they are in 'millisSinceEpoch' format.", + Response.Status.BAD_REQUEST, e); + } Preconditions.checkArgument(startTimestamp < endTimestamp, "The value of startTimestamp should be smaller than the one of endTimestamp. Start timestamp: %d. End " + "timestamp: %d", @@ -988,8 +1072,9 @@ public List>> getSelectedSegments( List>> resultList = new ArrayList<>(tableNamesWithType.size()); for (String tableNameWithType : tableNamesWithType) { TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableNameWithType); - List segments = _pinotHelixResourceManager - .getSegmentsForTableWithTimestamps(tableNameWithType, startTimestamp, endTimestamp, excludeOverlapping); + List segments = + _pinotHelixResourceManager.getSegmentsFor(tableNameWithType, true, startTimestamp, endTimestamp, + excludeOverlapping); resultList.add(Collections.singletonMap(tableType, segments)); } return resultList; @@ -1072,4 +1157,34 @@ private SuccessResponse updateZKTimeIntervalInternal(String tableNameWithType) { } return new SuccessResponse("Successfully updated time interval zk metadata for table: " + tableNameWithType); } + + private List>> selectSegments( + String tableName, String tableTypeStr, boolean excludeReplacedSegments, String startTimestampStr, + String endTimestampStr, boolean excludeOverlapping) { + long startTimestamp; + long endTimestamp; + try { + startTimestamp = Strings.isNullOrEmpty(startTimestampStr) ? Long.MIN_VALUE : Long.parseLong(startTimestampStr); + endTimestamp = Strings.isNullOrEmpty(endTimestampStr) ? Long.MAX_VALUE : Long.parseLong(endTimestampStr); + } catch (NumberFormatException e) { + throw new ControllerApplicationException(LOGGER, + "Failed to parse the start/end timestamp. Please make sure they are in 'millisSinceEpoch' format.", + Response.Status.BAD_REQUEST, e); + } + Preconditions.checkArgument(startTimestamp < endTimestamp, + "The value of startTimestamp should be smaller than the one of endTimestamp. Start timestamp: %d. End " + + "timestamp: %d", startTimestamp, endTimestamp); + + List tableNamesWithType = ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager, tableName, + Constants.validateTableType(tableTypeStr), LOGGER); + List>> resultList = new ArrayList<>(tableNamesWithType.size()); + for (String tableNameWithType : tableNamesWithType) { + TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableNameWithType); + List segments = + _pinotHelixResourceManager.getSegmentsFor(tableNameWithType, excludeReplacedSegments, startTimestamp, + endTimestamp, excludeOverlapping); + resultList.add(Pair.of(tableType, segments)); + } + return resultList; + } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java index 0d493ab3d01..90f6a663a1f 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java @@ -69,6 +69,8 @@ import org.apache.pinot.common.metrics.ControllerGauge; import org.apache.pinot.common.metrics.ControllerMeter; import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.common.restlet.resources.EndReplaceSegmentsRequest; +import org.apache.pinot.common.restlet.resources.RevertReplaceSegmentsRequest; import org.apache.pinot.common.restlet.resources.StartReplaceSegmentsRequest; import org.apache.pinot.common.utils.FileUploadDownloadClient; import org.apache.pinot.common.utils.URIUtils; @@ -94,7 +96,6 @@ import org.apache.pinot.spi.filesystem.PinotFSFactory; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.JsonUtils; -import org.apache.pinot.spi.utils.StringUtil; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.glassfish.grizzly.http.server.Request; import org.glassfish.jersey.media.multipart.FormDataBodyPart; @@ -167,7 +168,12 @@ public Response downloadSegment( File segmentFile; // If the segment file is local, just use it as the return entity; otherwise copy it from remote to local first. if (CommonConstants.Segment.LOCAL_SEGMENT_SCHEME.equals(dataDirURI.getScheme())) { - segmentFile = new File(new File(dataDirURI), StringUtil.join(File.separator, tableName, segmentName)); + File dataDir = new File(dataDirURI); + File tableDir = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile(dataDir, tableName, + "Invalid table name: %s", tableName); + segmentFile = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile(tableDir, segmentName, + "Invalid segment name: %s", segmentName); + if (!segmentFile.exists()) { throw new ControllerApplicationException(LOGGER, "Segment " + segmentName + " or table " + tableName + " not found in " + segmentFile.getAbsolutePath(), @@ -182,8 +188,13 @@ public Response downloadSegment( "Segment: " + segmentName + " of table: " + tableName + " not found at: " + remoteSegmentFileURI, Response.Status.NOT_FOUND); } - segmentFile = new File(new File(ControllerFilePathProvider.getInstance().getFileDownloadTempDir(), tableName), - segmentName + "-" + UUID.randomUUID()); + File downloadTempDir = ControllerFilePathProvider.getInstance().getFileDownloadTempDir(); + File tableDir = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile(downloadTempDir, tableName, + "Invalid table name: %s", tableName); + segmentFile = + org.apache.pinot.common.utils.FileUtils.concatAndValidateFile(tableDir, segmentName + "-" + UUID.randomUUID(), + "Invalid segment name: %s", segmentName); + pinotFS.copyToLocalFile(remoteSegmentFileURI, segmentFile); // Streaming in the tmp file and delete it afterward. builder.entity((StreamingOutput) output -> { @@ -629,7 +640,8 @@ public Response startReplaceSegments( ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager, tableName, tableType, LOGGER).get(0); try { String segmentLineageEntryId = _pinotHelixResourceManager.startReplaceSegments(tableNameWithType, - startReplaceSegmentsRequest.getSegmentsFrom(), startReplaceSegmentsRequest.getSegmentsTo(), forceCleanup); + startReplaceSegmentsRequest.getSegmentsFrom(), startReplaceSegmentsRequest.getSegmentsTo(), forceCleanup, + startReplaceSegmentsRequest.getCustomMap()); return Response.ok(JsonUtils.newObjectNode().put("segmentLineageEntryId", segmentLineageEntryId)).build(); } catch (Exception e) { _controllerMetrics.addMeteredTableValue(tableNameWithType, ControllerMeter.NUMBER_START_REPLACE_FAILURE, 1); @@ -646,7 +658,9 @@ public Response endReplaceSegments( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "OFFLINE|REALTIME", required = true) @QueryParam("type") String tableTypeStr, @ApiParam(value = "Segment lineage entry id returned by startReplaceSegments API", required = true) - @QueryParam("segmentLineageEntryId") String segmentLineageEntryId) { + @QueryParam("segmentLineageEntryId") String segmentLineageEntryId, + @ApiParam(value = "Fields belonging to end replace segment request", required = false) + EndReplaceSegmentsRequest endReplaceSegmentsRequest) { TableType tableType = Constants.validateTableType(tableTypeStr); if (tableType == null) { throw new ControllerApplicationException(LOGGER, "Table type should either be offline or realtime", @@ -657,7 +671,8 @@ public Response endReplaceSegments( try { // Check that the segment lineage entry id is valid Preconditions.checkNotNull(segmentLineageEntryId, "'segmentLineageEntryId' should not be null"); - _pinotHelixResourceManager.endReplaceSegments(tableNameWithType, segmentLineageEntryId); + _pinotHelixResourceManager.endReplaceSegments(tableNameWithType, segmentLineageEntryId, + endReplaceSegmentsRequest); return Response.ok().build(); } catch (Exception e) { _controllerMetrics.addMeteredTableValue(tableNameWithType, ControllerMeter.NUMBER_END_REPLACE_FAILURE, 1); @@ -676,7 +691,9 @@ public Response revertReplaceSegments( @ApiParam(value = "Segment lineage entry id to revert", required = true) @QueryParam("segmentLineageEntryId") String segmentLineageEntryId, @ApiParam(value = "Force revert in case the user knows that the lineage entry is interrupted") - @QueryParam("forceRevert") @DefaultValue("false") boolean forceRevert) { + @QueryParam("forceRevert") @DefaultValue("false") boolean forceRevert, + @ApiParam(value = "Fields belonging to revert replace segment request", required = false) + RevertReplaceSegmentsRequest revertReplaceSegmentsRequest) { TableType tableType = Constants.validateTableType(tableTypeStr); if (tableType == null) { throw new ControllerApplicationException(LOGGER, "Table type should either be offline or realtime", @@ -687,7 +704,8 @@ public Response revertReplaceSegments( try { // Check that the segment lineage entry id is valid Preconditions.checkNotNull(segmentLineageEntryId, "'segmentLineageEntryId' should not be null"); - _pinotHelixResourceManager.revertReplaceSegments(tableNameWithType, segmentLineageEntryId, forceRevert); + _pinotHelixResourceManager.revertReplaceSegments(tableNameWithType, segmentLineageEntryId, forceRevert, + revertReplaceSegmentsRequest); return Response.ok().build(); } catch (Exception e) { _controllerMetrics.addMeteredTableValue(tableNameWithType, ControllerMeter.NUMBER_REVERT_REPLACE_FAILURE, 1); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java index cfa1cffe2d5..a5f7e26901a 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java @@ -28,6 +28,8 @@ import io.swagger.annotations.ApiKeyAuthDefinition; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; @@ -76,6 +78,7 @@ import org.apache.pinot.common.exception.SchemaNotFoundException; import org.apache.pinot.common.exception.TableNotFoundException; import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.controllerjob.ControllerJobType; import org.apache.pinot.common.metrics.ControllerMeter; import org.apache.pinot.common.metrics.ControllerMetrics; import org.apache.pinot.common.restlet.resources.TableSegmentValidationInfo; @@ -89,8 +92,11 @@ import org.apache.pinot.controller.api.exception.InvalidTableConfigException; import org.apache.pinot.controller.api.exception.TableAlreadyExistsException; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.controller.helix.core.PinotResourceManagerResponse; import org.apache.pinot.controller.helix.core.minion.PinotHelixTaskResourceManager; import org.apache.pinot.controller.helix.core.rebalance.RebalanceResult; +import org.apache.pinot.controller.helix.core.rebalance.TableRebalanceProgressStats; +import org.apache.pinot.controller.helix.core.rebalance.TableRebalancer; import org.apache.pinot.controller.recommender.RecommenderDriver; import org.apache.pinot.controller.tuner.TableConfigTunerUtils; import org.apache.pinot.controller.util.CompletionServiceHelper; @@ -176,8 +182,7 @@ public class PinotTableRestletResource { @Path("/tables") @ApiOperation(value = "Adds a table", notes = "Adds a table") @ManualAuthorization // performed after parsing table configs - public ConfigSuccessResponse addTable( - String tableConfigStr, + public ConfigSuccessResponse addTable(String tableConfigStr, @ApiParam(value = "comma separated list of validation type(s) to skip. supported types: (ALL|TASK|UPSERT)") @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, @Context HttpHeaders httpHeaders, @Context Request request) { @@ -204,10 +209,10 @@ public ConfigSuccessResponse addTable( TableConfigUtils.validate(tableConfig, schema, typesToSkip, _controllerConf.isDisableIngestionGroovy()); // TableConfigUtils.validateTableName(...) checks table name rules. // So it won't affect already created tables. - boolean allowTableNameWithDatabase = _controllerConf.getProperty( - CommonConstants.Helix.ALLOW_TABLE_NAME_WITH_DATABASE, - CommonConstants.Helix.DEFAULT_ALLOW_TABLE_NAME_WITH_DATABASE); - TableConfigUtils.validateTableName(tableConfig, allowTableNameWithDatabase); + boolean allowTableNameWithDatabase = + _controllerConf.getProperty(CommonConstants.Helix.ALLOW_TABLE_NAME_WITH_DATABASE, + CommonConstants.Helix.DEFAULT_ALLOW_TABLE_NAME_WITH_DATABASE); + TableConfigUtils.validateTableName(tableConfig, allowTableNameWithDatabase); } catch (Exception e) { throw new ControllerApplicationException(LOGGER, e.getMessage(), Response.Status.BAD_REQUEST, e); } @@ -322,7 +327,7 @@ public String listTables(@ApiParam(value = "realtime|offline") @QueryParam("type tableNamesWithType.set(j, tempTableName); }; Arrays.quickSort(0, numTables, comparator, swapper); - tableNames = tableNamesWithType; + tableNames = tableNamesWithType; } return JsonUtils.newObjectNode().set("tables", JsonUtils.objectToJsonNode(tableNames)).toString(); @@ -361,9 +366,7 @@ private String listTableConfigs(String tableName, @Nullable String tableTypeStr) @GET @Produces(MediaType.APPLICATION_JSON) @Path("/tables/{tableName}") - @ApiOperation(value = "Get/Enable/Disable/Drop a table", - notes = "Get/Enable/Disable/Drop a table. If table name is the only parameter specified " - + ", the tableconfig will be printed") + @ApiOperation(value = "Lists the table configs") public String alterTableStateOrListTableConfig( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "enable|disable|drop") @QueryParam("state") String stateStr, @@ -374,13 +377,15 @@ public String alterTableStateOrListTableConfig( return listTableConfigs(tableName, tableTypeStr); } + // TODO: DO NOT allow toggling state with GET request + StateType stateType = Constants.validateState(stateStr); TableType tableType = Constants.validateTableType(tableTypeStr); // validate if user has permission to change the table state String endpointUrl = request.getRequestURL().toString(); - AccessControlUtils - .validatePermission(tableName, AccessType.UPDATE, httpHeaders, endpointUrl, _accessControlFactory.create()); + AccessControlUtils.validatePermission(tableName, AccessType.UPDATE, httpHeaders, endpointUrl, + _accessControlFactory.create()); ArrayNode ret = JsonUtils.newArrayNode(); boolean tableExists = false; @@ -540,8 +545,7 @@ public ConfigSuccessResponse updateTableConfig( notes = "This API returns the table config that matches the one you get from 'GET /tables/{tableName}'." + " This allows us to validate table config before apply.") @ManualAuthorization // performed after parsing TableConfig - public ObjectNode checkTableConfig( - String tableConfigStr, + public ObjectNode checkTableConfig(String tableConfigStr, @ApiParam(value = "comma separated list of validation type(s) to skip. supported types: (ALL|TASK|UPSERT)") @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, @Context HttpHeaders httpHeaders, @Context Request request) { @@ -577,8 +581,7 @@ public ObjectNode checkTableConfig( + "table name. This API returns the table config that matches the one you get from 'GET /tables/{tableName}'." + " This allows us to validate table config before apply.") @ManualAuthorization // performed after parsing TableAndSchemaConfig - public String validateTableAndSchema( - TableAndSchemaConfig tableSchemaConfig, + public String validateTableAndSchema(TableAndSchemaConfig tableSchemaConfig, @ApiParam(value = "comma separated list of validation type(s) to skip. supported types: (ALL|TASK|UPSERT)") @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, @Context HttpHeaders httpHeaders, @Context Request request) { @@ -627,7 +630,7 @@ public RebalanceResult rebalance( @ApiParam(value = "Name of the table to rebalance", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "OFFLINE|REALTIME", required = true) @QueryParam("type") String tableTypeStr, @ApiParam(value = "Whether to rebalance table in dry-run mode") @DefaultValue("false") @QueryParam("dryRun") - boolean dryRun, + boolean dryRun, @ApiParam(value = "Whether to reassign instances before reassigning segments") @DefaultValue("false") @QueryParam("reassignInstances") boolean reassignInstances, @ApiParam(value = "Whether to reassign CONSUMING segments for real-time table") @DefaultValue("false") @@ -636,16 +639,18 @@ public RebalanceResult rebalance( + "segments in a round-robin fashion as if adding new segments to an empty table)") @DefaultValue("false") @QueryParam("bootstrap") boolean bootstrap, @ApiParam(value = "Whether to allow downtime for the rebalance") @DefaultValue("false") @QueryParam("downtime") - boolean downtime, @ApiParam( - value = "For no-downtime rebalance, minimum number of replicas to keep alive during rebalance, or maximum " - + "number of replicas allowed to be unavailable if value is negative") @DefaultValue("1") + boolean downtime, + @ApiParam(value = "For no-downtime rebalance, minimum number of replicas to keep alive during rebalance, or " + + "maximum number of replicas allowed to be unavailable if value is negative") @DefaultValue("1") @QueryParam("minAvailableReplicas") int minAvailableReplicas, @ApiParam( value = "Whether to use best-efforts to rebalance (not fail the rebalance when the no-downtime contract cannot " + "be achieved)") @DefaultValue("false") @QueryParam("bestEfforts") boolean bestEfforts, @ApiParam( value = "How often to check if external view converges with ideal states") @DefaultValue("1000") @QueryParam("externalViewCheckIntervalInMs") long externalViewCheckIntervalInMs, @ApiParam(value = "How long to wait till external view converges with ideal states") @DefaultValue("3600000") - @QueryParam("externalViewStabilizationTimeoutInMs") long externalViewStabilizationTimeoutInMs) { + @QueryParam("externalViewStabilizationTimeoutInMs") long externalViewStabilizationTimeoutInMs, + @ApiParam(value = "Whether to update segment target tier as part of the rebalance") @DefaultValue("false") + @QueryParam("updateTargetTier") boolean updateTargetTier) { String tableNameWithType = constructTableNameWithType(tableName, tableTypeStr); @@ -661,29 +666,32 @@ public RebalanceResult rebalance( externalViewCheckIntervalInMs); rebalanceConfig.addProperty(RebalanceConfigConstants.EXTERNAL_VIEW_STABILIZATION_TIMEOUT_IN_MS, externalViewStabilizationTimeoutInMs); + rebalanceConfig.addProperty(RebalanceConfigConstants.UPDATE_TARGET_TIER, updateTargetTier); + rebalanceConfig.addProperty(RebalanceConfigConstants.JOB_ID, TableRebalancer.createUniqueRebalanceJobIdentifier()); try { if (dryRun || downtime) { // For dry-run or rebalance with downtime, directly return the rebalance result as it should return immediately - return _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig); + return _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig, false); } else { // Make a dry-run first to get the target assignment rebalanceConfig.setProperty(RebalanceConfigConstants.DRY_RUN, true); - RebalanceResult dryRunResult = _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig); + RebalanceResult dryRunResult = + _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig, false); if (dryRunResult.getStatus() == RebalanceResult.Status.DONE) { // If dry-run succeeded, run rebalance asynchronously rebalanceConfig.setProperty(RebalanceConfigConstants.DRY_RUN, false); _executorService.submit(() -> { try { - _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig); + _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig, true); } catch (Throwable t) { LOGGER.error("Caught exception/error while rebalancing table: {}", tableNameWithType, t); } }); - return new RebalanceResult(RebalanceResult.Status.IN_PROGRESS, + return new RebalanceResult(dryRunResult.getJobId(), RebalanceResult.Status.IN_PROGRESS, "In progress, check controller logs for updates", dryRunResult.getInstanceAssignment(), - dryRunResult.getSegmentAssignment()); + dryRunResult.getTierInstanceAssignment(), dryRunResult.getSegmentAssignment()); } else { // If dry-run failed or is no-op, return the dry-run result return dryRunResult; @@ -712,6 +720,76 @@ public String getTableState( } } + @PUT + @Path("/tables/{tableName}/state") + @Authenticate(AccessType.UPDATE) + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Enable/disable a table", notes = "Enable/disable a table") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 400, message = "Bad Request"), + @ApiResponse(code = 404, message = "Table not found"), + @ApiResponse(code = 500, message = "Internal error") + }) + public SuccessResponse toggleTableState( + @ApiParam(value = "Table name", required = true) @PathParam("tableName") String tableName, + @ApiParam(value = "realtime|offline", required = true) @QueryParam("type") String tableTypeStr, + @ApiParam(value = "enable|disable", required = true) @QueryParam("state") String state) { + String tableNameWithType = constructTableNameWithType(tableName, tableTypeStr); + StateType stateType; + if (StateType.ENABLE.name().equalsIgnoreCase(state)) { + stateType = StateType.ENABLE; + } else if (StateType.DISABLE.name().equalsIgnoreCase(state)) { + stateType = StateType.DISABLE; + } else { + throw new ControllerApplicationException(LOGGER, "Unknown state '" + state + "'", Response.Status.BAD_REQUEST); + } + if (!_pinotHelixResourceManager.hasTable(tableNameWithType)) { + throw new ControllerApplicationException(LOGGER, "Table '" + tableName + "' does not exist", + Response.Status.NOT_FOUND); + } + PinotResourceManagerResponse response = _pinotHelixResourceManager.toggleTableState(tableNameWithType, stateType); + if (response.isSuccessful()) { + return new SuccessResponse("Request to " + state + " table '" + tableNameWithType + "' is successful"); + } else { + throw new ControllerApplicationException(LOGGER, + "Failed to " + state + " table '" + tableNameWithType + "': " + response.getMessage(), + Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Authenticate(AccessType.UPDATE) + @Path("/rebalanceStatus/{jobId}") + @ApiOperation(value = "Gets detailed stats of a rebalance operation", + notes = "Gets detailed stats of a rebalance operation") + public ServerRebalanceJobStatusResponse rebalanceStatus( + @ApiParam(value = "Rebalance Job Id", required = true) @PathParam("jobId") String jobId) + throws JsonProcessingException { + Map controllerJobZKMetadata = + _pinotHelixResourceManager.getControllerJobZKMetadata(jobId, ControllerJobType.TABLE_REBALANCE); + + if (controllerJobZKMetadata == null) { + throw new ControllerApplicationException(LOGGER, "Failed to find controller job id: " + jobId, + Response.Status.NOT_FOUND); + } + TableRebalanceProgressStats tableRebalanceProgressStats = + JsonUtils.stringToObject(controllerJobZKMetadata.get(RebalanceConfigConstants.REBALANCE_PROGRESS_STATS), + TableRebalanceProgressStats.class); + long timeSinceStartInSecs = 0L; + if (!tableRebalanceProgressStats.getStatus().equals(RebalanceResult.Status.DONE)) { + timeSinceStartInSecs = + (System.currentTimeMillis() - tableRebalanceProgressStats.getStartTimeMs()) / 1000; + } + + ServerRebalanceJobStatusResponse serverRebalanceJobStatusResponse = new ServerRebalanceJobStatusResponse(); + serverRebalanceJobStatusResponse.setTableRebalanceProgressStats(tableRebalanceProgressStats); + serverRebalanceJobStatusResponse.setTimeElapsedSinceStartInSeconds(timeSinceStartInSecs); + return serverRebalanceJobStatusResponse; + } + @GET @Path("/tables/{tableName}/stats") @Produces(MediaType.APPLICATION_JSON) @@ -720,14 +798,14 @@ public String getTableStats( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "realtime|offline") @QueryParam("type") String tableTypeStr) { ObjectNode ret = JsonUtils.newObjectNode(); - if ((tableTypeStr == null || TableType.OFFLINE.name().equalsIgnoreCase(tableTypeStr)) && _pinotHelixResourceManager - .hasOfflineTable(tableName)) { + if ((tableTypeStr == null || TableType.OFFLINE.name().equalsIgnoreCase(tableTypeStr)) + && _pinotHelixResourceManager.hasOfflineTable(tableName)) { String tableNameWithType = TableNameBuilder.forType(TableType.OFFLINE).tableNameWithType(tableName); TableStats tableStats = _pinotHelixResourceManager.getTableStats(tableNameWithType); ret.set(TableType.OFFLINE.name(), JsonUtils.objectToJsonNode(tableStats)); } - if ((tableTypeStr == null || TableType.REALTIME.name().equalsIgnoreCase(tableTypeStr)) && _pinotHelixResourceManager - .hasRealtimeTable(tableName)) { + if ((tableTypeStr == null || TableType.REALTIME.name().equalsIgnoreCase(tableTypeStr)) + && _pinotHelixResourceManager.hasRealtimeTable(tableName)) { String tableNameWithType = TableNameBuilder.forType(TableType.REALTIME).tableNameWithType(tableName); TableStats tableStats = _pinotHelixResourceManager.getTableStats(tableNameWithType); ret.set(TableType.REALTIME.name(), JsonUtils.objectToJsonNode(tableStats)); @@ -749,9 +827,8 @@ private String constructTableNameWithType(String tableName, String tableTypeStr) private void checkHybridTableConfig(String rawTableName, TableConfig tableConfig) { if (tableConfig.getTableType() == TableType.REALTIME) { if (_pinotHelixResourceManager.hasOfflineTable(rawTableName)) { - TableConfigUtils - .verifyHybridTableConfigs(rawTableName, _pinotHelixResourceManager.getOfflineTableConfig(rawTableName), - tableConfig); + TableConfigUtils.verifyHybridTableConfigs(rawTableName, + _pinotHelixResourceManager.getOfflineTableConfig(rawTableName), tableConfig); } } else { if (_pinotHelixResourceManager.hasRealtimeTable(rawTableName)) { @@ -782,8 +859,8 @@ public String getTableStatus( } TableStatus.IngestionStatus ingestionStatus = null; if (TableType.OFFLINE == tableType) { - ingestionStatus = TableIngestionStatusHelper - .getOfflineTableIngestionStatus(tableNameWithType, _pinotHelixResourceManager, + ingestionStatus = + TableIngestionStatusHelper.getOfflineTableIngestionStatus(tableNameWithType, _pinotHelixResourceManager, _pinotHelixTaskResourceManager); } else { ingestionStatus = TableIngestionStatusHelper.getRealtimeTableIngestionStatus(tableNameWithType, @@ -808,7 +885,7 @@ public String getTableAggregateMetadata( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr, @ApiParam(value = "Columns name", allowMultiple = true) @QueryParam("columns") @DefaultValue("") - List columns) { + List columns) { LOGGER.info("Received a request to fetch aggregate metadata for a table {}", tableName); TableType tableType = Constants.validateTableType(tableTypeStr); if (tableType == TableType.REALTIME) { @@ -861,13 +938,21 @@ public Map> getControllerJobs( List tableNamesWithType = ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager, tableName, tableTypeFromRequest, LOGGER); - Set jobTypesToFilter = null; + Set validJobTypes = + java.util.Arrays.stream(ControllerJobType.values()).collect(Collectors.toSet()); + Set jobTypesToFilter = null; if (StringUtils.isNotEmpty(jobTypesString)) { - jobTypesToFilter = new HashSet<>(java.util.Arrays.asList(StringUtils.split(jobTypesString, ','))); + try { + jobTypesToFilter = new HashSet<>(java.util.Arrays.asList(StringUtils.split(jobTypesString, ','))).stream() + .map(type -> ControllerJobType.valueOf(type)).collect(Collectors.toSet()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Valid Types are: " + validJobTypes); + } } Map> result = new HashMap<>(); for (String tableNameWithType : tableNamesWithType) { - result.putAll(_pinotHelixResourceManager.getAllJobsForTable(tableNameWithType, jobTypesToFilter)); + result.putAll(_pinotHelixResourceManager.getAllJobsForTable(tableNameWithType, + jobTypesToFilter == null ? validJobTypes : jobTypesToFilter)); } return result; diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java index c2605c7614d..ae1a079c9c1 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java @@ -25,14 +25,18 @@ import io.swagger.annotations.ApiKeyAuthDefinition; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; import java.io.IOException; import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -58,6 +62,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.commons.httpclient.HttpConnectionManager; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.helix.model.InstanceConfig; import org.apache.helix.task.TaskPartitionState; @@ -69,7 +74,6 @@ import org.apache.pinot.controller.api.access.AccessType; import org.apache.pinot.controller.api.access.Authenticate; import org.apache.pinot.controller.api.exception.ControllerApplicationException; -import org.apache.pinot.controller.api.exception.NoTaskMetadataException; import org.apache.pinot.controller.api.exception.NoTaskScheduledException; import org.apache.pinot.controller.api.exception.TaskAlreadyExistsException; import org.apache.pinot.controller.api.exception.UnknownTaskTypeException; @@ -153,6 +157,7 @@ public class PinotTaskRestletResource { @GET @Path("/tasks/tasktypes") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("List all task types") public Set listTaskTypes() { return _pinotHelixTaskResourceManager.getTaskTypes(); @@ -161,6 +166,7 @@ public Set listTaskTypes() { @Deprecated @GET @Path("/tasks/taskqueues") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("List all task queues (deprecated)") public Set getTaskQueues() { return _pinotHelixTaskResourceManager.getTaskQueues(); @@ -168,6 +174,7 @@ public Set getTaskQueues() { @GET @Path("/tasks/{taskType}/state") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the state (task queue state) for the given task type") public TaskState getTaskQueueState( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { @@ -177,6 +184,7 @@ public TaskState getTaskQueueState( @Deprecated @GET @Path("/tasks/taskqueuestate/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the state (task queue state) for the given task type (deprecated)") public StringResultResponse getTaskQueueStateDeprecated( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { @@ -185,6 +193,7 @@ public StringResultResponse getTaskQueueStateDeprecated( @GET @Path("/tasks/{taskType}/tasks") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("List all tasks for the given task type") public Set getTasks(@ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { return _pinotHelixTaskResourceManager.getTasks(taskType); @@ -192,6 +201,7 @@ public Set getTasks(@ApiParam(value = "Task type", required = true) @Pat @GET @Path("/tasks/{taskType}/{tableNameWithType}/state") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("List all tasks for the given task type") public Map getTaskStatesByTable( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType, @@ -202,6 +212,7 @@ public Map getTaskStatesByTable( @GET @Path("/tasks/{taskType}/{tableNameWithType}/metadata") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get task metadata for the given task type and table") public String getTaskMetadataByTable( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType, @@ -209,8 +220,6 @@ public String getTaskMetadataByTable( String tableNameWithType) { try { return _pinotHelixTaskResourceManager.getTaskMetadataByTable(taskType, tableNameWithType); - } catch (NoTaskMetadataException e) { - throw new ControllerApplicationException(LOGGER, e.getMessage(), Response.Status.NOT_FOUND); } catch (JsonProcessingException e) { throw new ControllerApplicationException(LOGGER, String .format("Failed to format task metadata into Json for task type: %s from table: %s", taskType, @@ -220,6 +229,7 @@ public String getTaskMetadataByTable( @DELETE @Path("/tasks/{taskType}/{tableNameWithType}/metadata") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Delete task metadata for the given task type and table") public SuccessResponse deleteTaskMetadataByTable( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType, @@ -232,6 +242,7 @@ public SuccessResponse deleteTaskMetadataByTable( @GET @Path("/tasks/{taskType}/taskcounts") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch count of sub-tasks for each of the tasks for the given task type") public Map getTaskCounts( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { @@ -240,6 +251,7 @@ public Map getTaskCounts( @GET @Path("/tasks/{taskType}/debug") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch information for all the tasks for the given task type") public Map getTasksDebugInfo( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType, @@ -252,6 +264,7 @@ public Map getTasksDebugInf @GET @Path("/tasks/{taskType}/{tableNameWithType}/debug") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch information for all the tasks for the given task type and table") public Map getTasksDebugInfo( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType, @@ -320,6 +333,7 @@ public String getTaskGenerationDebugInto( @GET @Path("/tasks/task/{taskName}/debug") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch information for the given task name") public PinotHelixTaskResourceManager.TaskDebugInfo getTaskDebugInfo( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName, @@ -333,6 +347,7 @@ public PinotHelixTaskResourceManager.TaskDebugInfo getTaskDebugInfo( @Deprecated @GET @Path("/tasks/tasks/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("List all tasks for the given task type (deprecated)") public Set getTasksDeprecated( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { @@ -341,6 +356,7 @@ public Set getTasksDeprecated( @GET @Path("/tasks/{taskType}/taskstates") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get a map from task to task state for the given task type") public Map getTaskStates( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { @@ -350,6 +366,7 @@ public Map getTaskStates( @Deprecated @GET @Path("/tasks/taskstates/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get a map from task to task state for the given task type (deprecated)") public Map getTaskStatesDeprecated( @ApiParam(value = "Task type", required = true) @PathParam("taskType") String taskType) { @@ -358,6 +375,7 @@ public Map getTaskStatesDeprecated( @GET @Path("/tasks/task/{taskName}/state") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the task state for the given task") public TaskState getTaskState( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName) { @@ -367,6 +385,7 @@ public TaskState getTaskState( @Deprecated @GET @Path("/tasks/taskstate/{taskName}") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the task state for the given task (deprecated)") public StringResultResponse getTaskStateDeprecated( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName) { @@ -375,6 +394,7 @@ public StringResultResponse getTaskStateDeprecated( @GET @Path("/tasks/subtask/{taskName}/state") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the states of all the sub tasks for the given task") public Map getSubtaskStates( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName) { @@ -383,6 +403,7 @@ public Map getSubtaskStates( @GET @Path("/tasks/task/{taskName}/config") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the task config (a list of child task configs) for the given task") public List getTaskConfigs( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName) { @@ -391,6 +412,7 @@ public List getTaskConfigs( @GET @Path("/tasks/task/{taskName}/runtime/config") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the task runtime config for the given task") public Map getTaskConfig( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName) { @@ -400,6 +422,7 @@ public Map getTaskConfig( @Deprecated @GET @Path("/tasks/taskconfig/{taskName}") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the task config (a list of child task configs) for the given task (deprecated)") public List getTaskConfigsDeprecated( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName) { @@ -408,6 +431,7 @@ public List getTaskConfigsDeprecated( @GET @Path("/tasks/subtask/{taskName}/config") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Get the configs of specified sub tasks for the given task") public Map getSubtaskConfigs( @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName, @@ -419,7 +443,7 @@ public Map getSubtaskConfigs( @GET @Path("/tasks/subtask/{taskName}/progress") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation("Get progress of specified sub tasks for the given task tracked by worker in memory") + @ApiOperation("Get progress of specified sub tasks for the given task tracked by minion worker in memory") public String getSubtaskProgress(@Context HttpHeaders httpHeaders, @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName, @ApiParam(value = "Sub task names separated by comma") @QueryParam("subtaskNames") @Nullable @@ -451,8 +475,52 @@ public String getSubtaskProgress(@Context HttpHeaders httpHeaders, } } + @GET + @Path("/tasks/subtask/workers/progress") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation("Get progress of all subtasks with specified state tracked by minion worker in memory") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error") + }) + public String getSubtaskOnWorkerProgress(@Context HttpHeaders httpHeaders, + @ApiParam(value = "Subtask state (UNKNOWN,IN_PROGRESS,SUCCEEDED,CANCELLED,ERROR)", required = true) + @QueryParam("subTaskState") String subTaskState, + @ApiParam(value = "Minion worker IDs separated by comma") @QueryParam("minionWorkerIds") @Nullable + String minionWorkerIds) { + Set selectedMinionWorkers = new HashSet<>(); + if (StringUtils.isNotEmpty(minionWorkerIds)) { + selectedMinionWorkers.addAll( + Arrays.stream(StringUtils.split(minionWorkerIds, ',')).map(String::trim).collect(Collectors.toList())); + } + // Relying on original schema that was used to query the controller + String scheme = _uriInfo.getRequestUri().getScheme(); + List allMinionWorkerInstanceConfigs = _pinotHelixResourceManager.getAllMinionInstanceConfigs(); + Map selectedMinionWorkerEndpoints = new HashMap<>(); + for (InstanceConfig worker : allMinionWorkerInstanceConfigs) { + if (selectedMinionWorkers.isEmpty() || selectedMinionWorkers.contains(worker.getId())) { + selectedMinionWorkerEndpoints.put(worker.getId(), + String.format("%s://%s:%d", scheme, worker.getHostName(), Integer.parseInt(worker.getPort()))); + } + } + Map requestHeaders = new HashMap<>(); + httpHeaders.getRequestHeaders().keySet().forEach(header -> + requestHeaders.put(header, httpHeaders.getHeaderString(header))); + int timeoutMs = _controllerConf.getMinionAdminRequestTimeoutSeconds() * 1000; + try { + Map minionWorkerIdSubtaskProgressMap = + _pinotHelixTaskResourceManager.getSubtaskOnWorkerProgress(subTaskState, _executor, _connectionManager, + selectedMinionWorkerEndpoints, requestHeaders, timeoutMs); + return JsonUtils.objectToString(minionWorkerIdSubtaskProgressMap); + } catch (Exception e) { + throw new ControllerApplicationException(LOGGER, + String.format("Failed to get minion worker side progress for subtasks with state %s due to error: %s", + subTaskState, ExceptionUtils.getStackTrace(e)), Response.Status.INTERNAL_SERVER_ERROR, e); + } + } + @GET @Path("/tasks/scheduler/information") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch cron scheduler information") public Map getCronSchedulerInformation() throws SchedulerException { @@ -490,6 +558,7 @@ public Map getCronSchedulerInformation() @GET @Path("/tasks/scheduler/jobKeys") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch cron scheduler job keys") public List getCronSchedulerJobKeys() throws SchedulerException { @@ -506,6 +575,7 @@ public List getCronSchedulerJobKeys() @GET @Path("/tasks/scheduler/jobDetails") + @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch cron scheduler job keys") public Map getCronSchedulerJobDetails( @ApiParam(value = "Table name (with type suffix)") @QueryParam("tableName") String tableName, @@ -562,6 +632,7 @@ public Map getCronSchedulerJobDetails( @POST @Path("/tasks/schedule") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Schedule tasks and return a map from task type to task name scheduled") public Map scheduleTasks(@ApiParam(value = "Task type") @QueryParam("taskType") String taskType, @@ -610,6 +681,7 @@ public void executeAdhocTask(AdhocTaskConfig adhocTaskConfig, @Suspended AsyncRe @Deprecated @PUT @Path("/tasks/scheduletasks") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Schedule tasks (deprecated)") public Map scheduleTasksDeprecated() { @@ -618,6 +690,7 @@ public Map scheduleTasksDeprecated() { @PUT @Path("/tasks/{taskType}/cleanup") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Clean up finished tasks (COMPLETED, FAILED) for the given task type") public SuccessResponse cleanUpTasks( @@ -629,6 +702,7 @@ public SuccessResponse cleanUpTasks( @Deprecated @PUT @Path("/tasks/cleanuptasks/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Clean up finished tasks (COMPLETED, FAILED) for the given task type (deprecated)") public SuccessResponse cleanUpTasksDeprecated( @@ -639,6 +713,7 @@ public SuccessResponse cleanUpTasksDeprecated( @PUT @Path("/tasks/{taskType}/stop") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Stop all running/pending tasks (as well as the task queue) for the given task type") public SuccessResponse stopTasks( @@ -649,6 +724,7 @@ public SuccessResponse stopTasks( @PUT @Path("/tasks/{taskType}/resume") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Resume all stopped tasks (as well as the task queue) for the given task type") public SuccessResponse resumeTasks( @@ -660,6 +736,7 @@ public SuccessResponse resumeTasks( @Deprecated @PUT @Path("/tasks/taskqueue/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.UPDATE) @ApiOperation("Stop/resume a task queue (deprecated)") public SuccessResponse toggleTaskQueueState( @@ -679,6 +756,7 @@ public SuccessResponse toggleTaskQueueState( @DELETE @Path("/tasks/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.DELETE) @ApiOperation("Delete all tasks (as well as the task queue) for the given task type") public SuccessResponse deleteTasks( @@ -691,6 +769,7 @@ public SuccessResponse deleteTasks( @DELETE @Path("/tasks/task/{taskName}") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.DELETE) @ApiOperation("Delete a single task given its task name") public SuccessResponse deleteTask( @@ -704,6 +783,7 @@ public SuccessResponse deleteTask( @Deprecated @DELETE @Path("/tasks/taskqueue/{taskType}") + @Produces(MediaType.APPLICATION_JSON) @Authenticate(AccessType.DELETE) @ApiOperation("Delete a task queue (deprecated)") public SuccessResponse deleteTaskQueue( diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTenantRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTenantRestletResource.java index d1c89d206a3..7b14b6fb0f0 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTenantRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTenantRestletResource.java @@ -203,21 +203,37 @@ public TenantsList getAllTenants( @GET @Path("/tenants/{tenantName}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "List instance for a tenant, or enable/disable/drop a tenant") + @ApiOperation(value = "List instance for a tenant") @ApiResponses(value = { @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Error reading tenants list") }) - public String listInstanceOrToggleTenantState( + public String listInstance( @ApiParam(value = "Tenant name", required = true) @PathParam("tenantName") String tenantName, @ApiParam(value = "Tenant type (server|broker)") @QueryParam("type") String tenantType, - @ApiParam(value = "Table type (offline|realtime)") @QueryParam("tableType") String tableType, - @ApiParam(value = "state") @QueryParam("state") String stateStr) - throws Exception { - if (stateStr == null) { - return listInstancesForTenant(tenantName, tenantType, tableType); - } else { + @ApiParam(value = "Table type (offline|realtime)") @QueryParam("tableType") String tableType) { + return listInstancesForTenant(tenantName, tenantType, tableType); + } + + @POST + @Path("/tenants/{tenantName}") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "enable/disable a tenant") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Error applying state to tenant") + }) + public SuccessResponse enableOrDisableTenant( + @ApiParam(value = "Tenant name", required = true) @PathParam("tenantName") String tenantName, + @ApiParam(value = "Tenant type (server|broker)") @QueryParam("type") String tenantType, + @ApiParam(value = "state (enable|disable)") @QueryParam("state") String stateStr) { + if (stateStr.equalsIgnoreCase(String.valueOf(StateType.ENABLE)) + || stateStr.equalsIgnoreCase(String.valueOf(StateType.DISABLE))) { return toggleTenantState(tenantName, stateStr, tenantType); + } else { + throw new ControllerApplicationException(LOGGER, + "Error: State mentioned " + stateStr + " is wrong. Valid States: Enable, Disable", + Response.Status.BAD_REQUEST); } } @@ -260,7 +276,7 @@ private String getTablesServedFromTenant(String tenantName) { return resourceGetRet.toString(); } - private String toggleTenantState(String tenantName, String stateStr, @Nullable String tenantType) { + private SuccessResponse toggleTenantState(String tenantName, String stateStr, @Nullable String tenantType) { Set serverInstances = new HashSet<>(); Set brokerInstances = new HashSet<>(); ObjectNode instanceResult = JsonUtils.newObjectNode(); @@ -276,27 +292,17 @@ private String toggleTenantState(String tenantName, String stateStr, @Nullable S Set allInstances = new HashSet(serverInstances); allInstances.addAll(brokerInstances); - if (StateType.DROP.name().equalsIgnoreCase(stateStr)) { - if (!allInstances.isEmpty()) { - throw new ControllerApplicationException(LOGGER, - "Error: Tenant " + tenantName + " has live instances, cannot be dropped.", Response.Status.BAD_REQUEST); + if (StateType.DISABLE.name().equalsIgnoreCase(stateStr)) { + for (String instance : allInstances) { + instanceResult.put(instance, JsonUtils.objectToJsonNode(_pinotHelixResourceManager.disableInstance(instance))); } - _pinotHelixResourceManager.deleteBrokerTenantFor(tenantName); - _pinotHelixResourceManager.deleteOfflineServerTenantFor(tenantName); - _pinotHelixResourceManager.deleteRealtimeServerTenantFor(tenantName); - return new SuccessResponse("Dropped tenant " + tenantName + " successfully.").toString(); } - - boolean enable = StateType.ENABLE.name().equalsIgnoreCase(stateStr) ? true : false; - for (String instance : allInstances) { - if (enable) { + if (StateType.ENABLE.name().equalsIgnoreCase(stateStr)) { + for (String instance : allInstances) { instanceResult.put(instance, JsonUtils.objectToJsonNode(_pinotHelixResourceManager.enableInstance(instance))); - } else { - instanceResult.put(instance, JsonUtils.objectToJsonNode(_pinotHelixResourceManager.disableInstance(instance))); } } - - return null; + return new SuccessResponse("Changed state of tenant " + tenantName + " to " + stateStr + " successfully."); } private String listInstancesForTenant(String tenantName, String tenantType, String tableTypeString) { @@ -401,6 +407,7 @@ public static class TenantsList { // CHANGE-ALERT: This is not backward compatible. We've changed this API from GET to POST because: // 1. That is correct // 2. with GET, we need to write our own routing logic to avoid conflict since this is same as the API above + @Deprecated @POST @Path("/tenants/{tenantName}/metadata") @Authenticate(AccessType.UPDATE) diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotUpsertRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotUpsertRestletResource.java index 363b288ef62..0c0fdaa8e0b 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotUpsertRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotUpsertRestletResource.java @@ -129,14 +129,25 @@ public String estimateHeapUsage(String tableSchemaConfigStr, // Estimated value space, it contains and overhead. // Here we only calculate the map content size. TODO: Add the map entry size and the array size within the map. int bytesPerValue = 60; - String comparisonColumn = tableConfig.getUpsertConfig().getComparisonColumn(); - if (comparisonColumn != null) { - FieldSpec.DataType dt = schema.getFieldSpecFor(comparisonColumn).getDataType(); - if (!dt.isFixedWidth()) { - String msg = "Not support data types for the comparison column"; - throw new ControllerApplicationException(LOGGER, msg, Response.Status.BAD_REQUEST); - } else { - bytesPerValue = 52 + dt.size(); + List comparisonColumns = tableConfig.getUpsertConfig().getComparisonColumns(); + if (comparisonColumns != null) { + int bytesPerArrayElem = 8; // object ref + bytesPerValue = 52; + for (String columnName : comparisonColumns) { + FieldSpec.DataType dt = schema.getFieldSpecFor(columnName).getDataType(); + if (!dt.isFixedWidth()) { + String msg = "Not support data types for the comparison column"; + throw new ControllerApplicationException(LOGGER, msg, Response.Status.BAD_REQUEST); + } else { + if (comparisonColumns.size() == 1) { + bytesPerValue += dt.size(); + } else { + bytesPerValue += bytesPerArrayElem + dt.size(); + } + } + } + if (comparisonColumns.size() > 1) { + bytesPerValue += 48 + 4; // array overhead + comparableIndex integer } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerRebalanceJobStatusResponse.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerRebalanceJobStatusResponse.java new file mode 100644 index 00000000000..3018b4877e8 --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerRebalanceJobStatusResponse.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.resources; + +import org.apache.pinot.controller.helix.core.rebalance.TableRebalanceProgressStats; + + +public class ServerRebalanceJobStatusResponse { + private long _timeElapsedSinceStartInSeconds; + + private TableRebalanceProgressStats _tableRebalanceProgressStats; + + public void setTimeElapsedSinceStartInSeconds(Long timeElapsedSinceStart) { + _timeElapsedSinceStartInSeconds = timeElapsedSinceStart; + } + + public void setTableRebalanceProgressStats(TableRebalanceProgressStats tableRebalanceProgressStats) { + _tableRebalanceProgressStats = tableRebalanceProgressStats; + } + + public TableRebalanceProgressStats getTableRebalanceProgressStats() { + return _tableRebalanceProgressStats; + } + + public long getTimeElapsedSinceStartInSeconds() { + return _timeElapsedSinceStartInSeconds; + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerTableSizeReader.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerTableSizeReader.java index 64eb00c491c..6ba049d3db4 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerTableSizeReader.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ServerTableSizeReader.java @@ -66,7 +66,8 @@ public Map> getSegmentSizeInfoFromServers(BiMap> serverToSegmentSizeInfoListMap = new HashMap<>(); int failedParses = 0; for (Map.Entry streamResponse : serviceResponse._httpResponses.entrySet()) { diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java index 8d8daff4d53..6e1989339c5 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java @@ -227,7 +227,7 @@ public ConfigSuccessResponse addConfig( // Invoke delete on tables whether they exist or not, to account for metadata/segments etc. _pinotHelixResourceManager.deleteRealtimeTable(rawTableName); _pinotHelixResourceManager.deleteOfflineTable(rawTableName); - _pinotHelixResourceManager.deleteSchema(schema); + _pinotHelixResourceManager.deleteSchema(schema.getSchemaName()); throw e; } @@ -271,12 +271,9 @@ public SuccessResponse deleteConfig( LOGGER.info("Deleted realtime table: {}", tableName); _pinotHelixResourceManager.deleteOfflineTable(tableName); LOGGER.info("Deleted offline table: {}", tableName); - Schema schema = _pinotHelixResourceManager.getSchema(tableName); - if (schema != null) { - _pinotHelixResourceManager.deleteSchema(schema); - LOGGER.info("Deleted schema: {}", tableName); - } - if (tableExists || schema != null) { + boolean schemaExists = _pinotHelixResourceManager.deleteSchema(tableName); + LOGGER.info("Deleted schema: {}", tableName); + if (tableExists || schemaExists) { return new SuccessResponse("Deleted TableConfigs: " + tableName); } else { return new SuccessResponse( @@ -291,6 +288,9 @@ public SuccessResponse deleteConfig( * Updated the {@link TableConfigs} by updating the schema tableName, * then updating the offline tableConfig or creating a new one if it doesn't already exist in the cluster, * then updating the realtime tableConfig or creating a new one if it doesn't already exist in the cluster. + * + * The option to skip table config validation (validationTypesToSkip) and force update the table schema + * (forceTableSchemaUpdate) are provided for testing purposes and should be used with caution. */ @PUT @Path("/tableConfigs/{tableName}") @@ -304,8 +304,10 @@ public ConfigSuccessResponse updateConfig( @ApiParam(value = "comma separated list of validation type(s) to skip. supported types: (ALL|TASK|UPSERT)") @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, @ApiParam(value = "Reload the table if the new schema is backward compatible") @DefaultValue("false") - @QueryParam("reload") boolean reload, String tableConfigsStr) - throws Exception { + @QueryParam("reload") boolean reload, + @ApiParam(value = "Force update the table schema") @DefaultValue("false") + @QueryParam("forceTableSchemaUpdate") boolean forceTableSchemaUpdate, + String tableConfigsStr) throws Exception { Pair> tableConfigsAndUnrecognizedProps; TableConfigs tableConfigs; try { @@ -333,7 +335,7 @@ public ConfigSuccessResponse updateConfig( Schema schema = tableConfigs.getSchema(); try { - _pinotHelixResourceManager.updateSchema(schema, reload); + _pinotHelixResourceManager.updateSchema(schema, reload, forceTableSchemaUpdate); LOGGER.info("Updated schema: {}", tableName); if (offlineTableConfig != null) { diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableSize.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableSize.java index 4e28b7af147..a9071c89659 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableSize.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableSize.java @@ -29,12 +29,10 @@ import io.swagger.annotations.SwaggerDefinition; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -72,18 +70,15 @@ public class TableSize { @GET @Path("/tables/{tableName}/size") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Read table sizes", - notes = "Get table size details. Table size is the size of untarred segments including replication") + @ApiOperation(value = "Read table sizes", notes = "Get table size details. Table size is the size of untarred " + + "segments including replication") @ApiResponses(value = { - @ApiResponse(code = 200, message = "Success"), - @ApiResponse(code = 404, message = "Table not found"), + @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 404, message = "Table not found"), @ApiResponse(code = 500, message = "Internal server error") }) public TableSizeReader.TableSizeDetails getTableSize( @ApiParam(value = "Table name without type", required = true, example = "myTable | myTable_OFFLINE") - @PathParam("tableName") String tableName, - @ApiParam(value = "Get detailed information", required = false) @DefaultValue("true") @QueryParam("detailed") - boolean detailed) { + @PathParam("tableName") String tableName) { TableSizeReader tableSizeReader = new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _pinotHelixResourceManager); TableSizeReader.TableSizeDetails tableSizeDetails = null; diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java index 2da40ef3e55..e6aa83dea15 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java @@ -81,6 +81,16 @@ public Schema getSchema(String schemaName) } } + public void updateSchema(Schema schema) + throws IOException { + String url = _controllerRequestURLBuilder.forSchemaUpdate(schema.getSchemaName()); + try { + HttpClient.wrapAndThrowHttpException(_httpClient.sendMultipartPutRequest(url, schema.toSingleLineJsonString())); + } catch (HttpErrorStatusException e) { + throw new IOException(e); + } + } + public void deleteSchema(String schemaName) throws IOException { String url = _controllerRequestURLBuilder.forSchemaDelete(schemaName); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java index 4eb14c59728..60ec5b24616 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java @@ -298,7 +298,7 @@ private void updateSegmentMetrics(String tableNameWithType, TableConfig tableCon (nReplicasIdealMax > 0) ? (nReplicasExternal * 100 / nReplicasIdealMax) : 100); _controllerMetrics.setValueOfTableGauge(tableNameWithType, ControllerGauge.SEGMENTS_IN_ERROR_STATE, nErrors); _controllerMetrics.setValueOfTableGauge(tableNameWithType, ControllerGauge.PERCENT_SEGMENTS_AVAILABLE, - (nSegments > 0) ? (100 - (nOffline * 100 / nSegments)) : 100); + (nSegments > 0) ? (nSegments - nOffline) * 100 / nSegments : 100); _controllerMetrics.setValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_COMPRESSED_SIZE, tableCompressedSize); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java index dda6b1de239..346a19a1d66 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java @@ -34,6 +34,7 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -47,7 +48,6 @@ import java.util.TreeSet; import java.util.UUID; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -112,12 +112,20 @@ import org.apache.pinot.common.metadata.instance.InstanceZKMetadata; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.minion.MinionTaskMetadataUtils; +import org.apache.pinot.common.restlet.resources.EndReplaceSegmentsRequest; +import org.apache.pinot.common.restlet.resources.RevertReplaceSegmentsRequest; +import org.apache.pinot.common.tier.Tier; +import org.apache.pinot.common.tier.TierFactory; +import org.apache.pinot.common.tier.TierSegmentSelector; import org.apache.pinot.common.utils.BcryptUtils; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.common.utils.SegmentName; import org.apache.pinot.common.utils.config.AccessControlUserConfigUtils; import org.apache.pinot.common.utils.config.InstanceUtils; import org.apache.pinot.common.utils.config.TableConfigUtils; import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.common.utils.config.TierConfigUtils; import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.common.utils.helix.PinotHelixPropertyStoreZnRecordProvider; import org.apache.pinot.controller.ControllerConf; @@ -126,15 +134,19 @@ import org.apache.pinot.controller.api.exception.TableAlreadyExistsException; import org.apache.pinot.controller.api.exception.UserAlreadyExistsException; import org.apache.pinot.controller.api.resources.InstanceInfo; +import org.apache.pinot.controller.api.resources.OperationValidationResponse; import org.apache.pinot.controller.api.resources.PeriodicTaskInvocationResponse; import org.apache.pinot.controller.api.resources.StateType; import org.apache.pinot.controller.helix.core.assignment.instance.InstanceAssignmentDriver; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignment; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentFactory; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils; +import org.apache.pinot.controller.helix.core.lineage.LineageManager; +import org.apache.pinot.controller.helix.core.lineage.LineageManagerFactory; import org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager; import org.apache.pinot.controller.helix.core.rebalance.RebalanceResult; import org.apache.pinot.controller.helix.core.rebalance.TableRebalancer; +import org.apache.pinot.controller.helix.core.rebalance.ZkBasedTableRebalanceObserver; import org.apache.pinot.controller.helix.core.util.ZKMetadataUtils; import org.apache.pinot.controller.helix.starter.HelixConfig; import org.apache.pinot.segment.spi.SegmentMetadata; @@ -148,6 +160,7 @@ import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TagOverrideConfig; import org.apache.pinot.spi.config.table.TenantConfig; +import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.UpsertConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.config.tenant.Tenant; @@ -164,6 +177,7 @@ import org.apache.pinot.spi.utils.IngestionConfigUtils; import org.apache.pinot.spi.utils.InstanceTypeUtils; import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.TimeUtils; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.spi.utils.retry.RetryPolicies; @@ -177,10 +191,15 @@ public class PinotHelixResourceManager { private static final Logger LOGGER = LoggerFactory.getLogger(PinotHelixResourceManager.class); private static final long CACHE_ENTRY_EXPIRE_TIME_HOURS = 6L; private static final RetryPolicy DEFAULT_RETRY_POLICY = RetryPolicies.exponentialBackoffRetryPolicy(5, 1000L, 2.0f); + private static final int DEFAULT_SEGMENT_LINEAGE_UPDATE_NUM_RETRY = 10; public static final String APPEND = "APPEND"; private static final int DEFAULT_TABLE_UPDATER_LOCKERS_SIZE = 100; private static final String API_REQUEST_ID_PREFIX = "api-"; + private enum LineageUpdateType { + START, END, REVERT + } + // TODO: make this configurable public static final long EXTERNAL_VIEW_ONLINE_SEGMENTS_MAX_WAIT_MS = 10 * 60_000L; // 10 minutes public static final long EXTERNAL_VIEW_CHECK_INTERVAL_MS = 1_000L; // 1 second @@ -203,6 +222,7 @@ public class PinotHelixResourceManager { private final boolean _enableBatchMessageMode; private final boolean _allowHLCTables; private final int _deletedSegmentsRetentionInDays; + private final boolean _enableTieredSegmentAssignment; private HelixManager _helixZkManager; private HelixAdmin _helixAdmin; @@ -212,10 +232,11 @@ public class PinotHelixResourceManager { private SegmentDeletionManager _segmentDeletionManager; private PinotLLCRealtimeSegmentManager _pinotLLCRealtimeSegmentManager; private TableCache _tableCache; + private final LineageManager _lineageManager; public PinotHelixResourceManager(String zkURL, String helixClusterName, @Nullable String dataDir, boolean isSingleTenantCluster, boolean enableBatchMessageMode, boolean allowHLCTables, - int deletedSegmentsRetentionInDays) { + int deletedSegmentsRetentionInDays, boolean enableTieredSegmentAssignment, LineageManager lineageManager) { _helixZkURL = HelixConfig.getAbsoluteZkPathForHelix(zkURL); _helixClusterName = helixClusterName; _dataDir = dataDir; @@ -223,6 +244,7 @@ public PinotHelixResourceManager(String zkURL, String helixClusterName, @Nullabl _enableBatchMessageMode = enableBatchMessageMode; _deletedSegmentsRetentionInDays = deletedSegmentsRetentionInDays; _allowHLCTables = allowHLCTables; + _enableTieredSegmentAssignment = enableTieredSegmentAssignment; _instanceAdminEndpointCache = CacheBuilder.newBuilder().expireAfterWrite(CACHE_ENTRY_EXPIRE_TIME_HOURS, TimeUnit.HOURS) .build(new CacheLoader() { @@ -237,12 +259,14 @@ public String load(String instanceId) { for (int i = 0; i < _tableUpdaterLocks.length; i++) { _tableUpdaterLocks[i] = new Object(); } + _lineageManager = lineageManager; } public PinotHelixResourceManager(ControllerConf controllerConf) { this(controllerConf.getZkStr(), controllerConf.getHelixClusterName(), controllerConf.getDataDir(), controllerConf.tenantIsolationEnabled(), controllerConf.getEnableBatchMessageMode(), - controllerConf.getHLCTablesAllowed(), controllerConf.getDeletedSegmentsRetentionInDays()); + controllerConf.getHLCTablesAllowed(), controllerConf.getDeletedSegmentsRetentionInDays(), + controllerConf.tieredSegmentAssignmentEnabled(), LineageManagerFactory.create(controllerConf)); } /** @@ -252,6 +276,8 @@ public PinotHelixResourceManager(ControllerConf controllerConf) { * as PARTICIPANT, * which would be put to lead controller resource and mess up the leadership assignment. Those places should use * SPECTATOR other than PARTICIPANT. + * TODO:For the backwards incompatible change, this is a + * reminder to clean up old Zk nodes when the controller starts up. */ public synchronized void start(HelixManager helixZkManager) { _helixZkManager = helixZkManager; @@ -286,15 +312,13 @@ public void onInstanceConfigChange(List instanceConfigs, Notific } catch (Exception e) { throw new RuntimeException("Caught exception while adding InstanceConfigChangeListener"); } - // Initialize TableCache HelixConfigScope helixConfigScope = new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.CLUSTER).forCluster(_helixClusterName).build(); Map configs = _helixAdmin.getConfig(helixConfigScope, - Arrays.asList(Helix.ENABLE_CASE_INSENSITIVE_KEY, Helix.DEPRECATED_ENABLE_CASE_INSENSITIVE_KEY)); - boolean caseInsensitive = - Boolean.parseBoolean(configs.get(Helix.ENABLE_CASE_INSENSITIVE_KEY)) || Boolean.parseBoolean( - configs.get(Helix.DEPRECATED_ENABLE_CASE_INSENSITIVE_KEY)); + Arrays.asList(Helix.ENABLE_CASE_INSENSITIVE_KEY)); + boolean caseInsensitive = Boolean.parseBoolean(configs.getOrDefault(Helix.ENABLE_CASE_INSENSITIVE_KEY, + Boolean.toString(Helix.DEFAULT_ENABLE_CASE_INSENSITIVE))); _tableCache = new TableCache(_propertyStore, caseInsensitive); } @@ -314,6 +338,15 @@ public String getHelixZkURL() { return _helixZkURL; } + /** + * Get the tablecache object. + * + * @return TableCache object + */ + public TableCache getTableCache() { + return _tableCache; + } + /** * Get the Helix cluster name. * @@ -359,6 +392,15 @@ public ZkHelixPropertyStore getPropertyStore() { return _propertyStore; } + /** + * Get the linage manager. + * + * @return lineage manager + */ + public LineageManager getLineageManager() { + return _lineageManager; + } + /** * Instance related APIs */ @@ -738,36 +780,26 @@ public String getActualTableName(String tableName) { * @return List of segment names */ public List getSegmentsFor(String tableNameWithType, boolean shouldExcludeReplacedSegments) { - IdealState idealState = getTableIdealState(tableNameWithType); - Preconditions.checkState(idealState != null, "Failed to find ideal state for table: %s", tableNameWithType); - List segments = new ArrayList<>(idealState.getPartitionSet()); - return shouldExcludeReplacedSegments ? excludeReplacedSegments(tableNameWithType, segments) : segments; + return getSegmentsFor(tableNameWithType, shouldExcludeReplacedSegments, Long.MIN_VALUE, Long.MAX_VALUE, false); } /** - * Returns the segments for the given table from the property store. This API is useful to track the orphan segments - * that are removed from the ideal state but not the property store. - */ - public List getSegmentsFromPropertyStore(String tableNameWithType) { - return ZKMetadataProvider.getSegments(_propertyStore, tableNameWithType); - } - - /** - * Returns the segments for the given table based on the start and end timestamp from the ideal state. + * Returns the segments for the given table from the ideal state. * - * @param tableNameWithType Table name with type suffix + * @param tableNameWithType Table name with type suffix + * @param shouldExcludeReplacedSegments whether to return the list of segments that doesn't contain replaced segments. * @param startTimestamp start timestamp in milliseconds (inclusive) * @param endTimestamp end timestamp in milliseconds (exclusive) * @param excludeOverlapping whether to exclude the segments overlapping with the timestamps + * @return List of segment names */ - public List getSegmentsForTableWithTimestamps(String tableNameWithType, long startTimestamp, - long endTimestamp, boolean excludeOverlapping) { + public List getSegmentsFor(String tableNameWithType, boolean shouldExcludeReplacedSegments, + long startTimestamp, long endTimestamp, boolean excludeOverlapping) { IdealState idealState = getTableIdealState(tableNameWithType); Preconditions.checkState(idealState != null, "Failed to find ideal state for table: %s", tableNameWithType); - Set segments = idealState.getPartitionSet(); - // If no start and end timestamp specified, just select all the segments. + List segments = new ArrayList<>(idealState.getPartitionSet()); if (startTimestamp == Long.MIN_VALUE && endTimestamp == Long.MAX_VALUE) { - return excludeReplacedSegments(tableNameWithType, new ArrayList<>(segments)); + return shouldExcludeReplacedSegments ? excludeReplacedSegments(tableNameWithType, segments) : segments; } else { List selectedSegments = new ArrayList<>(); List segmentZKMetadataList = getSegmentsZKMetadata(tableNameWithType); @@ -778,10 +810,19 @@ public List getSegmentsForTableWithTimestamps(String tableNameWithType, selectedSegments.add(segmentName); } } - return excludeReplacedSegments(tableNameWithType, selectedSegments); + return shouldExcludeReplacedSegments ? excludeReplacedSegments(tableNameWithType, selectedSegments) + : selectedSegments; } } + /** + * Returns the segments for the given table from the property store. This API is useful to track the orphan segments + * that are removed from the ideal state but not the property store. + */ + public List getSegmentsFromPropertyStore(String tableNameWithType) { + return ZKMetadataProvider.getSegments(_propertyStore, tableNameWithType); + } + /** * Given the list of segment names, exclude all the replaced segments which cannot be queried. * @param tableNameWithType table name with type @@ -843,6 +884,24 @@ public List getSegmentsZKMetadata(String tableNameWithType) { return ZKMetadataProvider.getSegmentsZKMetadata(_propertyStore, tableNameWithType); } + public Collection getLastLLCCompletedSegments(String tableNameWithType) { + Map partitionIdToLastLLCCompletedSegmentMap = new HashMap<>(); + for (SegmentZKMetadata segMetadata : getSegmentsZKMetadata(tableNameWithType)) { + if (SegmentName.isLowLevelConsumerSegmentName(segMetadata.getSegmentName()) + && segMetadata.getStatus() == CommonConstants.Segment.Realtime.Status.DONE) { + LLCSegmentName llcName = LLCSegmentName.of(segMetadata.getSegmentName()); + int partitionGroupId = llcName.getPartitionGroupId(); + int sequenceNumber = llcName.getSequenceNumber(); + String lastCompletedSegName = partitionIdToLastLLCCompletedSegmentMap.get(partitionGroupId); + if (lastCompletedSegName == null + || LLCSegmentName.of(lastCompletedSegName).getSequenceNumber() < sequenceNumber) { + partitionIdToLastLLCCompletedSegmentMap.put(partitionGroupId, segMetadata.getSegmentName()); + } + } + } + return partitionIdToLastLLCCompletedSegmentMap.values(); + } + public synchronized PinotResourceManagerResponse deleteSegments(String tableNameWithType, List segmentNames) { return deleteSegments(tableNameWithType, segmentNames, null); } @@ -1166,7 +1225,7 @@ public Set getAllServerTenantNames() { return tenantSet; } - private List getTagsForInstance(String instanceName) { + public List getTagsForInstance(String instanceName) { InstanceConfig config = _helixDataAccessor.getProperty(_keyBuilder.instanceConfig(instanceName)); return config.getTags(); } @@ -1329,7 +1388,7 @@ public void updateSegmentsZKTimeInterval(String tableNameWithType, DateTimeField } } - public void updateSchema(Schema schema, boolean reload) + public void updateSchema(Schema schema, boolean reload, boolean forceTableSchemaUpdate) throws SchemaNotFoundException, SchemaBackwardIncompatibleException, TableNotFoundException { String schemaName = schema.getSchemaName(); LOGGER.info("Updating schema: {} with reload: {}", schemaName, reload); @@ -1339,7 +1398,7 @@ public void updateSchema(Schema schema, boolean reload) throw new SchemaNotFoundException(String.format("Schema: %s does not exist", schemaName)); } - updateSchema(schema, oldSchema, false); + updateSchema(schema, oldSchema, forceTableSchemaUpdate); if (reload) { LOGGER.info("Reloading tables with name: {}", schemaName); @@ -1354,7 +1413,7 @@ public void updateSchema(Schema schema, boolean reload) * Helper method to update the schema, or throw SchemaBackwardIncompatibleException when the new schema is not * backward-compatible with the existing schema. */ - private void updateSchema(Schema schema, Schema oldSchema, boolean force) + private void updateSchema(Schema schema, Schema oldSchema, boolean forceTableSchemaUpdate) throws SchemaBackwardIncompatibleException { String schemaName = schema.getSchemaName(); schema.updateBooleanFieldsIfNeeded(oldSchema); @@ -1364,7 +1423,7 @@ private void updateSchema(Schema schema, Schema oldSchema, boolean force) } boolean isBackwardCompatible = schema.isBackwardCompatibleWith(oldSchema); if (!isBackwardCompatible) { - if (force) { + if (forceTableSchemaUpdate) { LOGGER.warn("Force updated schema: {} which is backward incompatible with the existing schema", oldSchema); } else { // TODO: Add the reason of the incompatibility @@ -1381,20 +1440,29 @@ private void updateSchema(Schema schema, Schema oldSchema, boolean force) * @param schema The schema to be deleted. * @return True on success, false otherwise. */ + @Deprecated public boolean deleteSchema(Schema schema) { if (schema != null) { - String schemaName = schema.getSchemaName(); - LOGGER.info("Deleting schema: {}", schemaName); - String propertyStorePath = ZKMetadataProvider.constructPropertyStorePathForSchema(schemaName); - if (_propertyStore.exists(propertyStorePath, AccessOption.PERSISTENT)) { - _propertyStore.remove(propertyStorePath, AccessOption.PERSISTENT); - LOGGER.info("Deleted schema: {}", schemaName); - return true; - } + deleteSchema(schema.getSchemaName()); } return false; } + /** + * Deletes the given schema. Returns {@code true} when schema exists, {@code false} when schema does not exist. + */ + public boolean deleteSchema(String schemaName) { + LOGGER.info("Deleting schema: {}", schemaName); + String propertyStorePath = ZKMetadataProvider.constructPropertyStorePathForSchema(schemaName); + if (_propertyStore.exists(propertyStorePath, AccessOption.PERSISTENT)) { + _propertyStore.remove(propertyStorePath, AccessOption.PERSISTENT); + LOGGER.info("Deleted schema: {}", schemaName); + return true; + } else { + return false; + } + } + @Nullable public Schema getSchema(String schemaName) { return ZKMetadataProvider.getSchema(_propertyStore, schemaName); @@ -1469,6 +1537,12 @@ public void addTable(TableConfig tableConfig) + " already exists. If this is unexpected, try deleting the table to remove all metadata associated" + " with it."); } + if (_helixAdmin.getResourceExternalView(_helixClusterName, tableNameWithType) != null) { + throw new TableAlreadyExistsException("External view for " + tableNameWithType + + " still exists. If the table is just deleted, please wait for the clean up to finish before recreating it. " + + "If the external view is not removed after a long time, try restarting the servers showing up in the " + + "external view"); + } validateTableTenantConfig(tableConfig); TableType tableType = tableConfig.getTableType(); @@ -1477,8 +1551,9 @@ public void addTable(TableConfig tableConfig) case OFFLINE: // now lets build an ideal state LOGGER.info("building empty ideal state for table : " + tableNameWithType); - final IdealState offlineIdealState = PinotTableIdealStateBuilder.buildEmptyIdealStateFor(tableNameWithType, - tableConfig.getReplication(), _enableBatchMessageMode); + IdealState offlineIdealState = + PinotTableIdealStateBuilder.buildEmptyIdealStateFor(tableNameWithType, tableConfig.getReplication(), + _enableBatchMessageMode); LOGGER.info("adding table via the admin"); try { @@ -1619,6 +1694,17 @@ void validateTableTenantConfig(TableConfig tableConfig) { "Failed to find instances with tag: " + tag + " for table: " + tableNameWithType); } } + // Check if serverTags as configured in tierConfigs are valid. + List tierConfigList = tableConfig.getTierConfigsList(); + if (CollectionUtils.isNotEmpty(tierConfigList)) { + for (TierConfig tierConfig : tierConfigList) { + if (getInstancesWithTag(tierConfig.getServerTag()).isEmpty()) { + throw new InvalidTableConfigException( + String.format("Failed to find instances with tag: %s as used by tier: %s for table: %s", + tierConfig.getServerTag(), tierConfig.getName(), tableNameWithType)); + } + } + } } public boolean setZKData(String path, ZNRecord record, int expectedVersion, int accessOption) { @@ -1733,10 +1819,10 @@ private void assignInstances(TableConfig tableConfig, boolean override) { } } + InstanceAssignmentDriver instanceAssignmentDriver = new InstanceAssignmentDriver(tableConfig); + List instanceConfigs = getAllHelixInstanceConfigs(); if (!instancePartitionsTypesToAssign.isEmpty()) { LOGGER.info("Assigning {} instances to table: {}", instancePartitionsTypesToAssign, tableNameWithType); - InstanceAssignmentDriver instanceAssignmentDriver = new InstanceAssignmentDriver(tableConfig); - List instanceConfigs = getAllHelixInstanceConfigs(); for (InstancePartitionsType instancePartitionsType : instancePartitionsTypesToAssign) { boolean hasPreConfiguredInstancePartitions = TableConfigUtils.hasPreConfiguredInstancePartitions(tableConfig, instancePartitionsType); @@ -1756,6 +1842,26 @@ private void assignInstances(TableConfig tableConfig, boolean override) { } } } + + // Process and persist tier config instancePartitions + if (CollectionUtils.isNotEmpty(tableConfig.getTierConfigsList()) + && tableConfig.getInstanceAssignmentConfigMap() != null) { + for (TierConfig tierConfig : tableConfig.getTierConfigsList()) { + if (tableConfig.getInstanceAssignmentConfigMap().containsKey(tierConfig.getName())) { + if (override || InstancePartitionsUtils.fetchInstancePartitions(_propertyStore, + InstancePartitionsUtils.getInstancePartitionsNameForTier(tableNameWithType, tierConfig.getName())) + == null) { + LOGGER.info("Calculating instance partitions for tier: {}, table : {}", tierConfig.getName(), + tableNameWithType); + InstancePartitions instancePartitions = + instanceAssignmentDriver.assignInstances(tierConfig.getName(), instanceConfigs, null, + tableConfig.getInstanceAssignmentConfigMap().get(tierConfig.getName())); + LOGGER.info("Persisting instance partitions: {}", instancePartitions); + InstancePartitionsUtils.persistInstancePartitions(_propertyStore, instancePartitions); + } + } + } + } } public void updateUserConfig(UserConfig userConfig) @@ -1908,6 +2014,10 @@ public void deleteOfflineTable(String tableName, @Nullable String retentionPerio InstancePartitionsUtils.removeInstancePartitions(_propertyStore, offlineTableName); LOGGER.info("Deleting table {}: Removed instance partitions", offlineTableName); + // Remove tier instance partitions + InstancePartitionsUtils.removeTierInstancePartitions(_propertyStore, offlineTableName); + LOGGER.info("Deleting table {}: Removed tier instance partitions", offlineTableName); + // Remove segment lineage SegmentLineageAccessHelper.deleteSegmentLineage(_propertyStore, offlineTableName); LOGGER.info("Deleting table {}: Removed segment lineage", offlineTableName); @@ -1967,6 +2077,9 @@ public void deleteRealtimeTable(String tableName, @Nullable String retentionPeri InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName)); LOGGER.info("Deleting table {}: Removed instance partitions", realtimeTableName); + InstancePartitionsUtils.removeTierInstancePartitions(_propertyStore, rawTableName); + LOGGER.info("Deleting table {}: Removed tier instance partitions", realtimeTableName); + // Remove segment lineage SegmentLineageAccessHelper.deleteSegmentLineage(_propertyStore, realtimeTableName); LOGGER.info("Deleting table {}: Removed segment lineage", realtimeTableName); @@ -2041,13 +2154,14 @@ private Set getAllInstancesForTable(String tableNameWithType) { } /** - * Returns the ZK metdata for the given jobId + * Returns the ZK metdata for the given jobId and jobType * @param jobId the id of the job + * @param jobType Job Path * @return Map representing the job's ZK properties */ @Nullable - public Map getControllerJobZKMetadata(String jobId) { - String controllerJobResourcePath = ZKMetadataProvider.constructPropertyStorePathForControllerJob(); + public Map getControllerJobZKMetadata(String jobId, ControllerJobType jobType) { + String controllerJobResourcePath = ZKMetadataProvider.constructPropertyStorePathForControllerJob(jobType); ZNRecord taskResourceZnRecord = _propertyStore.get(controllerJobResourcePath, null, AccessOption.PERSISTENT); if (taskResourceZnRecord != null) { return taskResourceZnRecord.getMapFields().get(jobId); @@ -2061,21 +2175,27 @@ public Map getControllerJobZKMetadata(String jobId) { * @return A Map of jobId to job properties */ public Map> getAllJobsForTable(String tableNameWithType, - @Nullable Set jobTypesToFilter) { - String jobsResourcePath = ZKMetadataProvider.constructPropertyStorePathForControllerJob(); - try { - ZNRecord tableJobsRecord = _propertyStore.get(jobsResourcePath, null, -1); - Map> controllerJobs = tableJobsRecord.getMapFields(); - return controllerJobs.entrySet().stream().filter( - job -> job.getValue().get(CommonConstants.ControllerJob.TABLE_NAME_WITH_TYPE).equals(tableNameWithType) - && (jobTypesToFilter == null || jobTypesToFilter.contains( - job.getValue().get(CommonConstants.ControllerJob.JOB_TYPE)))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } catch (ZkNoNodeException e) { - LOGGER.warn("Could not find controller job node for table : {}", tableNameWithType, e); + Set jobTypes) { + Map> controllerJobs = new HashMap<>(); + for (ControllerJobType jobType : jobTypes) { + String jobsResourcePath = ZKMetadataProvider.constructPropertyStorePathForControllerJob(jobType); + try { + ZNRecord znRecord = _propertyStore.get(jobsResourcePath, null, -1); + if (znRecord != null) { + Map> tableJobsRecord = znRecord.getMapFields(); + for (Map.Entry> tableEntry : tableJobsRecord.entrySet()) { + if (tableEntry.getValue().get(CommonConstants.ControllerJob.JOB_TYPE).equals(jobType.name()) + && tableEntry.getValue().get(CommonConstants.ControllerJob.TABLE_NAME_WITH_TYPE) + .equals(tableNameWithType)) { + controllerJobs.put(tableEntry.getKey(), tableEntry.getValue()); + } + } + } + } catch (ZkNoNodeException e) { + LOGGER.warn("Could not find controller job node for table : {} jobType: {}", tableNameWithType, jobType, e); + } } - - return Collections.emptyMap(); + return controllerJobs; } /** @@ -2095,7 +2215,8 @@ public boolean addNewReloadSegmentJob(String tableNameWithType, String segmentNa jobMetadata.put(CommonConstants.ControllerJob.SUBMISSION_TIME_MS, Long.toString(System.currentTimeMillis())); jobMetadata.put(CommonConstants.ControllerJob.MESSAGE_COUNT, Integer.toString(numMessagesSent)); jobMetadata.put(CommonConstants.ControllerJob.SEGMENT_RELOAD_JOB_SEGMENT_NAME, segmentName); - return addControllerJobToZK(jobId, jobMetadata); + return addControllerJobToZK(jobId, jobMetadata, + ZKMetadataProvider.constructPropertyStorePathForControllerJob(ControllerJobType.RELOAD_SEGMENT)); } public boolean addNewForceCommitJob(String tableNameWithType, String jobId, Set consumingSegmentsCommitted) @@ -2108,7 +2229,8 @@ public boolean addNewForceCommitJob(String tableNameWithType, String jobId, Set< jobMetadata.put(CommonConstants.ControllerJob.CONSUMING_SEGMENTS_FORCE_COMMITTED_LIST, JsonUtils.objectToString(consumingSegmentsCommitted)); - return addControllerJobToZK(jobId, jobMetadata); + return addControllerJobToZK(jobId, jobMetadata, + ZKMetadataProvider.constructPropertyStorePathForControllerJob(ControllerJobType.FORCE_COMMIT)); } /** @@ -2122,14 +2244,16 @@ public boolean addNewReloadAllSegmentsJob(String tableNameWithType, String jobId Map jobMetadata = new HashMap<>(); jobMetadata.put(CommonConstants.ControllerJob.JOB_ID, jobId); jobMetadata.put(CommonConstants.ControllerJob.TABLE_NAME_WITH_TYPE, tableNameWithType); - jobMetadata.put(CommonConstants.ControllerJob.JOB_TYPE, ControllerJobType.RELOAD_ALL_SEGMENTS.toString()); + jobMetadata.put(CommonConstants.ControllerJob.JOB_TYPE, ControllerJobType.RELOAD_SEGMENT.toString()); jobMetadata.put(CommonConstants.ControllerJob.SUBMISSION_TIME_MS, Long.toString(System.currentTimeMillis())); jobMetadata.put(CommonConstants.ControllerJob.MESSAGE_COUNT, Integer.toString(numberOfMessagesSent)); - return addControllerJobToZK(jobId, jobMetadata); + return addControllerJobToZK(jobId, jobMetadata, + ZKMetadataProvider.constructPropertyStorePathForControllerJob(ControllerJobType.RELOAD_SEGMENT)); } - private boolean addControllerJobToZK(String jobId, Map jobMetadata) { - String jobResourcePath = ZKMetadataProvider.constructPropertyStorePathForControllerJob(); + public boolean addControllerJobToZK(String jobId, Map jobMetadata, String jobResourcePath) { + Preconditions.checkState(jobMetadata.get(CommonConstants.ControllerJob.SUBMISSION_TIME_MS) != null, + "Submission Time in JobMetadata record not set. Cannot expire these records"); Stat stat = new Stat(); ZNRecord tableJobsZnRecord = _propertyStore.get(jobResourcePath, stat, AccessOption.PERSISTENT); if (tableJobsZnRecord != null) { @@ -2137,8 +2261,8 @@ private boolean addControllerJobToZK(String jobId, Map jobMetada tasks.put(jobId, jobMetadata); if (tasks.size() > CommonConstants.ControllerJob.MAXIMUM_CONTROLLER_JOBS_IN_ZK) { tasks = tasks.entrySet().stream().sorted((v1, v2) -> Long.compare( - Long.parseLong(v2.getValue().get(CommonConstants.ControllerJob.SUBMISSION_TIME_MS)), - Long.parseLong(v1.getValue().get(CommonConstants.ControllerJob.SUBMISSION_TIME_MS)))) + Long.parseLong(v2.getValue().get(CommonConstants.ControllerJob.SUBMISSION_TIME_MS)), + Long.parseLong(v1.getValue().get(CommonConstants.ControllerJob.SUBMISSION_TIME_MS)))) .collect(Collectors.toList()).subList(0, CommonConstants.ControllerJob.MAXIMUM_CONTROLLER_JOBS_IN_ZK) .stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @@ -2177,10 +2301,32 @@ public void assignTableSegment(String tableNameWithType, String segmentName) { try { TableConfig tableConfig = getTableConfig(tableNameWithType); Preconditions.checkState(tableConfig != null, "Failed to find table config for table: " + tableNameWithType); + Map instancePartitionsMap = fetchOrComputeInstancePartitions(tableNameWithType, tableConfig); + + // Initialize tier information only in case direct tier assignment is configured + if (_enableTieredSegmentAssignment && CollectionUtils.isNotEmpty(tableConfig.getTierConfigsList())) { + List sortedTiers = TierConfigUtils.getSortedTiersForStorageType(tableConfig.getTierConfigsList(), + TierFactory.PINOT_SERVER_STORAGE_TYPE, _helixZkManager); + + // Update segment tier to support direct assignment for multiple data directories + updateSegmentTargetTier(tableNameWithType, segmentName, sortedTiers); + + InstancePartitions tierInstancePartitions = + TierConfigUtils.getTieredInstancePartitionsForSegment(tableNameWithType, segmentName, sortedTiers, + _helixZkManager); + if (tierInstancePartitions != null && TableNameBuilder.isOfflineTableResource(tableNameWithType)) { + // Override instance partitions for offline table + LOGGER.info("Overriding with tiered instance partitions: {} for segment: {} of table: {}", + tierInstancePartitions, segmentName, tableNameWithType); + instancePartitionsMap = Collections.singletonMap(InstancePartitionsType.OFFLINE, tierInstancePartitions); + } + } + SegmentAssignment segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(_helixZkManager, tableConfig); synchronized (getTableUpdaterLock(tableNameWithType)) { + Map finalInstancePartitionsMap = instancePartitionsMap; HelixHelper.updateIdealState(_helixZkManager, tableNameWithType, idealState -> { assert idealState != null; Map> currentAssignment = idealState.getRecord().getMapFields(); @@ -2189,7 +2335,8 @@ public void assignTableSegment(String tableNameWithType, String segmentName) { tableNameWithType); } else { List assignedInstances = - segmentAssignment.assignSegment(segmentName, currentAssignment, instancePartitionsMap); + segmentAssignment.assignSegment(segmentName, currentAssignment, + finalInstancePartitionsMap); LOGGER.info("Assigning segment: {} to instances: {} for table: {}", segmentName, assignedInstances, tableNameWithType); currentAssignment.put(segmentName, @@ -2410,8 +2557,7 @@ public Pair reloadSegment(String tableNameWithType, String segm /** * Resets a segment. This operation invoke resetPartition via state transition message. */ - public void resetSegment(String tableNameWithType, String segmentName, @Nullable String targetInstance) - throws InterruptedException, TimeoutException { + public void resetSegment(String tableNameWithType, String segmentName, @Nullable String targetInstance) { IdealState idealState = getTableIdealState(tableNameWithType); Preconditions.checkState(idealState != null, "Could not find ideal state for table: %s", tableNameWithType); ExternalView externalView = getTableExternalView(tableNameWithType); @@ -2421,7 +2567,7 @@ public void resetSegment(String tableNameWithType, String segmentName, @Nullable for (String instance : instanceSet) { if (externalViewStateMap == null || SegmentStateModel.OFFLINE.equals(externalViewStateMap.get(instance))) { - LOGGER.info("Skipping reset for segment: {} of table: {} on instance: {}", segmentName, tableNameWithType, + LOGGER.info("Skipping resetting for segment: {} of table: {} on instance: {}", segmentName, tableNameWithType, instance); } else { LOGGER.info("Resetting segment: {} of table: {} on instance: {}", segmentName, tableNameWithType, instance); @@ -2431,10 +2577,10 @@ public void resetSegment(String tableNameWithType, String segmentName, @Nullable } /** - * Resets all segments of a table. This operation invoke resetPartition via state transition message. + * Resets all segments or segments with Error state of a table. This operation invoke resetPartition via state + * transition message. */ - public void resetAllSegments(String tableNameWithType, @Nullable String targetInstance) - throws InterruptedException, TimeoutException { + public void resetSegments(String tableNameWithType, @Nullable String targetInstance, boolean errorSegmentsOnly) { IdealState idealState = getTableIdealState(tableNameWithType); Preconditions.checkState(idealState != null, "Could not find ideal state for table: %s", tableNameWithType); ExternalView externalView = getTableExternalView(tableNameWithType); @@ -2447,15 +2593,26 @@ public void resetAllSegments(String tableNameWithType, @Nullable String targetIn Set instanceSet = parseInstanceSet(idealState, segmentName, targetInstance); Map externalViewStateMap = externalView.getStateMap(segmentName); for (String instance : instanceSet) { - if (externalViewStateMap == null || SegmentStateModel.OFFLINE.equals(externalViewStateMap.get(instance))) { - instanceToSkippedSegmentsMap.computeIfAbsent(instance, i -> new HashSet<>()).add(segmentName); + if (errorSegmentsOnly) { + if (externalViewStateMap != null && SegmentStateModel.ERROR.equals(externalViewStateMap.get(instance))) { + instanceToResetSegmentsMap.computeIfAbsent(instance, i -> new HashSet<>()).add(segmentName); + } } else { - instanceToResetSegmentsMap.computeIfAbsent(instance, i -> new HashSet<>()).add(segmentName); + if (externalViewStateMap == null || SegmentStateModel.OFFLINE.equals(externalViewStateMap.get(instance))) { + instanceToSkippedSegmentsMap.computeIfAbsent(instance, i -> new HashSet<>()).add(segmentName); + } else { + instanceToResetSegmentsMap.computeIfAbsent(instance, i -> new HashSet<>()).add(segmentName); + } } } } - LOGGER.info("Resetting all segments of table: {}", tableNameWithType); + if (instanceToResetSegmentsMap.isEmpty()) { + LOGGER.info("No segments to reset for table: {}", tableNameWithType); + return; + } + + LOGGER.info("Resetting segments: {} of table: {}", instanceToResetSegmentsMap, tableNameWithType); for (Map.Entry> entry : instanceToResetSegmentsMap.entrySet()) { resetPartitionAllState(entry.getKey(), tableNameWithType, entry.getValue()); } @@ -2866,6 +3023,15 @@ public TableConfig getTableConfig(String tableNameWithType) { return ZKMetadataProvider.getTableConfig(_propertyStore, tableNameWithType); } + /** + * Get all table configs. + * + * @return List of table configs. Empty list in case of tables configs does not exist. + */ + public List getAllTableConfigs() { + return ZKMetadataProvider.getAllTableConfigs(_propertyStore); + } + /** * Get the offline table config for the given table name. * @@ -2966,20 +3132,9 @@ public PinotResourceManagerResponse disableInstance(String instanceName) { * */ public PinotResourceManagerResponse dropInstance(String instanceName) { - // Check if the instance is live - if (_helixDataAccessor.getProperty(_keyBuilder.liveInstance(instanceName)) != null) { - return PinotResourceManagerResponse.failure("Instance " + instanceName + " is still live"); - } - - // Check if any ideal state includes the instance - for (String resource : getAllResources()) { - IdealState idealState = _helixAdmin.getResourceIdealState(_helixClusterName, resource); - for (String partition : idealState.getPartitionSet()) { - if (idealState.getInstanceSet(partition).contains(instanceName)) { - return PinotResourceManagerResponse.failure( - "Instance " + instanceName + " exists in ideal state for " + resource); - } - } + OperationValidationResponse check = instanceDropSafetyCheck(instanceName); + if (!check.isSafe()) { + return PinotResourceManagerResponse.failure(check.getIssueMessage(0)); } // Remove '/INSTANCES/' @@ -3001,6 +3156,31 @@ public PinotResourceManagerResponse dropInstance(String instanceName) { return PinotResourceManagerResponse.success("Instance " + instanceName + " dropped"); } + /** + * Utility to perform a safety check of the operation to drop an instance. + * If the resource is not safe to drop the utility lists all the possible reasons. + * @param instanceName Pinot instance name + * @return {@link OperationValidationResponse} + */ + public OperationValidationResponse instanceDropSafetyCheck(String instanceName) { + OperationValidationResponse response = new OperationValidationResponse().setInstanceName(instanceName); + // Check if the instance is live + if (_helixDataAccessor.getProperty(_keyBuilder.liveInstance(instanceName)) != null) { + response.putIssue(OperationValidationResponse.ErrorCode.IS_ALIVE, instanceName); + } + // Check if any ideal state includes the instance + getAllResources().forEach(resource -> { + IdealState idealState = _helixAdmin.getResourceIdealState(_helixClusterName, resource); + for (String partition : idealState.getPartitionSet()) { + if (idealState.getInstanceSet(partition).contains(instanceName)) { + response.putIssue(OperationValidationResponse.ErrorCode.CONTAINS_RESOURCE, instanceName, resource); + break; + } + } + }); + return response.setSafe(response.getIssues().isEmpty()); + } + /** * Toggle the status of an Instance between OFFLINE and ONLINE. * Keeps checking until ideal-state is successfully updated or times out. @@ -3085,13 +3265,85 @@ private PinotResourceManagerResponse enableInstance(String instanceName, boolean "Instance: " + instanceName + (enableInstance ? " enable" : " disable") + " failed, timeout"); } - public RebalanceResult rebalanceTable(String tableNameWithType, Configuration rebalanceConfig) + /** + * Entry point for table Rebalacing. + * @param tableNameWithType + * @param rebalanceConfig + * @param trackRebalanceProgress - Do we want to track rebalance progress stats + * @return RebalanceResult + * @throws TableNotFoundException + */ + public RebalanceResult rebalanceTable(String tableNameWithType, Configuration rebalanceConfig, + boolean trackRebalanceProgress) throws TableNotFoundException { TableConfig tableConfig = getTableConfig(tableNameWithType); if (tableConfig == null) { throw new TableNotFoundException("Failed to find table config for table: " + tableNameWithType); } - return new TableRebalancer(_helixZkManager).rebalance(tableConfig, rebalanceConfig); + String rebalanceJobId = rebalanceConfig.getString(RebalanceConfigConstants.JOB_ID); + Preconditions.checkState(rebalanceJobId != null, "RebalanceId not populated in the rebalanceConfig "); + if (rebalanceConfig.getBoolean(RebalanceConfigConstants.UPDATE_TARGET_TIER, + RebalanceConfigConstants.DEFAULT_UPDATE_TARGET_TIER)) { + updateTargetTier(rebalanceJobId, tableNameWithType, tableConfig); + } + ZkBasedTableRebalanceObserver zkBasedTableRebalanceObserver = null; + if (trackRebalanceProgress) { + zkBasedTableRebalanceObserver = new ZkBasedTableRebalanceObserver(tableNameWithType, rebalanceJobId, this); + } + TableRebalancer tableRebalancer = new TableRebalancer(_helixZkManager, zkBasedTableRebalanceObserver); + return tableRebalancer.rebalance(tableConfig, rebalanceConfig); + } + + /** + * Calculate the target tier the segment belongs to and set it in segment ZK metadata as goal state, which can be + * checked by servers when loading the segment to put it onto the target storage tier. + */ + @VisibleForTesting + void updateTargetTier(String rebalanceJobId, String tableNameWithType, TableConfig tableConfig) { + List tierCfgs = tableConfig.getTierConfigsList(); + List sortedTiers = tierCfgs == null ? Collections.emptyList() + : TierConfigUtils.getSortedTiers(tierCfgs, _helixZkManager); + LOGGER.info("For rebalanceId: {}, updating target tiers for segments of table: {} with tierConfigs: {}", + rebalanceJobId, tableNameWithType, sortedTiers); + for (String segmentName : getSegmentsFor(tableNameWithType, true)) { + updateSegmentTargetTier(tableNameWithType, segmentName, sortedTiers); + } + } + + private void updateSegmentTargetTier(String tableNameWithType, String segmentName, List sortedTiers) { + ZNRecord segmentMetadataZNRecord = getSegmentMetadataZnRecord(tableNameWithType, segmentName); + if (segmentMetadataZNRecord == null) { + LOGGER.debug("No ZK metadata for segment: {} of table: {}", segmentName, tableNameWithType); + return; + } + Tier targetTier = null; + for (Tier tier : sortedTiers) { + TierSegmentSelector tierSegmentSelector = tier.getSegmentSelector(); + if (tierSegmentSelector.selectSegment(tableNameWithType, segmentName)) { + targetTier = tier; + break; + } + } + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segmentMetadataZNRecord); + String targetTierName = null; + if (targetTier == null) { + if (segmentZKMetadata.getTier() == null) { + LOGGER.debug("Segment: {} of table: {} is already set to go to default tier", segmentName, tableNameWithType); + return; + } + LOGGER.info("Segment: {} of table: {} is put back on default tier", segmentName, tableNameWithType); + } else { + targetTierName = targetTier.getName(); + if (targetTierName.equals(segmentZKMetadata.getTier())) { + LOGGER.debug("Segment: {} of table: {} is already set to go to target tier: {}", segmentName, tableNameWithType, + targetTierName); + return; + } + LOGGER.info("Segment: {} of table: {} is put onto new tier: {}", segmentName, tableNameWithType, targetTierName); + } + // Update the tier in segment ZK metadata and write it back to ZK. + segmentZKMetadata.setTier(targetTierName); + updateZkMetadata(tableNameWithType, segmentZKMetadata, segmentMetadataZNRecord.getVersion()); } /** @@ -3225,52 +3477,59 @@ public List getExistingTableNamesWithType(String tableName, @Nullable Ta * @param segmentsFrom a list of segments to be merged * @param segmentsTo a list of merged segments * @param forceCleanup True for enabling the force segment cleanup + * @param customMap * @return Segment lineage entry id * * @throws InvalidConfigException */ public String startReplaceSegments(String tableNameWithType, List segmentsFrom, List segmentsTo, - boolean forceCleanup) { + boolean forceCleanup, Map customMap) { // Create a segment lineage entry id String segmentLineageEntryId = SegmentLineageUtils.generateLineageEntryId(); // Check that all the segments from 'segmentsFrom' exist in the table Set segmentsForTable = new HashSet<>(getSegmentsFor(tableNameWithType, true)); - Preconditions.checkArgument(segmentsForTable.containsAll(segmentsFrom), String.format( - "Not all segments from 'segmentsFrom' are available in the table. (tableName = '%s', segmentsFrom = '%s', " - + "segmentsTo = '%s', segmentsFromTable = '%s')", tableNameWithType, segmentsFrom, segmentsTo, - segmentsForTable)); + for (String segment : segmentsFrom) { + Preconditions.checkState(segmentsForTable.contains(segment), + "Segment: %s from 'segmentsFrom' does not exist in table: %s", segment, tableNameWithType); + } - // Check that all the segments from 'segmentTo' does not exist in the table. - Preconditions.checkArgument(Collections.disjoint(segmentsForTable, segmentsTo), String.format( - "Any segments from 'segmentsTo' should not be available in the table at this point. (tableName = '%s', " - + "segmentsFrom = '%s', segmentsTo = '%s', segmentsFromTable = '%s')", tableNameWithType, segmentsFrom, - segmentsTo, segmentsForTable)); + // Check that all the segments from 'segmentTo' does not exist in the table + for (String segment : segmentsTo) { + Preconditions.checkState(!segmentsForTable.contains(segment), "Segment: %s from 'segmentsTo' exists in table: %s", + segment, tableNameWithType); + } try { DEFAULT_RETRY_POLICY.attempt(() -> { // Fetch table config TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, tableNameWithType); - Preconditions.checkNotNull(tableConfig, "Table config is not available for table '%s'", tableNameWithType); + Preconditions.checkState(tableConfig != null, "Failed to find table config for table: %s", tableNameWithType); // Fetch the segment lineage metadata ZNRecord segmentLineageZNRecord = SegmentLineageAccessHelper.getSegmentLineageZNRecord(_propertyStore, tableNameWithType); SegmentLineage segmentLineage; - int expectedVersion = -1; + int expectedVersion; if (segmentLineageZNRecord == null) { segmentLineage = new SegmentLineage(tableNameWithType); + expectedVersion = -1; } else { segmentLineage = SegmentLineage.fromZNRecord(segmentLineageZNRecord); expectedVersion = segmentLineageZNRecord.getVersion(); } - // Check that the segment lineage entry id doesn't exists in the segment lineage - Preconditions.checkArgument(segmentLineage.getLineageEntry(segmentLineageEntryId) == null, - String.format("SegmentLineageEntryId (%s) already exists in the segment lineage.", segmentLineageEntryId)); + // Check that the segment lineage entry id doesn't exist in the segment lineage + Preconditions.checkState(segmentLineage.getLineageEntry(segmentLineageEntryId) == null, + "Entry id: %s already exists in the segment lineage for table: %s", segmentLineageEntryId, + tableNameWithType); List segmentsToCleanUp = new ArrayList<>(); - for (String entryId : segmentLineage.getLineageEntryIds()) { - LineageEntry lineageEntry = segmentLineage.getLineageEntry(entryId); + Iterator> entryIterator = + segmentLineage.getLineageEntries().entrySet().iterator(); + while (entryIterator.hasNext()) { + Map.Entry entry = entryIterator.next(); + String entryId = entry.getKey(); + LineageEntry lineageEntry = entry.getValue(); // If the lineage entry is in 'REVERTED' state, no need to go through the validation because we can regard // the entry as not existing. @@ -3301,14 +3560,30 @@ public String startReplaceSegments(String tableNameWithType, List segmen lineageEntry.getSegmentsFrom(), lineageEntry.getSegmentsTo()); // Delete the 'IN_PROGRESS' entry or update it to 'REVERTED' - List segmentsToForRevertedEntry = - deleteOrUpdateSegmentLineageEntryToReverted(tableNameWithType, segmentLineage, entryId, lineageEntry, - segmentsTo); + // Delete or update segmentsTo of the entry to revert to handle the case of rerunning the protocol: + // Initial state: + // Entry1: { segmentsFrom: [s1, s2], segmentsTo: [s3, s4], status: IN_PROGRESS} + // 1. Rerunning the protocol with s4 and s5, s4 should not be deleted to avoid race conditions of + // concurrent data pushes and deletions: + // Entry1: { segmentsFrom: [s1, s2], segmentsTo: [s3], status: REVERTED} + // Entry2: { segmentsFrom: [s1, s2], segmentsTo: [s4, s5], status: IN_PROGRESS} + // 2. Rerunning the protocol with s3 and s4, we can simply remove the 'IN_PROGRESS' entry: + // Entry2: { segmentsFrom: [s1, s2], segmentsTo: [s3, s4], status: IN_PROGRESS} + List segmentsToForEntryToRevert = new ArrayList<>(lineageEntry.getSegmentsTo()); + segmentsToForEntryToRevert.removeAll(segmentsTo); + if (segmentsToForEntryToRevert.isEmpty()) { + // Delete 'IN_PROGRESS' entry if the segmentsTo is empty + entryIterator.remove(); + } else { + // Update the lineage entry to 'REVERTED' + entry.setValue(new LineageEntry(lineageEntry.getSegmentsFrom(), segmentsToForEntryToRevert, + LineageEntryState.REVERTED, System.currentTimeMillis())); + } // Add segments for proactive clean-up. - segmentsToCleanUp.addAll(segmentsToForRevertedEntry); + segmentsToCleanUp.addAll(segmentsToForEntryToRevert); } else if (lineageEntry.getState() == LineageEntryState.COMPLETED - && IngestionConfigUtils.getBatchSegmentIngestionType(tableConfig).equalsIgnoreCase("REFRESH") + && "REFRESH".equalsIgnoreCase(IngestionConfigUtils.getBatchSegmentIngestionType(tableConfig)) && CollectionUtils.isEqualCollection(segmentsFrom, lineageEntry.getSegmentsTo())) { // This part of code assumes that we only allow at most 2 data snapshots at a time by proactively // deleting the older snapshots (for REFRESH tables). @@ -3330,16 +3605,27 @@ public String startReplaceSegments(String tableNameWithType, List segmen } } else { // Check that any segment from 'segmentsFrom' does not appear twice. - Preconditions.checkArgument(Collections.disjoint(lineageEntry.getSegmentsFrom(), segmentsFrom), - String.format("It is not allowed to replace segments that are already replaced. (tableName = %s, " - + "segmentsFrom from the existing lineage entry = %s, requested segmentsFrom = %s)", - tableNameWithType, lineageEntry.getSegmentsFrom(), segmentsFrom)); - - // Check that any segment from 'segmentTo' does not appear twice. - Preconditions.checkArgument(Collections.disjoint(lineageEntry.getSegmentsTo(), segmentsTo), String.format( - "It is not allowed to have the same segment name for segments in 'segmentsTo'. (tableName = %s, " - + "segmentsTo from the existing lineage entry = %s, requested segmentsTo = %s)", tableNameWithType, - lineageEntry.getSegmentsTo(), segmentsTo)); + if (!segmentsFrom.isEmpty()) { + Set segmentsFromInLineageEntry = new HashSet<>(lineageEntry.getSegmentsFrom()); + if (!segmentsFromInLineageEntry.isEmpty()) { + for (String segment : segmentsFrom) { + Preconditions.checkState(!segmentsFromInLineageEntry.contains(segment), + "Segment: %s from 'segmentsFrom' exists in table: %s, entry id: %s as 'segmentsFrom'" + + " (replacing a replaced segment)", segment, tableNameWithType, entryId); + } + } + } + + if (!segmentsTo.isEmpty()) { + Set segmentsToInLineageEntry = new HashSet<>(lineageEntry.getSegmentsTo()); + if (!segmentsToInLineageEntry.isEmpty()) { + for (String segment : segmentsTo) { + Preconditions.checkState(!segmentsToInLineageEntry.contains(segment), + "Segment: %s from 'segmentsTo' exists in table: %s, entry id: %s as 'segmentTo'" + + " (name conflict)", segment, tableNameWithType, entryId); + } + } + } } } @@ -3347,6 +3633,8 @@ public String startReplaceSegments(String tableNameWithType, List segmen segmentLineage.addLineageEntry(segmentLineageEntryId, new LineageEntry(segmentsFrom, segmentsTo, LineageEntryState.IN_PROGRESS, System.currentTimeMillis())); + _lineageManager + .updateLineageForStartReplaceSegments(tableConfig, segmentLineageEntryId, customMap, segmentLineage); // Write back to the lineage entry to the property store if (SegmentLineageAccessHelper.writeSegmentLineage(_propertyStore, segmentLineage, expectedVersion)) { // Trigger the proactive segment clean up if needed. Once the lineage is updated in the property store, it @@ -3357,6 +3645,7 @@ public String startReplaceSegments(String tableNameWithType, List segmen } return true; } else { + LOGGER.warn("Failed to write segment lineage for table: {}", tableNameWithType); return false; } }); @@ -3382,29 +3671,22 @@ public String startReplaceSegments(String tableNameWithType, List segmen * * Update is done with retry logic along with read-modify-write block for achieving atomic update of the lineage * metadata. - * * @param tableNameWithType * @param segmentLineageEntryId + * @param endReplaceSegmentsRequest */ - public void endReplaceSegments(String tableNameWithType, String segmentLineageEntryId) { + public void endReplaceSegments(String tableNameWithType, String segmentLineageEntryId, + @Nullable EndReplaceSegmentsRequest endReplaceSegmentsRequest) { try { DEFAULT_RETRY_POLICY.attempt(() -> { - // Fetch the segment lineage metadata - ZNRecord segmentLineageZNRecord = - SegmentLineageAccessHelper.getSegmentLineageZNRecord(_propertyStore, tableNameWithType); - SegmentLineage segmentLineage; - int expectedVersion = -1; - Preconditions.checkArgument(segmentLineageZNRecord != null, - String.format("Segment lineage does not exist. (tableNameWithType = '%s', segmentLineageEntryId = '%s')", - tableNameWithType, segmentLineageEntryId)); - segmentLineage = SegmentLineage.fromZNRecord(segmentLineageZNRecord); - expectedVersion = segmentLineageZNRecord.getVersion(); + // Fetch the segment lineage and look up the lineage entry based on the entry id. + SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, tableNameWithType); + Preconditions.checkState(segmentLineage != null, "Failed to find segment lineage for table: %s", + tableNameWithType); - // Look up the lineage entry based on the segment lineage entry id LineageEntry lineageEntry = segmentLineage.getLineageEntry(segmentLineageEntryId); - Preconditions.checkArgument(lineageEntry != null, - String.format("Invalid segment lineage entry id (tableName='%s', segmentLineageEntryId='%s')", - tableNameWithType, segmentLineageEntryId)); + Preconditions.checkState(lineageEntry != null, "Failed to find entry id: %s from segment lineage for table: %s", + segmentLineageEntryId, tableNameWithType); // NO-OPS if the entry is already 'COMPLETED', reject if the entry is 'REVERTED' if (lineageEntry.getState() == LineageEntryState.COMPLETED) { @@ -3420,36 +3702,47 @@ public void endReplaceSegments(String tableNameWithType, String segmentLineageEn throw new RuntimeException(errorMsg); } - // Check that all the segments from 'segmentsTo' exist in the table Set segmentsForTable = new HashSet<>(getSegmentsFor(tableNameWithType, false)); - Preconditions.checkArgument(segmentsForTable.containsAll(lineageEntry.getSegmentsTo()), String.format( - "Not all segments from 'segmentsTo' are available in the table. (tableName = '%s', segmentsTo = '%s', " - + "segmentsFromTable = '%s')", tableNameWithType, lineageEntry.getSegmentsTo(), segmentsForTable)); + List segmentsTo = lineageEntry.getSegmentsTo(); + if (endReplaceSegmentsRequest != null && !endReplaceSegmentsRequest.getSegmentsTo().isEmpty()) { + Set segmentsToInSet = new HashSet<>(segmentsTo); + // Check that the segments generated is a subset of the original segmentsTo list. + for (String segment : endReplaceSegmentsRequest.getSegmentsTo()) { + Preconditions.checkState(segmentsToInSet.contains(segment), + "Segment: %s from EndReplaceSegmentsRequest does not exist in original segmentsTo list " + + "used while starting segment replacement: %s", segment, tableNameWithType); + } + segmentsTo = endReplaceSegmentsRequest.getSegmentsTo(); + } + + // Check that all the segments from 'segmentsTo' exist in the table + for (String segment : segmentsTo) { + Preconditions.checkState(segmentsForTable.contains(segment), + "Segment: %s from 'segmentsTo' does not exist in table: %s", segment, tableNameWithType); + } // Check that all the segments from 'segmentsTo' become ONLINE in the external view - try { - waitForSegmentsBecomeOnline(tableNameWithType, new HashSet<>(lineageEntry.getSegmentsTo())); - } catch (TimeoutException e) { - LOGGER.warn(String.format( - "Time out while waiting segments become ONLINE. (tableNameWithType = %s, segmentsToCheck = %s)", - tableNameWithType, lineageEntry.getSegmentsTo()), e); + if (!waitForSegmentsBecomeOnline(tableNameWithType, segmentsTo)) { return false; } // Update lineage entry - LineageEntry newLineageEntry = - new LineageEntry(lineageEntry.getSegmentsFrom(), lineageEntry.getSegmentsTo(), LineageEntryState.COMPLETED, + LineageEntry lineageEntryToUpdate = + new LineageEntry(lineageEntry.getSegmentsFrom(), segmentsTo, LineageEntryState.COMPLETED, System.currentTimeMillis()); - segmentLineage.updateLineageEntry(segmentLineageEntryId, newLineageEntry); - // Write back to the lineage entry - if (SegmentLineageAccessHelper.writeSegmentLineage(_propertyStore, segmentLineage, expectedVersion)) { + TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, tableNameWithType); + Map customMap = + endReplaceSegmentsRequest == null ? null : endReplaceSegmentsRequest.getCustomMap(); + if (writeLineageEntryWithTightLoop(tableConfig, segmentLineageEntryId, lineageEntryToUpdate, lineageEntry, + _propertyStore, LineageUpdateType.END, customMap)) { // If the segment lineage metadata is successfully updated, we need to trigger brokers to rebuild the // routing table because it is possible that there has been no EV change but the routing result may be // different after updating the lineage entry. sendRoutingTableRebuildMessage(tableNameWithType); return true; } else { + LOGGER.warn("Failed to write segment lineage for table: {}", tableNameWithType); return false; } }); @@ -3482,74 +3775,92 @@ public SegmentLineage listSegmentLineage(String tableNameWithType) { * * Update is done with retry logic along with read-modify-write block for achieving atomic update of the lineage * metadata. - * * @param tableNameWithType * @param segmentLineageEntryId + * @param revertReplaceSegmentsRequest */ - public void revertReplaceSegments(String tableNameWithType, String segmentLineageEntryId, boolean forceRevert) { + public void revertReplaceSegments(String tableNameWithType, String segmentLineageEntryId, boolean forceRevert, + @Nullable RevertReplaceSegmentsRequest revertReplaceSegmentsRequest) { try { DEFAULT_RETRY_POLICY.attempt(() -> { // Fetch the segment lineage metadata ZNRecord segmentLineageZNRecord = SegmentLineageAccessHelper.getSegmentLineageZNRecord(_propertyStore, tableNameWithType); - Preconditions.checkArgument(segmentLineageZNRecord != null, - String.format("Segment lineage does not exist. (tableNameWithType = '%s', segmentLineageEntryId = '%s')", - tableNameWithType, segmentLineageEntryId)); + Preconditions.checkState(segmentLineageZNRecord != null, "Failed to find segment lineage for table: %s", + tableNameWithType); SegmentLineage segmentLineage = SegmentLineage.fromZNRecord(segmentLineageZNRecord); int expectedVersion = segmentLineageZNRecord.getVersion(); // Look up the lineage entry based on the segment lineage entry id LineageEntry lineageEntry = segmentLineage.getLineageEntry(segmentLineageEntryId); - Preconditions.checkArgument(lineageEntry != null, - String.format("Invalid segment lineage entry id (tableName='%s', segmentLineageEntryId='%s')", - tableNameWithType, segmentLineageEntryId)); - - // We do not allow to revert the lineage entry with 'REVERTED' state. For 'IN_PROGRESS", we only allow to - // revert when 'forceRevert' is set to true. - if (lineageEntry.getState() == LineageEntryState.REVERTED || ( - lineageEntry.getState() == LineageEntryState.IN_PROGRESS && !forceRevert)) { - String errorMsg = String.format( - "Lineage state is not valid. Cannot update the lineage entry to be 'REVERTED'. (tableNameWithType=%s, " - + "segmentLineageEntryId=%s, segmentLineageEntryState=%s, forceRevert=%s)", tableNameWithType, - segmentLineageEntryId, lineageEntry.getState(), forceRevert); - throw new RuntimeException(errorMsg); + Preconditions.checkState(lineageEntry != null, "Failed to find entry id: %s from segment lineage for table: %s", + segmentLineageEntryId, tableNameWithType); + + // Do not allow reverting 'REVERTED' lineage entry. Allow reverting 'IN_PROGRESS' lineage entry only when + // 'forceRevert' is set to true + Preconditions.checkState(lineageEntry.getState() != LineageEntryState.REVERTED && ( + lineageEntry.getState() != LineageEntryState.IN_PROGRESS || forceRevert), + "Lineage state is not valid. Cannot update the lineage entry to be 'REVERTED'. (tableNameWithType=%s, " + + "segmentLineageEntryId=%s, segmentLineageEntryState=%s, forceRevert=%s)", tableNameWithType, + segmentLineageEntryId, lineageEntry.getState(), forceRevert); + + // Do not allow reverting 'COMPLETED' lineage entry when 'segmentsFrom' do not exist in the ideal state + if (lineageEntry.getState() == LineageEntryState.COMPLETED) { + Set onlineSegments = getOnlineSegmentsFromIdealState(tableNameWithType, true); + for (String segment : lineageEntry.getSegmentsFrom()) { + Preconditions.checkState(onlineSegments.contains(segment), + "Segment: %s from 'segmentsFrom' does not exist in table: %s (reverting a deleted segment)", segment, + tableNameWithType); + } } - // We do not allow to revert the lineage entry which segments in 'segmentsTo' appear in 'segmentsFrom' of other + // Do not allow reverting the lineage entry which segments in 'segmentsTo' appear in 'segmentsFrom' of other // 'IN_PROGRESS' or 'COMPLETED' entries. E.g. we do not allow reverting entry1 because it will block reverting // entry2. // entry1: {(Seg_0, Seg_1, Seg_2) -> (Seg_3, Seg_4, Seg_5), COMPLETED} // entry2: {(Seg_3, Seg_4, Seg_5) -> (Seg_6, Seg_7, Seg_8), IN_PROGRESS/COMPLETED} // TODO: need to expand the logic to revert multiple entries in one go when we support > 2 data snapshots - for (String currentEntryId : segmentLineage.getLineageEntryIds()) { - LineageEntry currentLineageEntry = segmentLineage.getLineageEntry(currentEntryId); - if (currentLineageEntry.getState() == LineageEntryState.IN_PROGRESS - || currentLineageEntry.getState() == LineageEntryState.COMPLETED) { - Preconditions.checkArgument( - Collections.disjoint(lineageEntry.getSegmentsTo(), currentLineageEntry.getSegmentsFrom()), - String.format("Cannot revert lineage entry, found segments from 'segmentsTo' " - + "appear in 'segmentsFrom' of another lineage entry. (tableNameWithType='%s', " - + "segmentLineageEntryId='%s', segmentsTo = '%s', segmentLineageEntryId='%s' " - + "segmentsFrom = '%s')", tableNameWithType, segmentLineageEntryId, - lineageEntry.getSegmentsTo(), - currentEntryId, currentLineageEntry.getSegmentsFrom())); + List segmentsTo = lineageEntry.getSegmentsTo(); + if (!segmentsTo.isEmpty()) { + for (Map.Entry entry : segmentLineage.getLineageEntries().entrySet()) { + String currentEntryId = entry.getKey(); + LineageEntry currentLineageEntry = entry.getValue(); + if (currentLineageEntry.getState() == LineageEntryState.IN_PROGRESS + || currentLineageEntry.getState() == LineageEntryState.COMPLETED) { + Set segmentsFromInLineageEntry = new HashSet<>(currentLineageEntry.getSegmentsFrom()); + if (!segmentsFromInLineageEntry.isEmpty()) { + for (String segment : segmentsTo) { + Preconditions.checkState(!segmentsFromInLineageEntry.contains(segment), + "Segment: %s from 'segmentsTo' exists in table: %s, entry id: %s as 'segmentsTo'" + + " (reverting a merged segment)", segment, tableNameWithType, currentEntryId); + } + } + } } } // Update segment lineage entry to 'REVERTED' - updateSegmentLineageEntryToReverted(tableNameWithType, segmentLineage, segmentLineageEntryId, lineageEntry); + LineageEntry lineageEntryToUpdate = + new LineageEntry(lineageEntry.getSegmentsFrom(), lineageEntry.getSegmentsTo(), LineageEntryState.REVERTED, + System.currentTimeMillis()); - // Write back to the lineage entry - if (SegmentLineageAccessHelper.writeSegmentLineage(_propertyStore, segmentLineage, expectedVersion)) { + TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, tableNameWithType); + Map customMap = + revertReplaceSegmentsRequest == null ? null : revertReplaceSegmentsRequest.getCustomMap(); + if (writeLineageEntryWithTightLoop(tableConfig, segmentLineageEntryId, lineageEntryToUpdate, lineageEntry, + _propertyStore, LineageUpdateType.REVERT, customMap)) { // If the segment lineage metadata is successfully updated, we need to trigger brokers to rebuild the // routing table because it is possible that there has been no EV change but the routing result may be // different after updating the lineage entry. sendRoutingTableRebuildMessage(tableNameWithType); // Invoke the proactive clean-up for segments that we no longer needs - deleteSegments(tableNameWithType, lineageEntry.getSegmentsTo()); + if (!segmentsTo.isEmpty()) { + deleteSegments(tableNameWithType, segmentsTo); + } return true; } else { + LOGGER.warn("Failed to write segment lineage for table: {}", tableNameWithType); return false; } }); @@ -3565,63 +3876,84 @@ public void revertReplaceSegments(String tableNameWithType, String segmentLineag tableNameWithType, segmentLineageEntryId); } - private void updateSegmentLineageEntryToReverted(String tableNameWithType, SegmentLineage segmentLineage, - String segmentLineageEntryId, LineageEntry lineageEntry) { - if (lineageEntry.getState() == LineageEntryState.COMPLETED) { - // Check that all segments from 'segmentsFrom' are in ONLINE state in the external view. - Set onlineSegments = getOnlineSegmentsFromExternalView(tableNameWithType); - Preconditions.checkArgument(onlineSegments.containsAll(lineageEntry.getSegmentsFrom()), String.format( - "Failed to update the lineage to be 'REVERTED'. Not all segments from 'segmentFrom' are in ONLINE state " - + "in the external view. (tableName = '%s', segmentsFrom = '%s', onlineSegments = '%s'", - tableNameWithType, lineageEntry.getSegmentsFrom(), onlineSegments)); - } - - // Update lineage entry - segmentLineage.updateLineageEntry(segmentLineageEntryId, - new LineageEntry(lineageEntry.getSegmentsFrom(), lineageEntry.getSegmentsTo(), LineageEntryState.REVERTED, - System.currentTimeMillis())); - } - - private List deleteOrUpdateSegmentLineageEntryToReverted(String tableNameWithType, - SegmentLineage segmentLineage, String segmentLineageEntryId, LineageEntry lineageEntry, - List newSegments) { - // Delete or update segmentsTo of the entry to revert to handle the case of rerunning the protocol: - // Initial state: - // Entry1: { segmentsFrom: [s1, s2], segmentsTo: [s3, s4], status: IN_PROGRESS} - // 1. Rerunning the protocol with s4 and s5, s4 should not be deleted to avoid race conditions of concurrent data - // pushes and deletions: - // Entry1: { segmentsFrom: [s1, s2], segmentsTo: [s3], status: REVERTED} - // Entry2: { segmentsFrom: [s1, s2], segmentsTo: [s4, s5], status: IN_PROGRESS} - // 2. Rerunning the protocol with s3 and s4, we can simply remove the 'IN_PROGRESS' entry: - // Entry2: { segmentsFrom: [s1, s2], segmentsTo: [s3, s4], status: IN_PROGRESS} - List segmentsToForEntryToRevert = new ArrayList<>(lineageEntry.getSegmentsTo()); - segmentsToForEntryToRevert.removeAll(newSegments); - - if (segmentsToForEntryToRevert.isEmpty()) { - // Delete 'IN_PROGRESS' entry if the segmentsTo is empty - segmentLineage.deleteLineageEntry(segmentLineageEntryId); - } else { - // Update the lineage entry to 'REVERTED' - segmentLineage.updateLineageEntry(segmentLineageEntryId, - new LineageEntry(lineageEntry.getSegmentsFrom(), segmentsToForEntryToRevert, LineageEntryState.REVERTED, - System.currentTimeMillis())); + /** + * Update the lineage entry with the tight loop to increase the chance for successful ZK write. + * @param tableConfig table config + * @param lineageEntryId lineage entry id + * @param lineageEntryToUpdate lineage entry that needs to be updated + * @param lineageEntryToMatch lineage entry that needs to match with the entry from the newly fetched segment lineage. + * @param propertyStore property store + * @param lineageUpdateType + * @param customMap + */ + private boolean writeLineageEntryWithTightLoop(TableConfig tableConfig, String lineageEntryId, + LineageEntry lineageEntryToUpdate, LineageEntry lineageEntryToMatch, + ZkHelixPropertyStore propertyStore, + LineageUpdateType lineageUpdateType, Map customMap) { + for (int i = 0; i < DEFAULT_SEGMENT_LINEAGE_UPDATE_NUM_RETRY; i++) { + // Fetch the segment lineage + ZNRecord segmentLineageToUpdateZNRecord = + SegmentLineageAccessHelper.getSegmentLineageZNRecord(propertyStore, tableConfig.getTableName()); + int expectedVersion = segmentLineageToUpdateZNRecord.getVersion(); + SegmentLineage segmentLineageToUpdate = SegmentLineage.fromZNRecord(segmentLineageToUpdateZNRecord); + LineageEntry currentLineageEntry = segmentLineageToUpdate.getLineageEntry(lineageEntryId); + + // If the lineage entry doesn't match with the previously fetched lineage, we need to fail the request. + if (!currentLineageEntry.equals(lineageEntryToMatch)) { + String errorMsg = String.format( + "Aborting the to update lineage entry since we find that the entry has been modified for table %s, " + + "entry id: %s", tableConfig.getTableName(), lineageEntryId); + LOGGER.error(errorMsg); + throw new RuntimeException(errorMsg); + } + + // Update lineage entry + segmentLineageToUpdate.updateLineageEntry(lineageEntryId, lineageEntryToUpdate); + switch (lineageUpdateType) { + case START: + _lineageManager + .updateLineageForStartReplaceSegments(tableConfig, lineageEntryId, customMap, segmentLineageToUpdate); + break; + case END: + _lineageManager + .updateLineageForEndReplaceSegments(tableConfig, lineageEntryId, customMap, segmentLineageToUpdate); + break; + case REVERT: + _lineageManager + .updateLineageForRevertReplaceSegments(tableConfig, lineageEntryId, customMap, segmentLineageToUpdate); + break; + default: + } + + // Write back to the lineage entry + if (SegmentLineageAccessHelper.writeSegmentLineage(propertyStore, segmentLineageToUpdate, expectedVersion)) { + return true; + } } - return segmentsToForEntryToRevert; + return false; } - private void waitForSegmentsBecomeOnline(String tableNameWithType, Set segmentsToCheck) - throws InterruptedException, TimeoutException { + private boolean waitForSegmentsBecomeOnline(String tableNameWithType, List segmentsToCheck) + throws InterruptedException { long endTimeMs = System.currentTimeMillis() + EXTERNAL_VIEW_ONLINE_SEGMENTS_MAX_WAIT_MS; + String segmentNotOnline; do { + segmentNotOnline = null; Set onlineSegments = getOnlineSegmentsFromExternalView(tableNameWithType); - if (onlineSegments.containsAll(segmentsToCheck)) { - return; + for (String segment : segmentsToCheck) { + if (!onlineSegments.contains(segment)) { + segmentNotOnline = segment; + break; + } + } + if (segmentNotOnline == null) { + return true; } Thread.sleep(EXTERNAL_VIEW_CHECK_INTERVAL_MS); } while (System.currentTimeMillis() < endTimeMs); - throw new TimeoutException( - String.format("Time out while waiting segments become ONLINE. (tableNameWithType = %s, segmentsToCheck = %s)", - tableNameWithType, segmentsToCheck)); + LOGGER.warn("Timed out while waiting for segment: {} to become ONLINE for table: {}", segmentNotOnline, + tableNameWithType); + return false; } public Set getOnlineSegmentsFromIdealState(String tableNameWithType, boolean includeConsuming) { @@ -3805,6 +4137,31 @@ public PeriodicTaskInvocationResponse invokeControllerPeriodicTask(String tableN return new PeriodicTaskInvocationResponse(periodicTaskRequestId, messageCount > 0); } + /** + * Construct a map of all the tags and their respective minimum instance requirements. + * The minimum instance requirement is computed by + * - for BROKER tenant tag set it to 1 if it hosts any table else set it to 0 + * - for SERVER tenant tag iterate over all the tables of that tenant and find the maximum table replication. + * - for rest of the tags just set it to 0 + * @return map of tags and their minimum instance requirements + */ + public Map minimumInstancesRequiredForTags() { + Map tagMinInstanceMap = new HashMap<>(); + for (InstanceConfig instanceConfig : getAllHelixInstanceConfigs()) { + for (String tag : instanceConfig.getTags()) { + tagMinInstanceMap.put(tag, 0); + } + } + for (TableConfig tableConfig : getAllTableConfigs()) { + String tag = TagNameUtils.getServerTagForTenant(tableConfig.getTenantConfig().getServer(), + tableConfig.getTableType()); + tagMinInstanceMap.put(tag, Math.max(tagMinInstanceMap.getOrDefault(tag, 0), tableConfig.getReplication())); + String brokerTag = TagNameUtils.getBrokerTagForTenant(tableConfig.getTenantConfig().getBroker()); + tagMinInstanceMap.put(brokerTag, 1); + } + return tagMinInstanceMap; + } + /* * Uncomment and use for testing on a real cluster public static void main(String[] args) throws Exception { diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/SegmentDeletionManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/SegmentDeletionManager.java index ff74bea7086..ebde702b09e 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/SegmentDeletionManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/SegmentDeletionManager.java @@ -44,6 +44,9 @@ import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.utils.SegmentName; import org.apache.pinot.common.utils.URIUtils; +import org.apache.pinot.controller.LeadControllerManager; +import org.apache.pinot.core.segment.processing.lifecycle.PinotSegmentLifecycleEventListenerManager; +import org.apache.pinot.core.segment.processing.lifecycle.impl.SegmentDeletionEventDetails; import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.filesystem.PinotFS; @@ -165,6 +168,10 @@ protected synchronized void deleteSegmentFromPropertyStoreAndLocal(String tableN propStorePathList.add(segmentPropertyStorePath); } + // Notify all active listeners here + PinotSegmentLifecycleEventListenerManager.getInstance() + .notifyListeners(new SegmentDeletionEventDetails(tableName, segmentsToDelete)); + boolean[] deleteSuccessful = _propertyStore.remove(propStorePathList, AccessOption.PERSISTENT); List propStoreFailedSegs = new ArrayList<>(segmentsToDelete.size()); for (int i = 0; i < deleteSuccessful.length; i++) { @@ -264,7 +271,7 @@ protected void removeSegmentFromStore(String tableNameWithType, String segmentId /** * Removes aged deleted segments from the deleted directory */ - public void removeAgedDeletedSegments() { + public void removeAgedDeletedSegments(LeadControllerManager leadControllerManager) { if (_dataDir != null) { URI deletedDirURI = URIUtils.getUri(_dataDir, DELETED_SEGMENTS); PinotFS pinotFS = PinotFSFactory.create(deletedDirURI.getScheme()); @@ -287,26 +294,29 @@ public void removeAgedDeletedSegments() { } for (String tableNameDir : tableNameDirs) { - URI tableNameURI = URIUtils.getUri(tableNameDir); - // Get files that are aged - final String[] targetFiles = pinotFS.listFiles(tableNameURI, false); - int numFilesDeleted = 0; - for (String targetFile : targetFiles) { - URI targetURI = URIUtils.getUri(targetFile); - long deletionTimeMs = getDeletionTimeMsFromFile(targetFile, pinotFS.lastModified(targetURI)); - if (System.currentTimeMillis() >= deletionTimeMs) { - if (!pinotFS.delete(targetURI, true)) { - LOGGER.warn("Cannot remove file {} from deleted directory.", targetURI.toString()); - } else { - numFilesDeleted++; + String tableName = URIUtils.getLastPart(tableNameDir); + if (leadControllerManager.isLeaderForTable(tableName)) { + URI tableNameURI = URIUtils.getUri(tableNameDir); + // Get files that are aged + final String[] targetFiles = pinotFS.listFiles(tableNameURI, false); + int numFilesDeleted = 0; + for (String targetFile : targetFiles) { + URI targetURI = URIUtils.getUri(targetFile); + long deletionTimeMs = getDeletionTimeMsFromFile(targetFile, pinotFS.lastModified(targetURI)); + if (System.currentTimeMillis() >= deletionTimeMs) { + if (!pinotFS.delete(targetURI, true)) { + LOGGER.warn("Cannot remove file {} from deleted directory.", targetURI); + } else { + numFilesDeleted++; + } } } - } - if (numFilesDeleted == targetFiles.length) { - // Delete directory if it's empty - if (!pinotFS.delete(tableNameURI, false)) { - LOGGER.warn("The directory {} cannot be removed.", tableNameDir); + if (numFilesDeleted == targetFiles.length) { + // Delete directory if it's empty + if (!pinotFS.delete(tableNameURI, false)) { + LOGGER.warn("The directory {} cannot be removed.", tableNameDir); + } } } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java index ba73f7bd6dc..7a5c9010293 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java @@ -25,6 +25,7 @@ import org.apache.helix.model.InstanceConfig; import org.apache.pinot.common.assignment.InstanceAssignmentConfigUtils; import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.assignment.InstancePartitionsUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.assignment.InstanceAssignmentConfig; import org.apache.pinot.spi.config.table.assignment.InstanceConstraintConfig; @@ -55,15 +56,31 @@ public InstanceAssignmentDriver(TableConfig tableConfig) { public InstancePartitions assignInstances(InstancePartitionsType instancePartitionsType, List instanceConfigs, @Nullable InstancePartitions existingInstancePartitions) { String tableNameWithType = _tableConfig.getTableName(); - LOGGER.info("Starting {} instance assignment for table: {}", instancePartitionsType, tableNameWithType); - InstanceAssignmentConfig assignmentConfig = InstanceAssignmentConfigUtils.getInstanceAssignmentConfig(_tableConfig, instancePartitionsType); + return getInstancePartitions( + instancePartitionsType.getInstancePartitionsName(TableNameBuilder.extractRawTableName(tableNameWithType)), + assignmentConfig, instanceConfigs, existingInstancePartitions); + } + + public InstancePartitions assignInstances(String tierName, List instanceConfigs, + @Nullable InstancePartitions existingInstancePartitions, InstanceAssignmentConfig instanceAssignmentConfig) { + return getInstancePartitions( + InstancePartitionsUtils.getInstancePartitionsNameForTier(_tableConfig.getTableName(), tierName), + instanceAssignmentConfig, instanceConfigs, existingInstancePartitions); + } + + private InstancePartitions getInstancePartitions(String instancePartitionsName, + InstanceAssignmentConfig instanceAssignmentConfig, List instanceConfigs, + @Nullable InstancePartitions existingInstancePartitions) { + String tableNameWithType = _tableConfig.getTableName(); + LOGGER.info("Starting {} instance assignment for table {}", instancePartitionsName, tableNameWithType); + InstanceTagPoolSelector tagPoolSelector = - new InstanceTagPoolSelector(assignmentConfig.getTagPoolConfig(), tableNameWithType); + new InstanceTagPoolSelector(instanceAssignmentConfig.getTagPoolConfig(), tableNameWithType); Map> poolToInstanceConfigsMap = tagPoolSelector.selectInstances(instanceConfigs); - InstanceConstraintConfig constraintConfig = assignmentConfig.getConstraintConfig(); + InstanceConstraintConfig constraintConfig = instanceAssignmentConfig.getConstraintConfig(); List constraintAppliers = new ArrayList<>(); if (constraintConfig == null) { LOGGER.info("No instance constraint is configured, using default hash-based-rotate instance constraint"); @@ -75,10 +92,9 @@ public InstancePartitions assignInstances(InstancePartitionsType instancePartiti } InstancePartitionSelector instancePartitionSelector = - InstancePartitionSelectorFactory.getInstance(assignmentConfig.getPartitionSelector(), - assignmentConfig.getReplicaGroupPartitionConfig(), tableNameWithType, existingInstancePartitions); - InstancePartitions instancePartitions = new InstancePartitions( - instancePartitionsType.getInstancePartitionsName(TableNameBuilder.extractRawTableName(tableNameWithType))); + InstancePartitionSelectorFactory.getInstance(instanceAssignmentConfig.getPartitionSelector(), + instanceAssignmentConfig.getReplicaGroupPartitionConfig(), tableNameWithType, existingInstancePartitions); + InstancePartitions instancePartitions = new InstancePartitions(instancePartitionsName); instancePartitionSelector.selectInstances(poolToInstanceConfigsMap, instancePartitions); return instancePartitions; } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/BaseSegmentAssignment.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/BaseSegmentAssignment.java index fc66bce53e2..b89fd497be9 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/BaseSegmentAssignment.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/BaseSegmentAssignment.java @@ -28,9 +28,9 @@ import org.apache.helix.HelixManager; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.tier.Tier; +import org.apache.pinot.common.utils.config.TableConfigUtils; import org.apache.pinot.controller.helix.core.assignment.segment.strategy.SegmentAssignmentStrategy; import org.apache.pinot.controller.helix.core.assignment.segment.strategy.SegmentAssignmentStrategyFactory; -import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; @@ -76,9 +76,7 @@ public void init(HelixManager helixManager, TableConfig tableConfig) { _tableNameWithType = tableConfig.getTableName(); _tableConfig = tableConfig; _replication = tableConfig.getReplication(); - ReplicaGroupStrategyConfig replicaGroupStrategyConfig = - tableConfig.getValidationConfig().getReplicaGroupStrategyConfig(); - _partitionColumn = replicaGroupStrategyConfig != null ? replicaGroupStrategyConfig.getPartitionColumn() : null; + _partitionColumn = TableConfigUtils.getPartitionColumn(_tableConfig); if (_partitionColumn == null) { _logger.info("Initialized with replication: {} without partition column for table: {} ", _replication, diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategy.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategy.java index d5a4d0e027b..3ff08072200 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategy.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategy.java @@ -27,8 +27,8 @@ import java.util.TreeMap; import org.apache.helix.HelixManager; import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.utils.config.TableConfigUtils; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils; -import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -54,9 +54,7 @@ public void init(HelixManager helixManager, TableConfig tableConfig) { SegmentsValidationAndRetentionConfig validationAndRetentionConfig = tableConfig.getValidationConfig(); Preconditions.checkState(validationAndRetentionConfig != null, "Validation Config is null"); _replication = tableConfig.getReplication(); - ReplicaGroupStrategyConfig replicaGroupStrategyConfig = - validationAndRetentionConfig.getReplicaGroupStrategyConfig(); - _partitionColumn = replicaGroupStrategyConfig != null ? replicaGroupStrategyConfig.getPartitionColumn() : null; + _partitionColumn = TableConfigUtils.getPartitionColumn(_tableConfig); if (_partitionColumn == null) { LOGGER.info("Initialized ReplicaGroupSegmentAssignmentStrategy " + "with replication: {} without partition column for table: {} ", _replication, _tableName); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/DefaultLineageManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/DefaultLineageManager.java new file mode 100644 index 00000000000..225c65b7178 --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/DefaultLineageManager.java @@ -0,0 +1,130 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.lineage; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.apache.pinot.common.lineage.LineageEntry; +import org.apache.pinot.common.lineage.LineageEntryState; +import org.apache.pinot.common.lineage.SegmentLineage; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.utils.IngestionConfigUtils; + + +public class DefaultLineageManager implements LineageManager { + private static final long REPLACED_SEGMENTS_RETENTION_IN_MILLIS = TimeUnit.DAYS.toMillis(1L); // 1 day + private static final long LINEAGE_ENTRY_CLEANUP_RETENTION_IN_MILLIS = TimeUnit.DAYS.toMillis(1L); // 1 day + + protected ControllerConf _controllerConf; + + public DefaultLineageManager(ControllerConf controllerConf) { + _controllerConf = controllerConf; + } + + @Override + public void updateLineageForStartReplaceSegments(TableConfig tableConfig, String lineageEntryId, + Map customMap, SegmentLineage lineage) { + } + + @Override + public void updateLineageForEndReplaceSegments(TableConfig tableConfig, String lineageEntryId, + Map customMap, SegmentLineage lineage) { + } + + @Override + public void updateLineageForRevertReplaceSegments(TableConfig tableConfig, String lineageEntryId, + Map customMap, SegmentLineage lineage) { + } + + /** + * This method: + * 1. Update lineage metadata by removing lineage entries + * 2. Find segments that need to be deleted + */ + @Override + public void updateLineageForRetention(TableConfig tableConfig, SegmentLineage lineage, List allSegments, + List segmentsToDelete, Set consumingSegments) { + // 1. The original segments can be deleted once the merged segments are successfully uploaded + // 2. The zombie lineage entry & merged segments should be deleted if the segment replacement failed in + // the middle + Set segmentsForTable = new HashSet<>(allSegments); + Iterator lineageEntryIterator = lineage.getLineageEntries().values().iterator(); + while (lineageEntryIterator.hasNext()) { + LineageEntry lineageEntry = lineageEntryIterator.next(); + if (lineageEntry.getState() == LineageEntryState.COMPLETED) { + Set sourceSegments = new HashSet<>(lineageEntry.getSegmentsFrom()); + sourceSegments.retainAll(segmentsForTable); + if (sourceSegments.isEmpty()) { + // If the lineage state is 'COMPLETED' and segmentFrom are removed, it is safe clean up the lineage entry + lineageEntryIterator.remove(); + } else { + // If the lineage state is 'COMPLETED' and we already preserved the original segments for the required + // retention, it is safe to delete all segments from 'segmentsFrom' + if (shouldDeleteReplacedSegments(tableConfig, lineageEntry)) { + segmentsToDelete.addAll(sourceSegments); + } + } + } else if (lineageEntry.getState() == LineageEntryState.REVERTED || ( + lineageEntry.getState() == LineageEntryState.IN_PROGRESS && lineageEntry.getTimestamp() + < System.currentTimeMillis() - LINEAGE_ENTRY_CLEANUP_RETENTION_IN_MILLIS)) { + // If the lineage state is 'IN_PROGRESS' or 'REVERTED', we need to clean up the zombie lineage + // entry and its segments + Set destinationSegments = new HashSet<>(lineageEntry.getSegmentsTo()); + destinationSegments.retainAll(segmentsForTable); + if (destinationSegments.isEmpty()) { + // If the lineage state is 'IN_PROGRESS or REVERTED' and source segments are already removed, it is safe + // to clean up the lineage entry. Deleting lineage will allow the task scheduler to re-schedule the source + // segments to be merged again. + lineageEntryIterator.remove(); + } else { + // If the lineage state is 'IN_PROGRESS', it is safe to delete all segments from 'segmentsTo' + segmentsToDelete.addAll(destinationSegments); + } + } + } + } + + + /** + * Helper function to decide whether we should delete segmentsFrom (replaced segments) given a lineage entry. + * + * The replaced segments are safe to delete if the following conditions are all satisfied + * 1) Table is "APPEND" + * 2) It has been more than 24 hours since the lineage entry became "COMPLETED" state. + * + * @param tableConfig a table config + * @param lineageEntry lineage entry + * @return True if we can safely delete the replaced segments. False otherwise. + */ + private boolean shouldDeleteReplacedSegments(TableConfig tableConfig, LineageEntry lineageEntry) { + // TODO: Currently, we preserve the replaced segments for 1 day for REFRESH tables only. Once we support + // data rollback for APPEND tables, we should remove this check. + String batchSegmentIngestionType = IngestionConfigUtils.getBatchSegmentIngestionType(tableConfig); + if (!batchSegmentIngestionType.equalsIgnoreCase("REFRESH") + || lineageEntry.getTimestamp() < System.currentTimeMillis() - REPLACED_SEGMENTS_RETENTION_IN_MILLIS) { + return true; + } + return false; + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/LineageManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/LineageManager.java new file mode 100644 index 00000000000..c829ef243b8 --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/LineageManager.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.lineage; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.lineage.SegmentLineage; +import org.apache.pinot.spi.config.table.TableConfig; + + +/** + * Interface to update lineage metadata based on custom inputs and garbage collect lineage entries + */ +public interface LineageManager { + + /** + * Update lineage based on customMap for start replace calls + * @param tableConfig + * @param lineageEntryId + * @param customMap + * @param lineage + */ + void updateLineageForStartReplaceSegments(TableConfig tableConfig, String lineageEntryId, + Map customMap, SegmentLineage lineage); + + /** + * Update lineage based on customMap for end replace calls + * @param tableConfig + * @param lineageEntryId + * @param customMap + * @param lineage + */ + void updateLineageForEndReplaceSegments(TableConfig tableConfig, String lineageEntryId, Map customMap, + SegmentLineage lineage); + + /** + * Update lineage based on customMap for revert replace calls + * @param tableConfig + * @param lineageEntryId + * @param customMap + * @param lineage + */ + void updateLineageForRevertReplaceSegments(TableConfig tableConfig, String lineageEntryId, + Map customMap, SegmentLineage lineage); + + /** + * Update lineage for retention purposes + * @param tableConfig + * @param lineage + * @param allSegments + * @param segmentsToDelete + * @param consumingSegments + */ + void updateLineageForRetention(TableConfig tableConfig, SegmentLineage lineage, List allSegments, + List segmentsToDelete, Set consumingSegments); +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/LineageManagerFactory.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/LineageManagerFactory.java new file mode 100644 index 00000000000..1a07dfefacd --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/lineage/LineageManagerFactory.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.lineage; + +import org.apache.pinot.controller.ControllerConf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Factory class to create {@link LineageManager} based on controller configs + */ +public class LineageManagerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(LineageManagerFactory.class); + + private LineageManagerFactory() { + } + + public static LineageManager create(ControllerConf controllerConf) { + String lineageManagerClassName = controllerConf.getLineageManagerClass(); + try { + return (LineageManager) Class.forName(lineageManagerClassName).getConstructor(ControllerConf.class) + .newInstance(controllerConf); + } catch (Exception e) { + LOGGER.error("LineageManager not found: {}", lineageManagerClassName); + throw new RuntimeException(e); + } + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/ClusterInfoAccessor.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/ClusterInfoAccessor.java index bcb14919887..5551c04d7cc 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/ClusterInfoAccessor.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/ClusterInfoAccessor.java @@ -21,7 +21,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; import javax.annotation.Nullable; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.helix.model.HelixConfigScope; import org.apache.helix.model.builder.HelixConfigScopeBuilder; import org.apache.helix.task.TaskState; @@ -51,15 +53,20 @@ public class ClusterInfoAccessor { private final ControllerConf _controllerConf; private final ControllerMetrics _controllerMetrics; private final LeadControllerManager _leadControllerManager; + private final Executor _executor; + private final MultiThreadedHttpConnectionManager _connectionManager; public ClusterInfoAccessor(PinotHelixResourceManager pinotHelixResourceManager, PinotHelixTaskResourceManager pinotHelixTaskResourceManager, ControllerConf controllerConf, - ControllerMetrics controllerMetrics, LeadControllerManager leadControllerManager) { + ControllerMetrics controllerMetrics, LeadControllerManager leadControllerManager, Executor executor, + MultiThreadedHttpConnectionManager connectionManager) { _pinotHelixResourceManager = pinotHelixResourceManager; _pinotHelixTaskResourceManager = pinotHelixTaskResourceManager; _controllerConf = controllerConf; _controllerMetrics = controllerMetrics; _leadControllerManager = leadControllerManager; + _executor = executor; + _connectionManager = connectionManager; } /** @@ -94,6 +101,20 @@ public List getSegmentsZKMetadata(String tableNameWithType) { return ZKMetadataProvider.getSegmentsZKMetadata(_pinotHelixResourceManager.getPropertyStore(), tableNameWithType); } + /** + * Get shared executor + */ + public Executor getExecutor() { + return _executor; + } + + /** + * Get shared connection manager + */ + public MultiThreadedHttpConnectionManager getConnectionManager() { + return _connectionManager; + } + /** * Fetches the ZNRecord under MINION_TASK_METADATA/${tableNameWithType}/${taskType} for the given * taskType and tableNameWithType @@ -114,8 +135,8 @@ public ZNRecord getMinionTaskMetadataZNRecord(String taskType, String tableNameW */ @Nullable public SegmentLineage getSegmentLineage(String tableNameWithType) { - return SegmentLineageAccessHelper - .getSegmentLineage(_pinotHelixResourceManager.getPropertyStore(), tableNameWithType); + return SegmentLineageAccessHelper.getSegmentLineage(_pinotHelixResourceManager.getPropertyStore(), + tableNameWithType); } /** @@ -127,8 +148,8 @@ public SegmentLineage getSegmentLineage(String tableNameWithType) { * @param expectedVersion The expected version of data to be overwritten. Set to -1 to override version check. */ public void setMinionTaskMetadata(BaseTaskMetadata taskMetadata, String taskType, int expectedVersion) { - MinionTaskMetadataUtils - .persistTaskMetadata(_pinotHelixResourceManager.getPropertyStore(), taskType, taskMetadata, expectedVersion); + MinionTaskMetadataUtils.persistTaskMetadata(_pinotHelixResourceManager.getPropertyStore(), taskType, taskMetadata, + expectedVersion); } /** @@ -175,8 +196,9 @@ public String getDataDir() { * @return cluster config */ public String getClusterConfig(String configName) { - HelixConfigScope helixConfigScope = new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.CLUSTER) - .forCluster(_pinotHelixResourceManager.getHelixClusterName()).build(); + HelixConfigScope helixConfigScope = + new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.CLUSTER).forCluster( + _pinotHelixResourceManager.getHelixClusterName()).build(); Map configMap = _pinotHelixResourceManager.getHelixAdmin().getConfig(helixConfigScope, Collections.singletonList(configName)); return configMap != null ? configMap.get(configName) : null; diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java index f7441593c90..4b62b143af8 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java @@ -37,6 +37,7 @@ import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.lang3.StringUtils; @@ -53,7 +54,6 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.minion.MinionTaskMetadataUtils; import org.apache.pinot.common.utils.DateTimeUtils; -import org.apache.pinot.controller.api.exception.NoTaskMetadataException; import org.apache.pinot.controller.api.exception.NoTaskScheduledException; import org.apache.pinot.controller.api.exception.UnknownTaskTypeException; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; @@ -620,6 +620,59 @@ Map getSubtaskProgress(String taskName, @Nullable String subtask return subtaskProgressMap; } + /** + * Gets progress of all subtasks with specified state tracked by given minion workers in memory + * @param subtaskState a specified subtask state, valid values are in org.apache.pinot.minion.event.MinionTaskState + * @param executor an {@link Executor} used to run logic on + * @param connMgr a {@link HttpConnectionManager} used to manage http connections + * @param selectedMinionWorkerEndpoints a map of worker id to http endpoint for minions to get subtask progress from + * @param requestHeaders http headers used to send requests to minion workers + * @param timeoutMs timeout (in millisecond) for requests sent to minion workers + * @return a map of minion worker id to subtask progress + */ + public synchronized Map getSubtaskOnWorkerProgress(String subtaskState, + Executor executor, HttpConnectionManager connMgr, Map selectedMinionWorkerEndpoints, + Map requestHeaders, int timeoutMs) + throws JsonProcessingException { + return getSubtaskOnWorkerProgress(subtaskState, + new CompletionServiceHelper(executor, connMgr, HashBiMap.create(0)), selectedMinionWorkerEndpoints, + requestHeaders, timeoutMs); + } + + @VisibleForTesting + Map getSubtaskOnWorkerProgress(String subtaskState, + CompletionServiceHelper completionServiceHelper, Map selectedMinionWorkerEndpoints, + Map requestHeaders, int timeoutMs) + throws JsonProcessingException { + Map minionWorkerIdSubtaskProgressMap = new HashMap<>(); + if (selectedMinionWorkerEndpoints.isEmpty()) { + return minionWorkerIdSubtaskProgressMap; + } + Map minionWorkerUrlToWorkerIdMap = selectedMinionWorkerEndpoints.entrySet().stream() + .collect(Collectors.toMap( + entry -> String.format("%s/tasks/subtask/state/progress?subTaskState=%s", entry.getValue(), subtaskState), + Map.Entry::getKey)); + List workerUrls = new ArrayList<>(minionWorkerUrlToWorkerIdMap.keySet()); + LOGGER.debug("Getting task progress with workerUrls: {}", workerUrls); + // Scatter and gather progress from multiple workers. + CompletionServiceHelper.CompletionServiceResponse serviceResponse = + completionServiceHelper.doMultiGetRequest(workerUrls, null, true, requestHeaders, timeoutMs); + for (Map.Entry entry : serviceResponse._httpResponses.entrySet()) { + String worker = entry.getKey(); + String resp = entry.getValue(); + LOGGER.debug("Got resp: {} from worker: {}", resp, worker); + minionWorkerIdSubtaskProgressMap + .put(minionWorkerUrlToWorkerIdMap.get(worker), JsonUtils.stringToObject(resp, Map.class)); + } + if (serviceResponse._failedResponseCount > 0) { + // Instead of aborting, subtasks without worker side progress return the task status tracked by Helix. + // The detailed worker failure response is logged as error by CompletionServiceResponse for debugging. + LOGGER.warn("There were {} workers failed to report task progress. Got partial progress info: {}", + serviceResponse._failedResponseCount, minionWorkerIdSubtaskProgressMap); + } + return minionWorkerIdSubtaskProgressMap; + } + /** * Helper method to return a map of task names to corresponding task state * where the task corresponds to the given Pinot table name. This is used to @@ -864,8 +917,7 @@ public String getTaskMetadataByTable(String taskType, String tableNameWithType) ZkHelixPropertyStore propertyStore = _helixResourceManager.getPropertyStore(); ZNRecord raw = MinionTaskMetadataUtils.fetchTaskMetadata(propertyStore, taskType, tableNameWithType); if (raw == null) { - throw new NoTaskMetadataException( - String.format("No task metadata for task type: %s from table: %s", taskType, tableNameWithType)); + return JsonUtils.objectToString(JsonUtils.newObjectNode()); } return JsonUtils.objectToString(raw); } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java index e7a7584b768..4f10b6a5c50 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java @@ -32,8 +32,10 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import javax.annotation.Nullable; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.helix.AccessOption; import org.apache.helix.task.TaskState; import org.apache.helix.zookeeper.zkclient.IZkChildListener; @@ -114,7 +116,8 @@ public class PinotTaskManager extends ControllerPeriodicTask { public PinotTaskManager(PinotHelixTaskResourceManager helixTaskResourceManager, PinotHelixResourceManager helixResourceManager, LeadControllerManager leadControllerManager, ControllerConf controllerConf, ControllerMetrics controllerMetrics, - TaskManagerStatusCache taskManagerStatusCache) { + TaskManagerStatusCache taskManagerStatusCache, Executor executor, + MultiThreadedHttpConnectionManager connectionManager) { super("PinotTaskManager", controllerConf.getTaskManagerFrequencyInSeconds(), controllerConf.getPinotTaskManagerInitialDelaySeconds(), helixResourceManager, leadControllerManager, controllerMetrics); @@ -122,7 +125,7 @@ public PinotTaskManager(PinotHelixTaskResourceManager helixTaskResourceManager, _taskManagerStatusCache = taskManagerStatusCache; _clusterInfoAccessor = new ClusterInfoAccessor(helixResourceManager, helixTaskResourceManager, controllerConf, controllerMetrics, - leadControllerManager); + leadControllerManager, executor, connectionManager); _taskGeneratorRegistry = new TaskGeneratorRegistry(_clusterInfoAccessor); _skipLateCronSchedule = controllerConf.isSkipLateCronSchedule(); _maxCronScheduleDelayInSeconds = controllerConf.getMaxCronScheduleDelayInSeconds(); @@ -561,8 +564,8 @@ private String scheduleTask(PinotTaskGenerator taskGenerator, List long successRunTimestamp = System.currentTimeMillis(); for (TableConfig tableConfig : enabledTableConfigs) { _taskManagerStatusCache.saveTaskGeneratorInfo(tableConfig.getTableName(), taskGenerator.getTaskType(), - taskGeneratorMostRecentRunInfo -> taskGeneratorMostRecentRunInfo.addErrorRunMessage( - successRunTimestamp, errors.toString())); + taskGeneratorMostRecentRunInfo -> taskGeneratorMostRecentRunInfo.addErrorRunMessage(successRunTimestamp, + errors.toString())); // before the first task schedule, the follow gauge metric will be empty // TODO: find a better way to report task generation information _controllerMetrics.setOrUpdateTableGauge(tableConfig.getTableName(), taskGenerator.getTaskType(), diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java index 861d903d3d4..d2a28604406 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java @@ -36,6 +36,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; import org.apache.helix.AccessOption; import org.apache.helix.Criteria; import org.apache.helix.HelixAdmin; @@ -166,6 +167,7 @@ public class PinotLLCRealtimeSegmentManager { private final Lock[] _idealStateUpdateLocks; private final FlushThresholdUpdateManager _flushThresholdUpdateManager; private final boolean _isDeepStoreLLCSegmentUploadRetryEnabled; + private final int _deepstoreUploadRetryTimeoutMs; private final FileUploadDownloadClient _fileUploadDownloadClient; private final AtomicInteger _numCompletingSegments = new AtomicInteger(0); @@ -181,7 +183,8 @@ public PinotLLCRealtimeSegmentManager(PinotHelixResourceManager helixResourceMan _controllerConf = controllerConf; _controllerMetrics = controllerMetrics; _metadataEventNotifierFactory = - MetadataEventNotifierFactory.loadFactory(controllerConf.subset(METADATA_EVENT_NOTIFIER_PREFIX)); + MetadataEventNotifierFactory.loadFactory(controllerConf.subset(METADATA_EVENT_NOTIFIER_PREFIX), + helixResourceManager); _numIdealStateUpdateLocks = controllerConf.getRealtimeSegmentMetadataCommitNumLocks(); _idealStateUpdateLocks = new Lock[_numIdealStateUpdateLocks]; for (int i = 0; i < _numIdealStateUpdateLocks; i++) { @@ -189,6 +192,7 @@ public PinotLLCRealtimeSegmentManager(PinotHelixResourceManager helixResourceMan } _flushThresholdUpdateManager = new FlushThresholdUpdateManager(); _isDeepStoreLLCSegmentUploadRetryEnabled = controllerConf.isDeepStoreRetryUploadLLCSegmentEnabled(); + _deepstoreUploadRetryTimeoutMs = controllerConf.getDeepStoreRetryUploadTimeoutMs(); _fileUploadDownloadClient = _isDeepStoreLLCSegmentUploadRetryEnabled ? initFileUploadDownloadClient() : null; } @@ -479,12 +483,10 @@ public void commitSegmentFile(String realtimeTableName, CommittingSegmentDescrip LOGGER.info("No moving needed for segment on peer servers: {}", segmentLocation); return; } - URI segmentFileURI = URIUtils.getUri(segmentLocation); + URI tableDirURI = URIUtils.getUri(_controllerConf.getDataDir(), rawTableName); - URI uriToMoveTo = URIUtils.getUri(_controllerConf.getDataDir(), rawTableName, URIUtils.encode(segmentName)); PinotFS pinotFS = PinotFSFactory.create(tableDirURI.getScheme()); - Preconditions.checkState(pinotFS.move(segmentFileURI, uriToMoveTo, true), - "Failed to move segment file for segment: %s from: %s to: %s", segmentName, segmentLocation, uriToMoveTo); + String uriToMoveTo = moveSegmentFile(rawTableName, segmentName, segmentLocation, pinotFS); // Cleans up tmp segment files under table dir. // We only clean up tmp segment files in table level dir, so there's no need to list recursively. @@ -500,7 +502,7 @@ public void commitSegmentFile(String realtimeTableName, CommittingSegmentDescrip } catch (Exception e) { LOGGER.warn("Caught exception while deleting temporary segment files for segment: {}", segmentName, e); } - committingSegmentDescriptor.setSegmentLocation(uriToMoveTo.toString()); + committingSegmentDescriptor.setSegmentLocation(uriToMoveTo); } /** @@ -605,6 +607,10 @@ private void commitSegmentMetadataInternal(String realtimeTableName, // Trigger the metadata event notifier _metadataEventNotifierFactory.create().notifyOnSegmentFlush(tableConfig); + + if (StringUtils.isBlank(committingSegmentDescriptor.getSegmentLocation())) { + _controllerMetrics.addMeteredTableValue(realtimeTableName, ControllerMeter.SEGMENT_MISSING_DEEP_STORE_LINK, 1); + } } /** @@ -1386,6 +1392,7 @@ public void uploadToDeepStoreIfMissing(TableConfig tableConfig, List consumingSegments = findConsumingSegments(idealState); return new PauseStatus(Boolean.parseBoolean(isTablePausedStr), consumingSegments, null); } + + @VisibleForTesting + String moveSegmentFile(String rawTableName, String segmentName, String segmentLocation, PinotFS pinotFS) + throws IOException { + URI segmentFileURI = URIUtils.getUri(segmentLocation); + URI uriToMoveTo = createSegmentPath(rawTableName, segmentName); + Preconditions.checkState(pinotFS.move(segmentFileURI, uriToMoveTo, true), + "Failed to move segment file for segment: %s from: %s to: %s", segmentName, segmentLocation, uriToMoveTo); + return uriToMoveTo.toString(); + } + + @VisibleForTesting + URI createSegmentPath(String rawTableName, String segmentName) { + return URIUtils.getUri(_controllerConf.getDataDir(), rawTableName, URIUtils.encode(segmentName)); + } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java index 28ad0646013..7f5f7185a9c 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java @@ -1170,8 +1170,7 @@ private SegmentCompletionProtocol.Response processCommitWhileHoldingOrPartialCon */ private boolean isWinnerPicked(String preferredInstance, long now, final String stopReason) { if ((SegmentCompletionProtocol.REASON_ROW_LIMIT.equals(stopReason) - || SegmentCompletionProtocol.REASON_END_OF_PARTITION_GROUP.equals(stopReason) - || SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED.equals(stopReason)) + || SegmentCompletionProtocol.REASON_END_OF_PARTITION_GROUP.equals(stopReason)) && _commitStateMap.size() == 1) { _winner = preferredInstance; _winningOffset = _commitStateMap.get(preferredInstance); diff --git a/pinot-plugins/pinot-batch-ingestion/v0_deprecated/pinot-hadoop/src/main/java/org/apache/pinot/hadoop/io/FieldExtractor.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/NoOpTableRebalanceObserver.java similarity index 63% rename from pinot-plugins/pinot-batch-ingestion/v0_deprecated/pinot-hadoop/src/main/java/org/apache/pinot/hadoop/io/FieldExtractor.java rename to pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/NoOpTableRebalanceObserver.java index c915e570ec7..9bb9179c90e 100644 --- a/pinot-plugins/pinot-batch-ingestion/v0_deprecated/pinot-hadoop/src/main/java/org/apache/pinot/hadoop/io/FieldExtractor.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/NoOpTableRebalanceObserver.java @@ -16,28 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.hadoop.io; +package org.apache.pinot.controller.helix.core.rebalance; -import java.io.IOException; import java.util.Map; -import java.util.Set; -import org.apache.hadoop.conf.Configuration; - /** - * The FieldExtractor extracts fields from the records. - * @param Type of the record + * Default No-op TableRebalanceObserver. */ -public interface FieldExtractor { +public class NoOpTableRebalanceObserver implements TableRebalanceObserver { + @Override + public void onTrigger(TableRebalanceObserver.Trigger trigger, Map> initialState, + Map> targetState) { + } - /** - * Initializes the FieldExtractor. - */ - void init(Configuration conf, Set fields); + @Override + public void onSuccess(String msg) { + } - /** - * Extracts the fields from the given record. - */ - Map extractFields(T record) - throws IOException; + @Override + public void onError(String errorMsg) { + } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceResult.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceResult.java index a2d6d630d02..c76d06fcaf3 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceResult.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceResult.java @@ -29,22 +29,33 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class RebalanceResult { + private final String _jobId; private final Status _status; private final Map _instanceAssignment; + private final Map _tierInstanceAssignment; private final Map> _segmentAssignment; private final String _description; @JsonCreator - public RebalanceResult(@JsonProperty(value = "status", required = true) Status status, + public RebalanceResult(@JsonProperty(value = "jobId", required = true) String jobId, + @JsonProperty(value = "status", required = true) Status status, @JsonProperty(value = "description", required = true) String description, @JsonProperty("instanceAssignment") @Nullable Map instanceAssignment, + @JsonProperty("tierInstanceAssignment") @Nullable Map tierInstanceAssignment, @JsonProperty("segmentAssignment") @Nullable Map> segmentAssignment) { + _jobId = jobId; _status = status; _description = description; _instanceAssignment = instanceAssignment; + _tierInstanceAssignment = tierInstanceAssignment; _segmentAssignment = segmentAssignment; } + @JsonProperty + public String getJobId() { + return _jobId; + } + @JsonProperty public Status getStatus() { return _status; @@ -60,6 +71,11 @@ public Map getInstanceAssignment() { return _instanceAssignment; } + @JsonProperty + public Map getTierInstanceAssignment() { + return _tierInstanceAssignment; + } + @JsonProperty public Map> getSegmentAssignment() { return _segmentAssignment; diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalanceObserver.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalanceObserver.java new file mode 100644 index 00000000000..d52dce1f6e6 --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalanceObserver.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.rebalance; + +import java.util.Map; + + +/** + * The TableRebalanceObserver interface provides callbacks to take actions + * during critical triggers. The 3 main triggers during a rebalance operation are show below. + * For example, we can track stats + status of rebalance during these triggers. + */ + +public interface TableRebalanceObserver { + enum Trigger { + // Start of rebalance Trigger + START_TRIGGER, + // Waiting for external view to converge towards ideal state + EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER, + // Ideal state changes due to external events and new target for rebalance is computed + IDEAL_STATE_CHANGE_TRIGGER, + } + + void onTrigger(Trigger trigger, Map> currentState, + Map> targetState); + + void onSuccess(String msg); + + void onError(String errorMsg); +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalanceProgressStats.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalanceProgressStats.java new file mode 100644 index 00000000000..6d6f70b5c76 --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalanceProgressStats.java @@ -0,0 +1,130 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.rebalance; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * These are rebalance stats as to how the current state is, when compared to the target state. + * Eg: If the current has 4 segments whose replicas (16) don't match the target state, _segmentsToRebalance + * is 4 and _replicasToRebalance is 16. + */ +public class TableRebalanceProgressStats { + public static class RebalanceStateStats { + public int _segmentsMissing; + public int _segmentsToRebalance; + public double _percentSegmentsToRebalance; + public int _replicasToRebalance; + + RebalanceStateStats() { + _segmentsMissing = 0; + _segmentsToRebalance = 0; + _replicasToRebalance = 0; + _percentSegmentsToRebalance = 0.0; + } + } + + // Done/In_progress/Failed + private String _status; + // When did Rebalance start + private long _startTimeMs; + // How long did rebalance take + private long _timeToFinishInSeconds; + // Success/failure message + private String _completionStatusMsg; + @JsonProperty("initialToTargetStateConvergence") + private RebalanceStateStats _initialToTargetStateConvergence; + @JsonProperty("currentToTargetConvergence") + private RebalanceStateStats _currentToTargetConvergence; + @JsonProperty("externalViewToIdealStateConvergence") + private RebalanceStateStats _externalViewToIdealStateConvergence; + + public TableRebalanceProgressStats() { + _currentToTargetConvergence = new RebalanceStateStats(); + _externalViewToIdealStateConvergence = new RebalanceStateStats(); + _initialToTargetStateConvergence = new RebalanceStateStats(); + } + + public void setStatus(String status) { + _status = status; + } + + public void setInitialToTargetStateConvergence(RebalanceStateStats initialToTargetStateConvergence) { + _initialToTargetStateConvergence = initialToTargetStateConvergence; + } + + public void setStartTimeMs(long startTimeMs) { + _startTimeMs = startTimeMs; + } + + public void setTimeToFinishInSeconds(Long timeToFinishInSeconds) { + _timeToFinishInSeconds = timeToFinishInSeconds; + } + + public void setExternalViewToIdealStateConvergence(RebalanceStateStats externalViewToIdealStateConvergence) { + _externalViewToIdealStateConvergence = externalViewToIdealStateConvergence; + } + + public void setCurrentToTargetConvergence(RebalanceStateStats currentToTargetConvergence) { + _currentToTargetConvergence = currentToTargetConvergence; + } + + public void setCompletionStatusMsg(String completionStatusMsg) { + _completionStatusMsg = completionStatusMsg; + } + + public String getStatus() { + return _status; + } + + public String getCompletionStatusMsg() { + return _completionStatusMsg; + } + + public RebalanceStateStats getInitialToTargetStateConvergence() { + return _initialToTargetStateConvergence; + } + + public long getStartTimeMs() { + return _startTimeMs; + } + + public long getTimeToFinishInSeconds() { + return _timeToFinishInSeconds; + } + + public RebalanceStateStats getExternalViewToIdealStateConvergence() { + return _externalViewToIdealStateConvergence; + } + + public RebalanceStateStats getCurrentToTargetConvergence() { + return _currentToTargetConvergence; + } + + public static boolean statsDiffer(RebalanceStateStats base, RebalanceStateStats compare) { + if (base._replicasToRebalance != compare._replicasToRebalance + || base._segmentsToRebalance != compare._segmentsToRebalance + || base._segmentsMissing != compare._segmentsMissing + || base._percentSegmentsToRebalance != compare._percentSegmentsToRebalance) { + return true; + } + return false; + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java index 4a1bc13b52c..88700e427b2 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.function.ToIntFunction; import javax.annotation.Nullable; @@ -59,12 +60,12 @@ import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TierConfig; +import org.apache.pinot.spi.config.table.assignment.InstanceAssignmentConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; import org.apache.pinot.spi.utils.IngestionConfigUtils; import org.apache.pinot.spi.utils.RebalanceConfigConstants; -import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,15 +116,35 @@ public class TableRebalancer { private static final Logger LOGGER = LoggerFactory.getLogger(TableRebalancer.class); private final HelixManager _helixManager; private final HelixDataAccessor _helixDataAccessor; + private final TableRebalanceObserver _tableRebalanceObserver; - public TableRebalancer(HelixManager helixManager) { + public TableRebalancer(HelixManager helixManager, @Nullable TableRebalanceObserver tableRebalanceObserver) { _helixManager = helixManager; + if (tableRebalanceObserver != null) { + _tableRebalanceObserver = tableRebalanceObserver; + } else { + _tableRebalanceObserver = new NoOpTableRebalanceObserver(); + } _helixDataAccessor = helixManager.getHelixDataAccessor(); } + public TableRebalancer(HelixManager helixManager) { + this(helixManager, null); + } + + public static String createUniqueRebalanceJobIdentifier() { + return UUID.randomUUID().toString(); + } + public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanceConfig) { long startTimeMs = System.currentTimeMillis(); String tableNameWithType = tableConfig.getTableName(); + String rebalanceJobId = rebalanceConfig.getString(RebalanceConfigConstants.JOB_ID); + if (rebalanceJobId == null) { + // If not passed along, create one. + // TODO - Add rebalanceJobId to all log messages for easy tracking. + rebalanceJobId = createUniqueRebalanceJobIdentifier(); + } boolean dryRun = rebalanceConfig.getBoolean(RebalanceConfigConstants.DRY_RUN, RebalanceConfigConstants.DEFAULT_DRY_RUN); @@ -152,25 +173,26 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc LOGGER.info( "Start rebalancing table: {} with dryRun: {}, reassignInstances: {}, includeConsuming: {}, bootstrap: {}, " + "downtime: {}, minReplicasToKeepUpForNoDowntime: {}, enableStrictReplicaGroup: {}, bestEfforts: {}, " - + "externalViewCheckIntervalInMs: {}, externalViewStabilizationTimeoutInMs: {}", - tableNameWithType, dryRun, reassignInstances, includeConsuming, bootstrap, downtime, - minReplicasToKeepUpForNoDowntime, enableStrictReplicaGroup, bestEfforts, externalViewCheckIntervalInMs, - externalViewStabilizationTimeoutInMs); + + "externalViewCheckIntervalInMs: {}, externalViewStabilizationTimeoutInMs: {}", tableNameWithType, dryRun, + reassignInstances, includeConsuming, bootstrap, downtime, minReplicasToKeepUpForNoDowntime, + enableStrictReplicaGroup, bestEfforts, externalViewCheckIntervalInMs, externalViewStabilizationTimeoutInMs); // Validate table config try { // Do not allow rebalancing HLC real-time table if (tableConfig.getTableType() == TableType.REALTIME && new StreamConfig(tableNameWithType, IngestionConfigUtils.getStreamConfigMap(tableConfig)).hasHighLevelConsumerType()) { - LOGGER.warn("Cannot rebalance table: {} with high-level consumer, aborting the rebalance", tableNameWithType); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Cannot rebalance table with high-level consumer", - null, null); + LOGGER.warn("For rebalanceId: {}, cannot rebalance table: {} with high-level consumer, aborting the rebalance", + rebalanceJobId, tableNameWithType); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Cannot rebalance table with high-level consumer", null, null, null); } } catch (Exception e) { - LOGGER.warn("Caught exception while validating table config for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Caught exception while validating table config: " + e, - null, null); + LOGGER.warn( + "For rebalanceId: {}, caught exception while validating table config for table: {}, aborting the rebalance", + rebalanceJobId, tableNameWithType, e); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while validating table config: " + e, null, null, null); } // Fetch ideal state @@ -179,42 +201,62 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc try { currentIdealState = _helixDataAccessor.getProperty(idealStatePropertyKey); } catch (Exception e) { - LOGGER.warn("Caught exception while fetching IdealState for table: {}, aborting the rebalance", tableNameWithType, - e); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Caught exception while fetching IdealState: " + e, - null, null); + LOGGER.warn( + "For rebalanceId: {}, caught exception while fetching IdealState for table: {}, aborting the rebalance", + rebalanceJobId, tableNameWithType, e); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while fetching IdealState: " + e, null, null, null); } if (currentIdealState == null) { - LOGGER.warn("Cannot find the IdealState for table: {}, aborting the rebalance", tableNameWithType); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Cannot find the IdealState for table", null, null); + LOGGER.warn("For rebalanceId: {}, cannot find the IdealState for table: {}, aborting the rebalance", + rebalanceJobId, tableNameWithType); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, "Cannot find the IdealState for table", + null, null, null); } if (!currentIdealState.isEnabled() && !downtime) { - LOGGER.warn("Cannot rebalance disabled table: {} without downtime, aborting the rebalance", tableNameWithType); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Cannot rebalance disabled table without downtime", - null, null); + LOGGER.warn("For rebalanceId: {}, cannot rebalance disabled table: {} without downtime, aborting the rebalance", + rebalanceJobId, tableNameWithType); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Cannot rebalance disabled table without downtime", null, null, null); } - LOGGER.info("Fetching/computing instance partitions, reassigning instances if configured for table: {}", - tableNameWithType); + LOGGER.info("For rebalanceId: {}, processing instance partitions for table: {}", rebalanceJobId, tableNameWithType); // Calculate instance partitions map Map instancePartitionsMap; + boolean instancePartitionsUnchanged; try { - instancePartitionsMap = getInstancePartitionsMap(tableConfig, reassignInstances, dryRun); + Pair, Boolean> instancePartitionsMapAndUnchanged = + getInstancePartitionsMap(tableConfig, reassignInstances, bootstrap, dryRun); + instancePartitionsMap = instancePartitionsMapAndUnchanged.getLeft(); + instancePartitionsUnchanged = instancePartitionsMapAndUnchanged.getRight(); } catch (Exception e) { - LOGGER.warn( - "Caught exception while fetching/calculating instance partitions for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, - "Caught exception while fetching/calculating instance partitions: " + e, null, null); + LOGGER.warn("For rebalanceId: {}, caught exception while fetching/calculating instance partitions for table: {}, " + + "aborting the rebalance", rebalanceJobId, tableNameWithType, e); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while fetching/calculating instance partitions: " + e, null, null, null); } // Calculate instance partitions for tiers if configured - List sortedTiers = getSortedTiers(tableConfig); - Map tierToInstancePartitionsMap = - getTierToInstancePartitionsMap(tableNameWithType, sortedTiers); + List sortedTiers; + Map tierToInstancePartitionsMap; + boolean tierInstancePartitionsUnchanged; + try { + sortedTiers = getSortedTiers(tableConfig); + Pair, Boolean> tierToInstancePartitionsMapAndUnchanged = + getTierToInstancePartitionsMap(tableConfig, sortedTiers, reassignInstances, bootstrap, dryRun); + tierToInstancePartitionsMap = tierToInstancePartitionsMapAndUnchanged.getLeft(); + tierInstancePartitionsUnchanged = tierToInstancePartitionsMapAndUnchanged.getRight(); + } catch (Exception e) { + LOGGER.warn( + "For rebalanceId: {}, caught exception while fetching/calculating tier instance partitions for table: {}, " + + "aborting the rebalance", rebalanceJobId, tableNameWithType, e); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while fetching/calculating tier instance partitions: " + e, null, null, null); + } - LOGGER.info("Calculating the target assignment for table: {}", tableNameWithType); + LOGGER.info("For rebalanceId: {}, calculating the target assignment for table: {}", rebalanceJobId, + tableNameWithType); SegmentAssignment segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(_helixManager, tableConfig); Map> currentAssignment = currentIdealState.getRecord().getMapFields(); Map> targetAssignment; @@ -222,36 +264,45 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc targetAssignment = segmentAssignment.rebalanceTable(currentAssignment, instancePartitionsMap, sortedTiers, tierToInstancePartitionsMap, rebalanceConfig); } catch (Exception e) { - LOGGER.warn("Caught exception while calculating target assignment for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, - "Caught exception while calculating target assignment: " + e, instancePartitionsMap, null); + LOGGER.warn("For rebalanceId: {}, caught exception while calculating target assignment for table: {}, " + + "aborting the rebalance", rebalanceJobId, tableNameWithType, e); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while calculating target assignment: " + e, instancePartitionsMap, + tierToInstancePartitionsMap, null); } - if (currentAssignment.equals(targetAssignment)) { + boolean segmentAssignmentUnchanged = currentAssignment.equals(targetAssignment); + LOGGER.info("For rebalanceId: {}, instancePartitionsUnchanged: {}, tierInstancePartitionsUnchanged: {}, " + + "segmentAssignmentUnchanged: {} for table: {}", rebalanceJobId, instancePartitionsUnchanged, + tierInstancePartitionsUnchanged, segmentAssignmentUnchanged, tableNameWithType); + + if (segmentAssignmentUnchanged) { LOGGER.info("Table: {} is already balanced", tableNameWithType); - if (reassignInstances) { + if (instancePartitionsUnchanged && tierInstancePartitionsUnchanged) { + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.NO_OP, "Table is already balanced", + instancePartitionsMap, tierToInstancePartitionsMap, targetAssignment); + } else { if (dryRun) { - return new RebalanceResult(RebalanceResult.Status.DONE, + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.DONE, "Instance reassigned in dry-run mode, table is already balanced", instancePartitionsMap, - targetAssignment); + tierToInstancePartitionsMap, targetAssignment); } else { - return new RebalanceResult(RebalanceResult.Status.DONE, "Instance reassigned, table is already balanced", - instancePartitionsMap, targetAssignment); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.DONE, + "Instance reassigned, table is already balanced", instancePartitionsMap, tierToInstancePartitionsMap, + targetAssignment); } - } else { - return new RebalanceResult(RebalanceResult.Status.NO_OP, "Table is already balanced", instancePartitionsMap, - targetAssignment); } } if (dryRun) { - LOGGER.info("Rebalancing table: {} in dry-run mode, returning the target assignment", tableNameWithType); - return new RebalanceResult(RebalanceResult.Status.DONE, "Dry-run mode", instancePartitionsMap, targetAssignment); + LOGGER.info("For rebalanceId: {}, rebalancing table: {} in dry-run mode, returning the target assignment", + rebalanceJobId, tableNameWithType); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.DONE, "Dry-run mode", instancePartitionsMap, + tierToInstancePartitionsMap, targetAssignment); } if (downtime) { - LOGGER.info("Rebalancing table: {} with downtime", tableNameWithType); + LOGGER.info("For rebalanceId: {}, rebalancing table: {} with downtime", rebalanceJobId, tableNameWithType); // Reuse current IdealState to update the IdealState in cluster ZNRecord idealStateRecord = currentIdealState.getRecord(); @@ -264,19 +315,26 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc Preconditions.checkState(_helixDataAccessor.getBaseDataAccessor() .set(idealStatePropertyKey.getPath(), idealStateRecord, idealStateRecord.getVersion(), AccessOption.PERSISTENT), "Failed to update IdealState"); - LOGGER.info("Finished rebalancing table: {} with downtime in {}ms.", tableNameWithType, - System.currentTimeMillis() - startTimeMs); - return new RebalanceResult(RebalanceResult.Status.DONE, + LOGGER.info("For rebalanceId: {}, finished rebalancing table: {} with downtime in {}ms.", rebalanceJobId, + tableNameWithType, System.currentTimeMillis() - startTimeMs); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.DONE, "Success with downtime (replaced IdealState with the target segment assignment, ExternalView might not " - + "reach the target segment assignment yet)", instancePartitionsMap, targetAssignment); + + "reach the target segment assignment yet)", instancePartitionsMap, tierToInstancePartitionsMap, + targetAssignment); } catch (Exception e) { - LOGGER.warn("Caught exception while updating IdealState for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Caught exception while updating IdealState: " + e, - instancePartitionsMap, targetAssignment); + LOGGER.warn( + "For rebalanceId: {}, caught exception while updating IdealState for table: {}, aborting the rebalance", + rebalanceJobId, tableNameWithType, e); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while updating IdealState: " + e, instancePartitionsMap, tierToInstancePartitionsMap, + targetAssignment); } } + // Record the beginning of rebalance + _tableRebalanceObserver.onTrigger(TableRebalanceObserver.Trigger.START_TRIGGER, currentAssignment, + targetAssignment); + // Calculate the min available replicas for no-downtime rebalance // NOTE: The calculation is based on the number of replicas of the target assignment. In case of increasing the // number of replicas for the current assignment, the current instance state map might not have enough @@ -291,12 +349,15 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc if (minReplicasToKeepUpForNoDowntime >= 0) { // For non-negative value, use it as min available replicas if (minReplicasToKeepUpForNoDowntime >= numReplicas) { - LOGGER.warn( - "Illegal config for minReplicasToKeepUpForNoDowntime: {} for table: {}, must be less than number of " - + "replicas: {}, aborting the rebalance", minReplicasToKeepUpForNoDowntime, tableNameWithType, - numReplicas); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Illegal min available replicas config", - instancePartitionsMap, targetAssignment); + String errorMsg = String.format( + "For rebalanceId: %s, Illegal config for minReplicasToKeepUpForNoDowntime: %d for table: %s, " + + "must be less than number of replicas: %d, aborting the rebalance", rebalanceJobId, + minReplicasToKeepUpForNoDowntime, tableNameWithType, numReplicas); + LOGGER.warn(errorMsg); + _tableRebalanceObserver.onError(errorMsg); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Illegal min available replicas config", instancePartitionsMap, tierToInstancePartitionsMap, + targetAssignment); } minAvailableReplicas = minReplicasToKeepUpForNoDowntime; } else { @@ -304,11 +365,13 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc minAvailableReplicas = Math.max(numReplicas + minReplicasToKeepUpForNoDowntime, 0); } - LOGGER.info("Rebalancing table: {} with minAvailableReplicas: {}, enableStrictReplicaGroup: {}, bestEfforts: {}, " - + "externalViewCheckIntervalInMs: {}, externalViewStabilizationTimeoutInMs: {}", tableNameWithType, - minAvailableReplicas, enableStrictReplicaGroup, bestEfforts, externalViewCheckIntervalInMs, - externalViewStabilizationTimeoutInMs); + LOGGER.info( + "For rebalanceId: {}, rebalancing table: {} with minAvailableReplicas: {}, enableStrictReplicaGroup: {}, " + + "bestEfforts: {}, externalViewCheckIntervalInMs: {}, externalViewStabilizationTimeoutInMs: {}", + rebalanceJobId, tableNameWithType, minAvailableReplicas, enableStrictReplicaGroup, bestEfforts, + externalViewCheckIntervalInMs, externalViewStabilizationTimeoutInMs); int expectedVersion = currentIdealState.getRecord().getVersion(); + while (true) { // Wait for ExternalView to converge before updating the next IdealState Set segmentsToMove = SegmentAssignmentUtils.getSegmentsToMove(currentAssignment, targetAssignment); @@ -318,19 +381,22 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc waitForExternalViewToConverge(tableNameWithType, bestEfforts, segmentsToMove, externalViewCheckIntervalInMs, externalViewStabilizationTimeoutInMs); } catch (Exception e) { - LOGGER.warn("Caught exception while waiting for ExternalView to converge for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, + String errorMsg = String.format( + "For rebalanceId: %s, caught exception while waiting for ExternalView to converge for table: %s, " + + "aborting the rebalance", rebalanceJobId, tableNameWithType); + LOGGER.warn(errorMsg, e); + _tableRebalanceObserver.onError(errorMsg); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, "Caught exception while waiting for ExternalView to converge: " + e, instancePartitionsMap, - targetAssignment); + tierToInstancePartitionsMap, targetAssignment); } // Re-calculate the target assignment if IdealState changed while waiting for ExternalView to converge ZNRecord idealStateRecord = idealState.getRecord(); if (idealStateRecord.getVersion() != expectedVersion) { LOGGER.info( - "IdealState version changed while waiting for ExternalView to converge for table: {}, re-calculating the " - + "target assignment", tableNameWithType); + "For rebalanceId: {}, idealState version changed while waiting for ExternalView to converge for table: {}, " + + "re-calculating the target assignment", rebalanceJobId, tableNameWithType); Map> oldAssignment = currentAssignment; currentAssignment = idealStateRecord.getMapFields(); expectedVersion = idealStateRecord.getVersion(); @@ -343,9 +409,9 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc Map currentInstanceStateMap = currentAssignment.get(segment); if (!oldInstanceStateMap.equals(currentInstanceStateMap)) { LOGGER.info( - "Segment state changed in IdealState from: {} to: {} for table: {}, segment: {}, re-calculating the " - + "target assignment based on the new IdealState", oldInstanceStateMap, currentInstanceStateMap, - tableNameWithType, segment); + "For rebalanceId: {}, segment state changed in IdealState from: {} to: {} for table: {}, segment: {}, " + + "re-calculating the target assignment based on the new IdealState", rebalanceJobId, + oldInstanceStateMap, currentInstanceStateMap, tableNameWithType, segment); segmentsToMoveChanged = true; break; } @@ -353,22 +419,27 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc if (segmentsToMoveChanged) { try { // Re-calculate the instance partitions in case the instance configs changed during the rebalance - instancePartitionsMap = getInstancePartitionsMap(tableConfig, reassignInstances, false); - tierToInstancePartitionsMap = getTierToInstancePartitionsMap(tableNameWithType, sortedTiers); + instancePartitionsMap = + getInstancePartitionsMap(tableConfig, reassignInstances, bootstrap, false).getLeft(); + tierToInstancePartitionsMap = + getTierToInstancePartitionsMap(tableConfig, sortedTiers, reassignInstances, bootstrap, + dryRun).getLeft(); targetAssignment = segmentAssignment.rebalanceTable(currentAssignment, instancePartitionsMap, sortedTiers, tierToInstancePartitionsMap, rebalanceConfig); } catch (Exception e) { - LOGGER.warn( - "Caught exception while re-calculating the target assignment for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, + String errorMsg = String.format( + "For rebalanceId: %s, caught exception while re-calculating the target assignment for table: %s, " + + "aborting the rebalance", rebalanceJobId, tableNameWithType); + LOGGER.warn(errorMsg, e); + _tableRebalanceObserver.onError(errorMsg); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, "Caught exception while re-calculating the target assignment: " + e, instancePartitionsMap, - targetAssignment); + tierToInstancePartitionsMap, targetAssignment); } } else { - LOGGER.info( - "No state change found for segments to be moved, re-calculating the target assignment based on the " - + "previous target assignment for table: {}", tableNameWithType); + LOGGER.info("For rebalanceId:{}, no state change found for segments to be moved, " + + "re-calculating the target assignment based on the previous target assignment for table: {}", + rebalanceJobId, tableNameWithType); Map> oldTargetAssignment = targetAssignment; targetAssignment = new HashMap<>(currentAssignment); for (String segment : segmentsToMove) { @@ -378,20 +449,25 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc } if (currentAssignment.equals(targetAssignment)) { - LOGGER.info( - "Finished rebalancing table: {} with minAvailableReplicas: {}, enableStrictReplicaGroup: {}, bestEfforts:" - + " {} in {}ms.", tableNameWithType, minAvailableReplicas, enableStrictReplicaGroup, bestEfforts, - System.currentTimeMillis() - startTimeMs); - return new RebalanceResult(RebalanceResult.Status.DONE, + String msg = String.format("For rebalanceId: %s, finished rebalancing table: %s with minAvailableReplicas: %d, " + + "enableStrictReplicaGroup: %b, bestEfforts: %b in %d ms.", rebalanceJobId, tableNameWithType, + minAvailableReplicas, enableStrictReplicaGroup, bestEfforts, System.currentTimeMillis() - startTimeMs); + LOGGER.info(msg); + // Record completion + _tableRebalanceObserver.onSuccess(msg); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.DONE, "Success with minAvailableReplicas: " + minAvailableReplicas + " (both IdealState and ExternalView should reach the target segment assignment)", - instancePartitionsMap, targetAssignment); + instancePartitionsMap, tierToInstancePartitionsMap, targetAssignment); } + // Record change of current ideal state and the new target + _tableRebalanceObserver.onTrigger(TableRebalanceObserver.Trigger.IDEAL_STATE_CHANGE_TRIGGER, currentAssignment, + targetAssignment); Map> nextAssignment = getNextAssignment(currentAssignment, targetAssignment, minAvailableReplicas, enableStrictReplicaGroup); - LOGGER.info("Got the next assignment for table: {} with number of segments to be moved to each instance: {}", - tableNameWithType, + LOGGER.info("For rebalanceId: {}, got the next assignment for table: {} with number of segments to be moved to " + + "each instance: {}", rebalanceJobId, tableNameWithType, SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, nextAssignment)); // Reuse current IdealState to update the IdealState in cluster @@ -406,34 +482,50 @@ public RebalanceResult rebalance(TableConfig tableConfig, Configuration rebalanc "Failed to update IdealState"); currentAssignment = nextAssignment; expectedVersion++; - LOGGER.info("Successfully updated the IdealState for table: {}", tableNameWithType); + LOGGER.info("For rebalanceId: {}, successfully updated the IdealState for table: {}", rebalanceJobId, + tableNameWithType); } catch (ZkBadVersionException e) { - LOGGER.info("Version changed while updating IdealState for table: {}", tableNameWithType); + LOGGER.info("For rebalanceId: {}, version changed while updating IdealState for table: {}", rebalanceJobId, + tableNameWithType); } catch (Exception e) { - LOGGER.warn("Caught exception while updating IdealState for table: {}, aborting the rebalance", - tableNameWithType, e); - return new RebalanceResult(RebalanceResult.Status.FAILED, "Caught exception while updating IdealState: " + e, - instancePartitionsMap, targetAssignment); + String errorMsg = String.format( + "For rebalanceId: %s, caught exception while updating IdealState for table: %s, " + + "aborting the rebalance", rebalanceJobId, tableNameWithType); + LOGGER.warn(errorMsg, e); + _tableRebalanceObserver.onError(errorMsg); + return new RebalanceResult(rebalanceJobId, RebalanceResult.Status.FAILED, + "Caught exception while updating IdealState: " + e, instancePartitionsMap, tierToInstancePartitionsMap, + targetAssignment); } } } - private Map getInstancePartitionsMap(TableConfig tableConfig, - boolean reassignInstances, boolean dryRun) { + /** + * Gets the instance partitions for instance partition types and also returns a boolean for whether they are unchanged + */ + private Pair, Boolean> getInstancePartitionsMap( + TableConfig tableConfig, boolean reassignInstances, boolean bootstrap, boolean dryRun) { + boolean instancePartitionsUnchanged; Map instancePartitionsMap = new TreeMap<>(); if (tableConfig.getTableType() == TableType.OFFLINE) { - instancePartitionsMap.put(InstancePartitionsType.OFFLINE, - getInstancePartitions(tableConfig, InstancePartitionsType.OFFLINE, reassignInstances, dryRun)); + Pair partitionAndUnchangedForOffline = + getInstancePartitions(tableConfig, InstancePartitionsType.OFFLINE, reassignInstances, bootstrap, dryRun); + instancePartitionsMap.put(InstancePartitionsType.OFFLINE, partitionAndUnchangedForOffline.getLeft()); + instancePartitionsUnchanged = partitionAndUnchangedForOffline.getRight(); } else { - instancePartitionsMap.put(InstancePartitionsType.CONSUMING, - getInstancePartitions(tableConfig, InstancePartitionsType.CONSUMING, reassignInstances, dryRun)); + Pair partitionAndUnchangedForConsuming = + getInstancePartitions(tableConfig, InstancePartitionsType.CONSUMING, reassignInstances, bootstrap, dryRun); + instancePartitionsMap.put(InstancePartitionsType.CONSUMING, partitionAndUnchangedForConsuming.getLeft()); + instancePartitionsUnchanged = partitionAndUnchangedForConsuming.getRight(); String tableNameWithType = tableConfig.getTableName(); if (InstanceAssignmentConfigUtils.shouldRelocateCompletedSegments(tableConfig)) { + Pair partitionAndUnchangedForCompleted = + getInstancePartitions(tableConfig, InstancePartitionsType.COMPLETED, reassignInstances, bootstrap, dryRun); LOGGER.info( "COMPLETED segments should be relocated, fetching/computing COMPLETED instance partitions for table: {}", tableNameWithType); - instancePartitionsMap.put(InstancePartitionsType.COMPLETED, - getInstancePartitions(tableConfig, InstancePartitionsType.COMPLETED, reassignInstances, dryRun)); + instancePartitionsMap.put(InstancePartitionsType.COMPLETED, partitionAndUnchangedForCompleted.getLeft()); + instancePartitionsUnchanged &= partitionAndUnchangedForCompleted.getRight(); } else { LOGGER.info( "COMPLETED segments should not be relocated, skipping fetching/computing COMPLETED instance partitions " @@ -447,65 +539,70 @@ private Map getInstancePartitionsMap } } } - return instancePartitionsMap; + return Pair.of(instancePartitionsMap, instancePartitionsUnchanged); } - private InstancePartitions getInstancePartitions(TableConfig tableConfig, - InstancePartitionsType instancePartitionsType, boolean reassignInstances, boolean dryRun) { + /** + * Fetches/computes the instance partitions and also returns a boolean for whether they are unchanged + */ + private Pair getInstancePartitions(TableConfig tableConfig, + InstancePartitionsType instancePartitionsType, boolean reassignInstances, boolean bootstrap, boolean dryRun) { String tableNameWithType = tableConfig.getTableName(); - if (InstanceAssignmentConfigUtils.allowInstanceAssignment(tableConfig, instancePartitionsType)) { - if (reassignInstances) { - String rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType); - boolean hasPreConfiguredInstancePartitions = TableConfigUtils.hasPreConfiguredInstancePartitions(tableConfig, - instancePartitionsType); + String instancePartitionsName = + InstancePartitionsUtils.getInstancePartitionsName(tableNameWithType, instancePartitionsType.toString()); + InstancePartitions existingInstancePartitions = + InstancePartitionsUtils.fetchInstancePartitions(_helixManager.getHelixPropertyStore(), instancePartitionsName); + + if (reassignInstances) { + if (InstanceAssignmentConfigUtils.allowInstanceAssignment(tableConfig, instancePartitionsType)) { + boolean hasPreConfiguredInstancePartitions = + TableConfigUtils.hasPreConfiguredInstancePartitions(tableConfig, instancePartitionsType); if (hasPreConfiguredInstancePartitions) { String referenceInstancePartitionsName = tableConfig.getInstancePartitionsMap().get(instancePartitionsType); - InstancePartitions instancePartitions = InstancePartitionsUtils.fetchInstancePartitionsWithRename( - _helixManager.getHelixPropertyStore(), referenceInstancePartitionsName, - instancePartitionsType.getInstancePartitionsName(rawTableName)); - if (!dryRun) { + InstancePartitions instancePartitions = + InstancePartitionsUtils.fetchInstancePartitionsWithRename(_helixManager.getHelixPropertyStore(), + referenceInstancePartitionsName, instancePartitionsName); + boolean instancePartitionsUnchanged = instancePartitions.equals(existingInstancePartitions); + if (!dryRun && !instancePartitionsUnchanged) { LOGGER.info("Persisting instance partitions: {} (referencing {})", instancePartitions, referenceInstancePartitionsName); InstancePartitionsUtils.persistInstancePartitions(_helixManager.getHelixPropertyStore(), instancePartitions); } - return instancePartitions; + return Pair.of(instancePartitions, instancePartitionsUnchanged); } - InstancePartitions existingInstancePartitions = - InstancePartitionsUtils.fetchInstancePartitions(_helixManager.getHelixPropertyStore(), - InstancePartitionsUtils.getInstancePartitionsName(tableNameWithType, - instancePartitionsType.toString())); LOGGER.info("Reassigning {} instances for table: {}", instancePartitionsType, tableNameWithType); InstanceAssignmentDriver instanceAssignmentDriver = new InstanceAssignmentDriver(tableConfig); + // Assign instances with existing instance partition to null if bootstrap mode is enabled, so that the instance + // partition map can be fully recalculated. InstancePartitions instancePartitions = instanceAssignmentDriver.assignInstances(instancePartitionsType, _helixDataAccessor.getChildValues(_helixDataAccessor.keyBuilder().instanceConfigs(), true), - existingInstancePartitions); - if (!dryRun) { + bootstrap ? null : existingInstancePartitions); + boolean instancePartitionsUnchanged = instancePartitions.equals(existingInstancePartitions); + if (!dryRun && !instancePartitionsUnchanged) { LOGGER.info("Persisting instance partitions: {} to ZK", instancePartitions); InstancePartitionsUtils.persistInstancePartitions(_helixManager.getHelixPropertyStore(), instancePartitions); } - return instancePartitions; + return Pair.of(instancePartitions, instancePartitionsUnchanged); } else { - LOGGER.info("Fetching/computing {} instance partitions for table: {}", instancePartitionsType, - tableNameWithType); - return InstancePartitionsUtils.fetchOrComputeInstancePartitions(_helixManager, tableConfig, - instancePartitionsType); - } - } else { - LOGGER.info("{} instance assignment is not allowed, using default instance partitions for table: {}", - instancePartitionsType, tableNameWithType); - if (reassignInstances) { - LOGGER.warn("Cannot reassign {} instances (instance assignment is not allowed) for table: {}", + LOGGER.info("{} instance assignment is not allowed, using default instance partitions for table: {}", instancePartitionsType, tableNameWithType); + InstancePartitions instancePartitions = + InstancePartitionsUtils.computeDefaultInstancePartitions(_helixManager, tableConfig, + instancePartitionsType); + boolean noExistingInstancePartitions = existingInstancePartitions == null; + if (!dryRun && !noExistingInstancePartitions) { + LOGGER.info("Removing instance partitions: {} from ZK", instancePartitionsName); + InstancePartitionsUtils.removeInstancePartitions(_helixManager.getHelixPropertyStore(), + instancePartitionsName); + } + return Pair.of(instancePartitions, noExistingInstancePartitions); } - InstancePartitions instancePartitions = - InstancePartitionsUtils.computeDefaultInstancePartitions(_helixManager, tableConfig, instancePartitionsType); - if (!dryRun) { - String instancePartitionsName = instancePartitions.getInstancePartitionsName(); - LOGGER.info("Removing instance partitions: {} from ZK if it exists", instancePartitionsName); - InstancePartitionsUtils.removeInstancePartitions(_helixManager.getHelixPropertyStore(), instancePartitionsName); - } - return instancePartitions; + } else { + LOGGER.info("Fetching/computing {} instance partitions for table: {}", instancePartitionsType, tableNameWithType); + return Pair.of( + InstancePartitionsUtils.fetchOrComputeInstancePartitions(_helixManager, tableConfig, instancePartitionsType), + true); } } @@ -522,32 +619,86 @@ private List getSortedTiers(TableConfig tableConfig) { } } - @Nullable - private Map getTierToInstancePartitionsMap(String tableNameWithType, - @Nullable List sortedTiers) { + /** + * Fetches/computes the instance partitions for sorted tiers and also returns a boolean for whether the + * instance partitions are unchanged. + */ + private Pair, Boolean> getTierToInstancePartitionsMap(TableConfig tableConfig, + @Nullable List sortedTiers, boolean reassignInstances, boolean bootstrap, boolean dryRun) { if (sortedTiers == null) { - return null; + return Pair.of(null, true); } + boolean instancePartitionsUnchanged = true; Map tierToInstancePartitionsMap = new HashMap<>(); for (Tier tier : sortedTiers) { LOGGER.info("Fetching/computing instance partitions for tier: {} of table: {}", tier.getName(), - tableNameWithType); - tierToInstancePartitionsMap.put(tier.getName(), getInstancePartitionsForTier(tier, tableNameWithType)); + tableConfig.getTableName()); + Pair partitionsAndUnchanged = + getInstancePartitionsForTier(tableConfig, tier, reassignInstances, bootstrap, dryRun); + tierToInstancePartitionsMap.put(tier.getName(), partitionsAndUnchanged.getLeft()); + instancePartitionsUnchanged = instancePartitionsUnchanged && partitionsAndUnchanged.getRight(); } - return tierToInstancePartitionsMap; + return Pair.of(tierToInstancePartitionsMap, instancePartitionsUnchanged); } /** - * Creates a default instance assignment for the tier. - * TODO: We only support default server-tag based assignment currently. - * In next iteration, we will add InstanceAssignmentConfig to the TierConfig and also support persisting of the - * InstancePartitions to zk. - * Then we'll be able to support replica group assignment while creating InstancePartitions for tiers + * Computes the instance partitions for the given tier. If table's instanceAssignmentConfigMap has an entry for the + * tier, it's used to calculate the instance partitions. Else default instance partitions are returned. Also returns + * a boolean for whether the instance partition is unchanged. */ - private InstancePartitions getInstancePartitionsForTier(Tier tier, String tableNameWithType) { - PinotServerTierStorage storage = (PinotServerTierStorage) tier.getStorage(); - return InstancePartitionsUtils.computeDefaultInstancePartitionsForTag(_helixManager, tableNameWithType, - tier.getName(), storage.getServerTag()); + private Pair getInstancePartitionsForTier(TableConfig tableConfig, Tier tier, + boolean reassignInstances, boolean bootstrap, boolean dryRun) { + String tableNameWithType = tableConfig.getTableName(); + String tierName = tier.getName(); + String instancePartitionsName = + InstancePartitionsUtils.getInstancePartitionsNameForTier(tableNameWithType, tierName); + InstancePartitions existingInstancePartitions = + InstancePartitionsUtils.fetchInstancePartitions(_helixManager.getHelixPropertyStore(), instancePartitionsName); + + if (reassignInstances) { + Map instanceAssignmentConfigMap = tableConfig.getInstanceAssignmentConfigMap(); + InstanceAssignmentConfig instanceAssignmentConfig = + instanceAssignmentConfigMap != null ? instanceAssignmentConfigMap.get(tierName) : null; + if (instanceAssignmentConfig == null) { + LOGGER.info( + "Instance assignment config for tier: {} does not exist for table: {}, using default instance partitions", + tierName, tableNameWithType); + PinotServerTierStorage storage = (PinotServerTierStorage) tier.getStorage(); + InstancePartitions instancePartitions = + InstancePartitionsUtils.computeDefaultInstancePartitionsForTag(_helixManager, tableNameWithType, tierName, + storage.getServerTag()); + boolean noExistingInstancePartitions = existingInstancePartitions == null; + if (!dryRun && !noExistingInstancePartitions) { + LOGGER.info("Removing instance partitions: {} from ZK", instancePartitionsName); + InstancePartitionsUtils.removeInstancePartitions(_helixManager.getHelixPropertyStore(), + instancePartitionsName); + } + return Pair.of(instancePartitions, noExistingInstancePartitions); + } else { + InstanceAssignmentDriver instanceAssignmentDriver = new InstanceAssignmentDriver(tableConfig); + // Assign instances with existing instance partition to null if bootstrap mode is enabled, so that the instance + // partition map can be fully recalculated. + InstancePartitions instancePartitions = instanceAssignmentDriver.assignInstances(tierName, + _helixDataAccessor.getChildValues(_helixDataAccessor.keyBuilder().instanceConfigs(), true), + bootstrap ? null : existingInstancePartitions, instanceAssignmentConfig); + boolean instancePartitionsUnchanged = instancePartitions.equals(existingInstancePartitions); + if (!dryRun && !instancePartitionsUnchanged) { + LOGGER.info("Persisting instance partitions: {} to ZK", instancePartitions); + InstancePartitionsUtils.persistInstancePartitions(_helixManager.getHelixPropertyStore(), instancePartitions); + } + return Pair.of(instancePartitions, instancePartitionsUnchanged); + } + } else { + if (existingInstancePartitions != null) { + return Pair.of(existingInstancePartitions, true); + } else { + PinotServerTierStorage storage = (PinotServerTierStorage) tier.getStorage(); + InstancePartitions instancePartitions = + InstancePartitionsUtils.computeDefaultInstancePartitionsForTag(_helixManager, tableNameWithType, tierName, + storage.getServerTag()); + return Pair.of(instancePartitions, true); + } + } } private IdealState waitForExternalViewToConverge(String tableNameWithType, boolean bestEfforts, @@ -566,6 +717,10 @@ private IdealState waitForExternalViewToConverge(String tableNameWithType, boole _helixDataAccessor.getProperty(_helixDataAccessor.keyBuilder().externalView(tableNameWithType)); // ExternalView might be null when table is just created, skipping check for this iteration if (externalView != null) { + // Record external view and ideal state convergence status + _tableRebalanceObserver.onTrigger( + TableRebalanceObserver.Trigger.EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER, + externalView.getRecord().getMapFields(), idealState.getRecord().getMapFields()); if (isExternalViewConverged(tableNameWithType, externalView.getRecord().getMapFields(), idealState.getRecord().getMapFields(), bestEfforts, segmentsToMonitor)) { LOGGER.info("ExternalView converged for table: {}", tableNameWithType); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/ZkBasedTableRebalanceObserver.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/ZkBasedTableRebalanceObserver.java new file mode 100644 index 00000000000..5db4bebf588 --- /dev/null +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/ZkBasedTableRebalanceObserver.java @@ -0,0 +1,197 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.rebalance; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Preconditions; +import java.util.HashMap; +import java.util.Map; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.controllerjob.ControllerJobType; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.RebalanceConfigConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ZkBasedTableRebalanceObserver observes rebalance progress and tracks rebalance status, + * stats in Zookeeper. This will be used to show the progress of rebalance to users via rebalanceStatus API. + */ +public class ZkBasedTableRebalanceObserver implements TableRebalanceObserver { + private static final Logger LOGGER = LoggerFactory.getLogger(ZkBasedTableRebalanceObserver.class); + private final String _tableNameWithType; + private final String _rebalanceJobId; + private final PinotHelixResourceManager _pinotHelixResourceManager; + private TableRebalanceProgressStats _tableRebalanceProgressStats; + // Keep track of number of updates. Useful during debugging. + private int _numUpdatesToZk; + + public ZkBasedTableRebalanceObserver(String tableNameWithType, String rebalanceJobId, + PinotHelixResourceManager pinotHelixResourceManager) { + Preconditions.checkState(tableNameWithType != null, "Table name cannot be null"); + Preconditions.checkState(rebalanceJobId != null, "rebalanceId cannot be null"); + Preconditions.checkState(pinotHelixResourceManager != null, "PinotHelixManager cannot be null"); + _tableNameWithType = tableNameWithType; + _rebalanceJobId = rebalanceJobId; + _pinotHelixResourceManager = pinotHelixResourceManager; + _tableRebalanceProgressStats = new TableRebalanceProgressStats(); + _numUpdatesToZk = 0; + } + + @Override + public void onTrigger(Trigger trigger, Map> currentState, + Map> targetState) { + switch (trigger) { + case START_TRIGGER: + updateOnStart(currentState, targetState); + trackStatsInZk(); + break; + // Write to Zk if there's change since previous stats computation + case IDEAL_STATE_CHANGE_TRIGGER: + TableRebalanceProgressStats.RebalanceStateStats latest = + getDifferenceBetweenTableRebalanceStates(targetState, currentState); + if (TableRebalanceProgressStats.statsDiffer(_tableRebalanceProgressStats.getCurrentToTargetConvergence(), + latest)) { + _tableRebalanceProgressStats.setCurrentToTargetConvergence(latest); + trackStatsInZk(); + } + break; + case EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER: + latest = getDifferenceBetweenTableRebalanceStates(targetState, currentState); + if (TableRebalanceProgressStats.statsDiffer( + _tableRebalanceProgressStats.getExternalViewToIdealStateConvergence(), latest)) { + _tableRebalanceProgressStats.setExternalViewToIdealStateConvergence(latest); + trackStatsInZk(); + } + break; + default: + throw new IllegalArgumentException("Unimplemented trigger: " + trigger); + } + } + + private void updateOnStart(Map> currentState, + Map> targetState) { + Preconditions.checkState(_tableRebalanceProgressStats.getStatus() != RebalanceResult.Status.IN_PROGRESS.toString(), + "Rebalance Observer onStart called multiple times"); + _tableRebalanceProgressStats.setStatus(RebalanceResult.Status.IN_PROGRESS.toString()); + _tableRebalanceProgressStats.setInitialToTargetStateConvergence( + getDifferenceBetweenTableRebalanceStates(targetState, currentState)); + _tableRebalanceProgressStats.setStartTimeMs(System.currentTimeMillis()); + } + + @Override + public void onSuccess(String msg) { + Preconditions.checkState(_tableRebalanceProgressStats.getStatus() != RebalanceResult.Status.DONE.toString(), + "Table Rebalance already completed"); + long timeToFinishInSeconds = + (System.currentTimeMillis() - _tableRebalanceProgressStats.getStartTimeMs()) / 1000L; + _tableRebalanceProgressStats.setCompletionStatusMsg(msg); + _tableRebalanceProgressStats.setTimeToFinishInSeconds(timeToFinishInSeconds); + _tableRebalanceProgressStats.setStatus(RebalanceResult.Status.DONE.toString()); + // Zero out the in_progress convergence stats + TableRebalanceProgressStats.RebalanceStateStats stats = new TableRebalanceProgressStats.RebalanceStateStats(); + _tableRebalanceProgressStats.setExternalViewToIdealStateConvergence(stats); + _tableRebalanceProgressStats.setCurrentToTargetConvergence(stats); + trackStatsInZk(); + } + + @Override + public void onError(String errorMsg) { + long timeToFinishInSeconds = + (long) (System.currentTimeMillis() - _tableRebalanceProgressStats.getStartTimeMs()) / 1000; + _tableRebalanceProgressStats.setTimeToFinishInSeconds(timeToFinishInSeconds); + _tableRebalanceProgressStats.setStatus(RebalanceResult.Status.FAILED.toString()); + _tableRebalanceProgressStats.setCompletionStatusMsg(errorMsg); + trackStatsInZk(); + } + + public int getNumUpdatesToZk() { + return _numUpdatesToZk; + } + + private void trackStatsInZk() { + Map jobMetadata = new HashMap<>(); + jobMetadata.put(CommonConstants.ControllerJob.TABLE_NAME_WITH_TYPE, _tableNameWithType); + jobMetadata.put(CommonConstants.ControllerJob.JOB_ID, _rebalanceJobId); + jobMetadata.put(CommonConstants.ControllerJob.SUBMISSION_TIME_MS, Long.toString(System.currentTimeMillis())); + jobMetadata.put(CommonConstants.ControllerJob.JOB_TYPE, ControllerJobType.TABLE_REBALANCE.name()); + try { + jobMetadata.put(RebalanceConfigConstants.REBALANCE_PROGRESS_STATS, + JsonUtils.objectToString(_tableRebalanceProgressStats)); + } catch (JsonProcessingException e) { + LOGGER.error("Error serialising rebalance stats to JSON for persisting to ZK {}", _rebalanceJobId, e); + } + _pinotHelixResourceManager.addControllerJobToZK(_rebalanceJobId, jobMetadata, + ZKMetadataProvider.constructPropertyStorePathForControllerJob(ControllerJobType.TABLE_REBALANCE)); + _numUpdatesToZk++; + LOGGER.debug("Number of updates to Zk: {} for rebalanceJob: {} ", _numUpdatesToZk, _rebalanceJobId); + } + + /** + * Takes in targetState and sourceState and computes stats based on the comparison + * between sourceState and targetState.This captures how far the source state is from + * the target state. Example - if there are 4 segments and 16 replicas in the source state + * not matching the target state, _segmentsToRebalance is 4 and _replicasToRebalance is 16. + * @param targetState- The state that we want to get to + * @param sourceState - A given state that needs to converge to targetState + * @return RebalanceStats + */ + public static TableRebalanceProgressStats.RebalanceStateStats getDifferenceBetweenTableRebalanceStates( + Map> targetState, Map> sourceState) { + + TableRebalanceProgressStats.RebalanceStateStats rebalanceStats = + new TableRebalanceProgressStats.RebalanceStateStats(); + + for (Map.Entry> entry : targetState.entrySet()) { + String segmentName = entry.getKey(); + Map sourceInstanceStateMap = sourceState.get(segmentName); + if (sourceInstanceStateMap == null) { + // Skip the missing segment + rebalanceStats._segmentsMissing++; + rebalanceStats._segmentsToRebalance++; + continue; + } + Map targetStateInstanceStateMap = entry.getValue(); + boolean hasSegmentConverged = true; + for (Map.Entry instanceStateEntry : targetStateInstanceStateMap.entrySet()) { + // Ignore OFFLINE state in target state + String targetStateInstanceState = instanceStateEntry.getValue(); + if (targetStateInstanceState.equals(CommonConstants.Helix.StateModel.SegmentStateModel.OFFLINE)) { + continue; + } + // Check whether the instance state in source matches the target + String instanceName = instanceStateEntry.getKey(); + String sourceInstanceState = sourceInstanceStateMap.get(instanceName); + if (!targetStateInstanceState.equals(sourceInstanceState)) { + rebalanceStats._replicasToRebalance++; + hasSegmentConverged = false; + } + } + if (!hasSegmentConverged) { + rebalanceStats._segmentsToRebalance++; + } + } + int totalSegments = targetState.size(); + rebalanceStats._percentSegmentsToRebalance = + (totalSegments == 0) ? 0 : ((double) rebalanceStats._segmentsToRebalance / totalSegments) * 100.0; + return rebalanceStats; + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocator.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocator.java index 4e2d50db9ea..cd481ec66df 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocator.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocator.java @@ -21,10 +21,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; @@ -38,13 +36,9 @@ import org.apache.helix.ClusterMessagingService; import org.apache.helix.Criteria; import org.apache.helix.InstanceType; -import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.assignment.InstanceAssignmentConfigUtils; import org.apache.pinot.common.messages.SegmentReloadMessage; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ControllerMetrics; -import org.apache.pinot.common.tier.Tier; -import org.apache.pinot.common.tier.TierSegmentSelector; import org.apache.pinot.common.utils.config.TierConfigUtils; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.LeadControllerManager; @@ -54,7 +48,6 @@ import org.apache.pinot.controller.helix.core.rebalance.TableRebalancer; import org.apache.pinot.controller.util.TableTierReader; import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.utils.IngestionConfigUtils; import org.apache.pinot.spi.utils.RebalanceConfigConstants; @@ -175,10 +168,6 @@ private void rebalanceTable(String tableNameWithType) { relocate = true; LOGGER.info("Relocating COMPLETED segments for table: {}", tableNameWithType); } - if (_enableLocalTierMigration) { - relocate = true; - LOGGER.info("Migrating segment tiers on servers locally for table: {}", tableNameWithType); - } if (!relocate) { LOGGER.debug("No need to relocate segments of table: {}", tableNameWithType); return; @@ -191,22 +180,18 @@ private void rebalanceTable(String tableNameWithType) { _externalViewCheckIntervalInMs); rebalanceConfig.addProperty(RebalanceConfigConstants.EXTERNAL_VIEW_STABILIZATION_TIMEOUT_IN_MS, _externalViewStabilizationTimeoutInMs); + rebalanceConfig.addProperty(RebalanceConfigConstants.UPDATE_TARGET_TIER, + TierConfigUtils.shouldRelocateToTiers(tableConfig)); + rebalanceConfig.addProperty(RebalanceConfigConstants.JOB_ID, TableRebalancer.createUniqueRebalanceJobIdentifier()); try { // Relocating segments to new tiers needs two sequential actions: table rebalance and local tier migration. // Table rebalance moves segments to the new ideal servers, which can change for a segment when its target // tier is updated. New servers can put segments onto the right tier when loading the segments. After that, // all segments are put on the right servers. If any segments are not on their target tier, the server local - // tier migration is triggered for them, basically asking the hosting servers to reload them. - // - // We assume segment target tier is not changed between the two actions, so update target tier here as well, - // instead of using a separate task. - // TODO: can add some sanity checks on the server side when reloading segments to be more defensive, e.g. only - // migrating segment to new tier when the hosting server is in the server pool configured for that tier. - updateTargetTier(tableNameWithType); - - RebalanceResult rebalance = - new TableRebalancer(_pinotHelixResourceManager.getHelixZkManager()).rebalance(tableConfig, rebalanceConfig); + // tier migration is triggered for them, basically asking the hosting servers to reload them. The segment + // target tier may get changed between the two sequential actions, but cluster states converge eventually. + RebalanceResult rebalance = _pinotHelixResourceManager.rebalanceTable(tableNameWithType, rebalanceConfig, false); switch (rebalance.getStatus()) { case NO_OP: LOGGER.info("All segments are already relocated for table: {}", tableNameWithType); @@ -225,26 +210,6 @@ private void rebalanceTable(String tableNameWithType) { } } - /** - * Calculate the target tier the segment belongs to and set it in segment ZK metadata as goal state, which can be - * checked by servers when loading the segment to put it onto the target storage tier. - */ - private void updateTargetTier(String tableNameWithType) { - if (!_enableLocalTierMigration) { - LOGGER.debug("Skipping updating target tier for segments of table: {}", tableNameWithType); - return; - } - TableConfig tableConfig = _pinotHelixResourceManager.getTableConfig(tableNameWithType); - Preconditions.checkState(tableConfig != null, "Failed to find table config for table: {}", tableNameWithType); - List tierCfgs = tableConfig.getTierConfigsList(); - List sortedTiers = tierCfgs == null ? Collections.emptyList() - : TierConfigUtils.getSortedTiers(tierCfgs, _pinotHelixResourceManager.getHelixZkManager()); - LOGGER.info("Updating target tiers for segments of table: {} with tierConfigs: {}", tableNameWithType, sortedTiers); - for (String segmentName : _pinotHelixResourceManager.getSegmentsFor(tableNameWithType, true)) { - updateSegmentTargetTier(tableNameWithType, segmentName, sortedTiers, _pinotHelixResourceManager); - } - } - /** * Migrate segment tiers on their hosting servers locally. Once table is balanced, i.e. segments are on their ideal * servers, we check if any segment needs to move to a new tier on its hosting servers, i.e. doing local tier @@ -268,46 +233,6 @@ private void migrateToTargetTier(String tableNameWithType) { } } - @VisibleForTesting - static void updateSegmentTargetTier(String tableNameWithType, String segmentName, List sortedTiers, - PinotHelixResourceManager pinotHelixResourceManager) { - ZNRecord segmentMetadataZNRecord = - pinotHelixResourceManager.getSegmentMetadataZnRecord(tableNameWithType, segmentName); - if (segmentMetadataZNRecord == null) { - LOGGER.debug("No ZK metadata for segment: {} of table: {}", segmentName, tableNameWithType); - return; - } - Tier targetTier = null; - for (Tier tier : sortedTiers) { - TierSegmentSelector tierSegmentSelector = tier.getSegmentSelector(); - if (tierSegmentSelector.selectSegment(tableNameWithType, segmentName)) { - targetTier = tier; - break; - } - } - SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segmentMetadataZNRecord); - String targetTierName = null; - if (targetTier == null) { - if (segmentZKMetadata.getTier() == null) { - LOGGER.debug("Segment: {} of table: {} is already set to go to default tier", segmentName, tableNameWithType); - return; - } - LOGGER.info("Segment: {} of table: {} is put back on default tier", segmentName, tableNameWithType); - } else { - targetTierName = targetTier.getName(); - if (targetTierName.equals(segmentZKMetadata.getTier())) { - LOGGER.debug("Segment: {} of table: {} is already set to go to target tier: {}", segmentName, tableNameWithType, - targetTierName); - return; - } - LOGGER.info("Segment: {} of table: {} is put onto new tier: {}", segmentName, tableNameWithType, targetTierName); - } - // Update the tier in segment ZK metadata and write it back to ZK. - segmentZKMetadata.setTier(targetTierName); - pinotHelixResourceManager.updateZkMetadata(tableNameWithType, segmentZKMetadata, - segmentMetadataZNRecord.getVersion()); - } - @VisibleForTesting static void triggerLocalTierMigration(String tableNameWithType, TableTierReader.TableTierDetails tableTiers, ClusterMessagingService messagingService) { diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java index 624ee914d7c..93c7d8573b7 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java @@ -26,8 +26,6 @@ import java.util.concurrent.TimeUnit; import org.apache.helix.model.IdealState; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.lineage.LineageEntry; -import org.apache.pinot.common.lineage.LineageEntryState; import org.apache.pinot.common.lineage.SegmentLineage; import org.apache.pinot.common.lineage.SegmentLineageAccessHelper; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; @@ -58,8 +56,6 @@ */ public class RetentionManager extends ControllerPeriodicTask { public static final long OLD_LLC_SEGMENTS_RETENTION_IN_MILLIS = TimeUnit.DAYS.toMillis(5L); - private static final long REPLACED_SEGMENTS_RETENTION_IN_MILLIS = TimeUnit.DAYS.toMillis(1L); // 1 day - public static final long LINEAGE_ENTRY_CLEANUP_RETENTION_IN_MILLIS = TimeUnit.DAYS.toMillis(1L); // 1 day private static final RetryPolicy DEFAULT_RETRY_POLICY = RetryPolicies.exponentialBackoffRetryPolicy(5, 1000L, 2.0f); private static final Logger LOGGER = LoggerFactory.getLogger(RetentionManager.class); @@ -94,7 +90,7 @@ protected void processTable(String tableNameWithType) { @Override protected void postprocess() { LOGGER.info("Removing aged deleted segments for all tables"); - _pinotHelixResourceManager.getSegmentDeletionManager().removeAgedDeletedSegments(); + _pinotHelixResourceManager.getSegmentDeletionManager().removeAgedDeletedSegments(_leadControllerManager); } private void manageRetentionForTable(TableConfig tableConfig) { @@ -163,6 +159,10 @@ private void manageRetentionForRealtimeTable(String realtimeTableName, Retention } } } + + // Remove last sealed segments such that the table can still create new consuming segments if it's paused + segmentsToDelete.removeAll(_pinotHelixResourceManager.getLastLLCCompletedSegments(realtimeTableName)); + if (!segmentsToDelete.isEmpty()) { LOGGER.info("Deleting {} segments from table: {}", segmentsToDelete.size(), realtimeTableName); _pinotHelixResourceManager.deleteSegments(realtimeTableName, segmentsToDelete); @@ -198,8 +198,9 @@ private void manageSegmentLineageCleanupForTable(TableConfig tableConfig) { try { DEFAULT_RETRY_POLICY.attempt(() -> { // Fetch segment lineage - ZNRecord segmentLineageZNRecord = SegmentLineageAccessHelper - .getSegmentLineageZNRecord(_pinotHelixResourceManager.getPropertyStore(), tableNameWithType); + ZNRecord segmentLineageZNRecord = + SegmentLineageAccessHelper.getSegmentLineageZNRecord(_pinotHelixResourceManager.getPropertyStore(), + tableNameWithType); if (segmentLineageZNRecord == null) { return true; } @@ -208,50 +209,19 @@ private void manageSegmentLineageCleanupForTable(TableConfig tableConfig) { SegmentLineage segmentLineage = SegmentLineage.fromZNRecord(segmentLineageZNRecord); int expectedVersion = segmentLineageZNRecord.getVersion(); - // 1. The original segments can be deleted once the merged segments are successfully uploaded - // 2. The zombie lineage entry & merged segments should be deleted if the segment replacement failed in - // the middle - Set segmentsForTable = - new HashSet<>(_pinotHelixResourceManager.getSegmentsFor(tableNameWithType, false)); + List segmentsForTable = _pinotHelixResourceManager.getSegmentsFor(tableNameWithType, false); List segmentsToDelete = new ArrayList<>(); - for (String lineageEntryId : segmentLineage.getLineageEntryIds()) { - LineageEntry lineageEntry = segmentLineage.getLineageEntry(lineageEntryId); - if (lineageEntry.getState() == LineageEntryState.COMPLETED) { - Set sourceSegments = new HashSet<>(lineageEntry.getSegmentsFrom()); - sourceSegments.retainAll(segmentsForTable); - if (sourceSegments.isEmpty()) { - // If the lineage state is 'COMPLETED' and segmentFrom are removed, it is safe clean up - // the lineage entry - segmentLineage.deleteLineageEntry(lineageEntryId); - } else { - // If the lineage state is 'COMPLETED' and we already preserved the original segments for the required - // retention, it is safe to delete all segments from 'segmentsFrom' - if (shouldDeleteReplacedSegments(tableConfig, lineageEntry)) { - segmentsToDelete.addAll(sourceSegments); - } - } - } else if (lineageEntry.getState() == LineageEntryState.REVERTED || ( - lineageEntry.getState() == LineageEntryState.IN_PROGRESS && lineageEntry.getTimestamp() - < System.currentTimeMillis() - LINEAGE_ENTRY_CLEANUP_RETENTION_IN_MILLIS)) { - // If the lineage state is 'IN_PROGRESS' or 'REVERTED', we need to clean up the zombie lineage - // entry and its segments - Set destinationSegments = new HashSet<>(lineageEntry.getSegmentsTo()); - destinationSegments.retainAll(segmentsForTable); - if (destinationSegments.isEmpty()) { - // If the lineage state is 'IN_PROGRESS or REVERTED' and source segments are already removed, it is safe - // to clean up the lineage entry. Deleting lineage will allow the task scheduler to re-schedule the source - // segments to be merged again. - segmentLineage.deleteLineageEntry(lineageEntryId); - } else { - // If the lineage state is 'IN_PROGRESS', it is safe to delete all segments from 'segmentsTo' - segmentsToDelete.addAll(destinationSegments); - } - } - } + _pinotHelixResourceManager.getLineageManager() + .updateLineageForRetention(tableConfig, segmentLineage, segmentsForTable, segmentsToDelete, + _pinotHelixResourceManager.getConsumingSegments(tableNameWithType)); // Write back to the lineage entry - if (SegmentLineageAccessHelper - .writeSegmentLineage(_pinotHelixResourceManager.getPropertyStore(), segmentLineage, expectedVersion)) { + if (SegmentLineageAccessHelper.writeSegmentLineage(_pinotHelixResourceManager.getPropertyStore(), + segmentLineage, expectedVersion)) { + // Remove last sealed segments such that the table can still create new consuming segments if it's paused + if (TableNameBuilder.isRealtimeTableResource(tableNameWithType)) { + segmentsToDelete.removeAll(_pinotHelixResourceManager.getLastLLCCompletedSegments(tableNameWithType)); + } // Delete segments based on the segment lineage if (!segmentsToDelete.isEmpty()) { _pinotHelixResourceManager.deleteSegments(tableNameWithType, segmentsToDelete); @@ -272,26 +242,4 @@ private void manageSegmentLineageCleanupForTable(TableConfig tableConfig) { } LOGGER.info("Segment lineage metadata clean-up is successfully processed for table: {}", tableNameWithType); } - - /** - * Helper function to decide whether we should delete segmentsFrom (replaced segments) given a lineage entry. - * - * The replaced segments are safe to delete if the following conditions are all satisfied - * 1) Table is "APPEND" - * 2) It has been more than 24 hours since the lineage entry became "COMPLETED" state. - * - * @param tableConfig a table config - * @param lineageEntry lineage entry - * @return True if we can safely delete the replaced segments. False otherwise. - */ - private boolean shouldDeleteReplacedSegments(TableConfig tableConfig, LineageEntry lineageEntry) { - // TODO: Currently, we preserve the replaced segments for 1 day for REFRESH tables only. Once we support - // data rollback for APPEND tables, we should remove this check. - String batchSegmentIngestionType = IngestionConfigUtils.getBatchSegmentIngestionType(tableConfig); - if (!batchSegmentIngestionType.equalsIgnoreCase("REFRESH") - || lineageEntry.getTimestamp() < System.currentTimeMillis() - REPLACED_SEGMENTS_RETENTION_IN_MILLIS) { - return true; - } - return false; - } } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java index d42fe6a36bf..ea4d8885893 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java @@ -79,7 +79,7 @@ private static void setupHelixClusterIfNeeded(String helixClusterName, String zk new HelixConfigScopeBuilder(ConfigScopeProperty.CLUSTER).forCluster(helixClusterName).build(); Map configMap = new HashMap<>(); configMap.put(ZKHelixManager.ALLOW_PARTICIPANT_AUTO_JOIN, Boolean.toString(true)); - configMap.put(ENABLE_CASE_INSENSITIVE_KEY, Boolean.toString(false)); + configMap.put(ENABLE_CASE_INSENSITIVE_KEY, Boolean.toString(DEFAULT_ENABLE_CASE_INSENSITIVE)); configMap.put(DEFAULT_HYPERLOGLOG_LOG2M_KEY, Integer.toString(DEFAULT_HYPERLOGLOG_LOG2M)); configMap.put(CommonConstants.Broker.CONFIG_OF_ENABLE_QUERY_LIMIT_OVERRIDE, Boolean.toString(false)); admin.setConfig(configScope, configMap); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/io/InputManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/io/InputManager.java index 4436fc356c9..4834866c518 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/io/InputManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/io/InputManager.java @@ -170,6 +170,8 @@ private void validateQueries() { for (String queryString : _queryWeightMap.keySet()) { try { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(queryString); + // TODO: we should catch and log errors here so we don't fail queries on optimization. + // For now, because this modifies the query in place, we let the error propagate. _queryOptimizer.optimize(pinotQuery, _schema); QueryContext queryContext = QueryContextConverterUtils.getQueryContext(pinotQuery); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/realtime/provisioning/MemoryEstimator.java b/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/realtime/provisioning/MemoryEstimator.java index baa2ea4b52b..9873d17ad81 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/realtime/provisioning/MemoryEstimator.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/recommender/realtime/provisioning/MemoryEstimator.java @@ -23,14 +23,12 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.controller.recommender.data.generator.DataGenerator; @@ -52,7 +50,9 @@ import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.data.DateTimeFormatSpec; import org.apache.pinot.spi.data.FieldSpec; @@ -77,22 +77,20 @@ public class MemoryEstimator { private final TableConfig _tableConfig; private final String _tableNameWithType; + private final Schema _schema; private final File _sampleCompletedSegment; private final long _sampleSegmentConsumedSeconds; private final int _totalDocsInSampleSegment; private final long _maxUsableHostMemory; private final int _tableRetentionHours; - private SegmentMetadataImpl _segmentMetadata; - private long _sampleCompletedSegmentSizeBytes; - private Set _invertedIndexColumns = new HashSet<>(); - private Set _noDictionaryColumns = new HashSet<>(); - private Set _varLengthDictionaryColumns = new HashSet<>(); + private final SegmentMetadataImpl _segmentMetadata; + private final long _sampleCompletedSegmentSizeBytes; int _avgMultiValues; // Working dir will contain statsFile and also the generated segment if requested. // It will get deleted after memory estimation is done. - private File _workingDir; + private final File _workingDir; private String[][] _activeMemoryPerHost; private String[][] _optimalSegmentSize; @@ -103,11 +101,12 @@ public class MemoryEstimator { /** * Constructor used for processing the given completed segment */ - public MemoryEstimator(TableConfig tableConfig, File sampleCompletedSegment, double ingestionRatePerPartition, - long maxUsableHostMemory, int tableRetentionHours, File workingDir) { + public MemoryEstimator(TableConfig tableConfig, Schema schema, File sampleCompletedSegment, + double ingestionRatePerPartition, long maxUsableHostMemory, int tableRetentionHours, File workingDir) { _maxUsableHostMemory = maxUsableHostMemory; _tableConfig = tableConfig; _tableNameWithType = tableConfig.getTableName(); + _schema = schema; _sampleCompletedSegment = sampleCompletedSegment; _tableRetentionHours = tableRetentionHours; @@ -120,15 +119,6 @@ public MemoryEstimator(TableConfig tableConfig, File sampleCompletedSegment, dou _totalDocsInSampleSegment = _segmentMetadata.getTotalDocs(); _sampleSegmentConsumedSeconds = (int) (_totalDocsInSampleSegment / ingestionRatePerPartition); - if (CollectionUtils.isNotEmpty(_tableConfig.getIndexingConfig().getNoDictionaryColumns())) { - _noDictionaryColumns.addAll(_tableConfig.getIndexingConfig().getNoDictionaryColumns()); - } - if (CollectionUtils.isNotEmpty(_tableConfig.getIndexingConfig().getVarLengthDictionaryColumns())) { - _varLengthDictionaryColumns.addAll(_tableConfig.getIndexingConfig().getVarLengthDictionaryColumns()); - } - if (CollectionUtils.isNotEmpty(_tableConfig.getIndexingConfig().getInvertedIndexColumns())) { - _invertedIndexColumns.addAll(_tableConfig.getIndexingConfig().getInvertedIndexColumns()); - } _avgMultiValues = getAvgMultiValues(); _workingDir = workingDir; } @@ -139,7 +129,8 @@ public MemoryEstimator(TableConfig tableConfig, File sampleCompletedSegment, dou public MemoryEstimator(TableConfig tableConfig, Schema schema, SchemaWithMetaData schemaWithMetadata, int numberOfRows, double ingestionRatePerPartition, long maxUsableHostMemory, int tableRetentionHours, File workingDir) { - this(tableConfig, generateCompletedSegment(schemaWithMetadata, schema, tableConfig, numberOfRows, workingDir), + this(tableConfig, schema, + generateCompletedSegment(schemaWithMetadata, schema, tableConfig, numberOfRows, workingDir), ingestionRatePerPartition, maxUsableHostMemory, tableRetentionHours, workingDir); } @@ -167,13 +158,11 @@ public File initializeStatsHistory() { // create a config RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder = - new RealtimeSegmentConfig.Builder().setTableNameWithType(_tableNameWithType) + new RealtimeSegmentConfig.Builder(_tableConfig, _schema).setTableNameWithType(_tableNameWithType) .setSegmentName(_segmentMetadata.getName()).setStreamName(_tableNameWithType) .setSchema(_segmentMetadata.getSchema()).setCapacity(_segmentMetadata.getTotalDocs()) - .setAvgNumMultiValues(_avgMultiValues).setNoDictionaryColumns(_noDictionaryColumns) - .setVarLengthDictionaryColumns(_varLengthDictionaryColumns).setInvertedIndexColumns(_invertedIndexColumns) - .setSegmentZKMetadata(segmentZKMetadata).setOffHeap(true).setMemoryManager(memoryManager) - .setStatsHistory(sampleStatsHistory); + .setAvgNumMultiValues(_avgMultiValues).setSegmentZKMetadata(segmentZKMetadata).setOffHeap(true) + .setMemoryManager(memoryManager).setStatsHistory(sampleStatsHistory); // create mutable segment impl MutableSegmentImpl mutableSegmentImpl = new MutableSegmentImpl(realtimeSegmentConfigBuilder.build(), null); @@ -256,12 +245,14 @@ public void estimateMemoryUsed(File statsFile, int[] numHosts, int[] numHours, f } try { + int invertedColumnsCount = countInvertedColumns(); + for (int i = 0; i < numHours.length; i++) { int numHoursToConsume = numHours[i]; if (numHoursToConsume > retentionHours) { continue; } - long secondsToConsume = numHoursToConsume * 3600; + long secondsToConsume = numHoursToConsume * 3600L; // consuming for _numHoursSampleSegmentConsumed, gives size sampleCompletedSegmentSizeBytes // hence, consuming for numHoursToConsume would give: long completedSegmentSizeBytes = @@ -272,7 +263,8 @@ public void estimateMemoryUsed(File statsFile, int[] numHosts, int[] numHours, f int totalDocs = (int) (((double) secondsToConsume / _sampleSegmentConsumedSeconds) * _totalDocsInSampleSegment); long memoryForConsumingSegmentPerPartition = getMemoryForConsumingSegmentPerPartition(statsFile, totalDocs); - memoryForConsumingSegmentPerPartition += getMemoryForInvertedIndex(memoryForConsumingSegmentPerPartition); + memoryForConsumingSegmentPerPartition += getMemoryForInvertedIndex( + memoryForConsumingSegmentPerPartition, invertedColumnsCount); int numActiveSegmentsPerPartition = (retentionHours + numHoursToConsume - 1) / numHoursToConsume; long activeMemoryForCompletedSegmentsPerPartition = @@ -329,11 +321,10 @@ private long getMemoryForConsumingSegmentPerPartition(File statsFile, int totalD SegmentZKMetadata segmentZKMetadata = getSegmentZKMetadata(_segmentMetadata, totalDocs); RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder = - new RealtimeSegmentConfig.Builder().setTableNameWithType(_tableNameWithType) + new RealtimeSegmentConfig.Builder(_tableConfig, _schema).setTableNameWithType(_tableNameWithType) .setSegmentName(_segmentMetadata.getName()).setStreamName(_tableNameWithType) .setSchema(_segmentMetadata.getSchema()).setCapacity(totalDocs).setAvgNumMultiValues(_avgMultiValues) - .setNoDictionaryColumns(_noDictionaryColumns).setVarLengthDictionaryColumns(_varLengthDictionaryColumns) - .setInvertedIndexColumns(_invertedIndexColumns).setSegmentZKMetadata(segmentZKMetadata).setOffHeap(true) + .setSegmentZKMetadata(segmentZKMetadata).setOffHeap(true) .setMemoryManager(memoryManager).setStatsHistory(statsHistory); // create mutable segment impl @@ -384,16 +375,23 @@ private int getAvgMultiValues() { * @param totalMemoryForConsumingSegment * @return */ - private long getMemoryForInvertedIndex(long totalMemoryForConsumingSegment) { + private long getMemoryForInvertedIndex(long totalMemoryForConsumingSegment, int invertedColumnsCount) { // TODO: better way to estimate inverted indexes memory utilization long totalInvertedIndexSizeBytes = 0; - if (!_invertedIndexColumns.isEmpty()) { + if (invertedColumnsCount > 0) { long memoryForEachColumn = totalMemoryForConsumingSegment / _segmentMetadata.getAllColumns().size(); - totalInvertedIndexSizeBytes = (long) (memoryForEachColumn * 0.3 * _invertedIndexColumns.size()); + totalInvertedIndexSizeBytes = (long) (memoryForEachColumn * 0.3 * invertedColumnsCount); } return totalInvertedIndexSizeBytes; } + private int countInvertedColumns() { + Map invertedConfig = StandardIndexes.inverted().getConfig(_tableConfig, _schema); + return (int) invertedConfig.values().stream() + .filter(IndexConfig::isEnabled) + .count(); + } + /** * Creates a sample segment ZK metadata for the given segment metadata * @param segmentMetadata diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/util/CompletionServiceHelper.java b/pinot-controller/src/main/java/org/apache/pinot/controller/util/CompletionServiceHelper.java index 8ec3b282380..c311ef379f7 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/util/CompletionServiceHelper.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/util/CompletionServiceHelper.java @@ -57,7 +57,7 @@ public CompletionServiceHelper(Executor executor, HttpConnectionManager httpConn public CompletionServiceResponse doMultiGetRequest(List serverURLs, String tableNameWithType, boolean multiRequestPerServer, int timeoutMs) { - return doMultiGetRequest(serverURLs, tableNameWithType, multiRequestPerServer, null, timeoutMs); + return doMultiGetRequest(serverURLs, tableNameWithType, multiRequestPerServer, null, timeoutMs, null); } /** @@ -70,11 +70,14 @@ public CompletionServiceResponse doMultiGetRequest(List serverURLs, Stri * get response. * @param requestHeaders Headers to be set when making the http calls. * @param timeoutMs timeout in milliseconds to wait per request. + * @param useCase the use case initiating the multi-get request. If not null and an exception is thrown, only the + * error message and the use case are logged instead of the full stack trace. * @return CompletionServiceResponse Map of the endpoint(server instance, or full request path if * multiRequestPerServer is true) to the response from that endpoint. */ public CompletionServiceResponse doMultiGetRequest(List serverURLs, String tableNameWithType, - boolean multiRequestPerServer, @Nullable Map requestHeaders, int timeoutMs) { + boolean multiRequestPerServer, @Nullable Map requestHeaders, int timeoutMs, + @Nullable String useCase) { CompletionServiceResponse completionServiceResponse = new CompletionServiceResponse(); // TODO: use some service other than completion service so that we know which server encounters the error @@ -95,7 +98,8 @@ public CompletionServiceResponse doMultiGetRequest(List serverURLs, Stri completionServiceResponse._httpResponses .put(multiRequestPerServer ? uri.toString() : instance, getMethod.getResponseBodyAsString()); } catch (Exception e) { - LOGGER.error("Connection error", e); + String reason = useCase == null ? "" : String.format(" in '%s'", useCase); + LOGGER.error("Connection error{}. Details: {}", reason, e.getMessage()); completionServiceResponse._failedResponseCount++; } finally { if (getMethod != null) { @@ -114,6 +118,17 @@ public CompletionServiceResponse doMultiGetRequest(List serverURLs, Stri return completionServiceResponse; } + public CompletionServiceResponse doMultiGetRequest(List serverURLs, String tableNameWithType, + boolean multiRequestPerServer, int timeoutMs, @Nullable String useCase) { + return doMultiGetRequest(serverURLs, tableNameWithType, multiRequestPerServer, null, timeoutMs, useCase); + } + + public CompletionServiceResponse doMultiGetRequest(List serverURLs, String tableNameWithType, + boolean multiRequestPerServer, @Nullable Map requestHeaders, int timeoutMs) { + return doMultiGetRequest(serverURLs, tableNameWithType, multiRequestPerServer, requestHeaders, timeoutMs, null); + } + + /** * Helper class to maintain the completion service response to be sent back to the caller. */ diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/util/FileIngestionHelper.java b/pinot-controller/src/main/java/org/apache/pinot/controller/util/FileIngestionHelper.java index 5ef3bc43ce0..e9ee2f902a9 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/util/FileIngestionHelper.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/util/FileIngestionHelper.java @@ -92,9 +92,9 @@ public SuccessResponse buildSegmentAndPush(DataPayload payload) String tableNameWithType = _tableConfig.getTableName(); // 1. append a timestamp for easy debugging // 2. append a random string to avoid using the same working directory when multiple tasks are running in parallel - File workingDir = new File(_ingestionDir, + File workingDir = org.apache.pinot.common.utils.FileUtils.concatAndValidateFile(_ingestionDir, String.format("%s_%s_%d_%s", WORKING_DIR_PREFIX, tableNameWithType, System.currentTimeMillis(), - RandomStringUtils.random(10, true, false))); + RandomStringUtils.random(10, true, false)), "Invalid table name: %S", tableNameWithType); LOGGER.info("Starting ingestion of {} payload to table: {} using working dir: {}", payload._payloadType, tableNameWithType, workingDir.getAbsolutePath()); @@ -179,8 +179,7 @@ public static void copyURIToLocal(Map batchConfigMap, URI source String sourceFileURIScheme = sourceFileURI.getScheme(); if (!PinotFSFactory.isSchemeSupported(sourceFileURIScheme)) { PinotFSFactory.register(sourceFileURIScheme, batchConfigMap.get(BatchConfigProperties.INPUT_FS_CLASS), - IngestionConfigUtils.getInputFsProps(IngestionConfigUtils.getConfigMapWithPrefix( - batchConfigMap, BatchConfigProperties.INPUT_FS_PROP_PREFIX))); + IngestionConfigUtils.getInputFsProps(batchConfigMap)); } PinotFSFactory.create(sourceFileURIScheme).copyToLocalFile(sourceFileURI, destFile); } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/util/TableSizeReader.java b/pinot-controller/src/main/java/org/apache/pinot/controller/util/TableSizeReader.java index 5d48ad54ea9..d03160536dc 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/util/TableSizeReader.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/util/TableSizeReader.java @@ -85,21 +85,30 @@ public TableSizeDetails getTableSizeDetails(@Nonnull String tableName, @Nonnegat TableConfig realtimeTableConfig = ZKMetadataProvider.getRealtimeTableConfig(_helixResourceManager.getPropertyStore(), tableName); - if (offlineTableConfig == null && realtimeTableConfig == null) { + boolean hasRealtimeTableConfig = (realtimeTableConfig != null); + boolean hasOfflineTableConfig = (offlineTableConfig != null); + boolean isMissingAllRealtimeSegments = false; + boolean isMissingAllOfflineSegments = false; + + if (!hasRealtimeTableConfig && !hasOfflineTableConfig) { return null; } TableSizeDetails tableSizeDetails = new TableSizeDetails(tableName); - if (realtimeTableConfig != null) { + if (hasRealtimeTableConfig) { String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(tableName); tableSizeDetails._realtimeSegments = getTableSubtypeSize(realtimeTableName, timeoutMsec); - tableSizeDetails._reportedSizeInBytes += tableSizeDetails._realtimeSegments._reportedSizeInBytes; - tableSizeDetails._estimatedSizeInBytes += tableSizeDetails._realtimeSegments._estimatedSizeInBytes; - + // taking max(0,value) as values as set to -1 if all the segments are in error + tableSizeDetails._reportedSizeInBytes += Math.max(tableSizeDetails._realtimeSegments._reportedSizeInBytes, 0L); + tableSizeDetails._estimatedSizeInBytes += Math.max(tableSizeDetails._realtimeSegments._estimatedSizeInBytes, 0L); + tableSizeDetails._reportedSizePerReplicaInBytes += + Math.max(tableSizeDetails._realtimeSegments._reportedSizePerReplicaInBytes, 0L); + isMissingAllRealtimeSegments = + (tableSizeDetails._realtimeSegments._missingSegments == tableSizeDetails._realtimeSegments._segments.size()); _controllerMetrics.setValueOfTableGauge(realtimeTableName, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER, tableSizeDetails._realtimeSegments._estimatedSizeInBytes); _controllerMetrics.setValueOfTableGauge(realtimeTableName, ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER, - tableSizeDetails._realtimeSegments._estimatedSizeInBytes / _helixResourceManager.getNumReplicas( - realtimeTableConfig)); + tableSizeDetails._realtimeSegments._estimatedSizeInBytes / _helixResourceManager + .getNumReplicas(realtimeTableConfig)); long largestSegmentSizeOnServer = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; for (SegmentSizeDetails segmentSizeDetail : tableSizeDetails._realtimeSegments._segments.values()) { @@ -108,22 +117,25 @@ public TableSizeDetails getTableSizeDetails(@Nonnull String tableName, @Nonnegat } } if (largestSegmentSizeOnServer != DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { - _controllerMetrics.setValueOfTableGauge(realtimeTableName, - ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER, + _controllerMetrics.setValueOfTableGauge(realtimeTableName, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER, largestSegmentSizeOnServer); } } - if (offlineTableConfig != null) { + if (hasOfflineTableConfig) { String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName); tableSizeDetails._offlineSegments = getTableSubtypeSize(offlineTableName, timeoutMsec); - tableSizeDetails._reportedSizeInBytes += tableSizeDetails._offlineSegments._reportedSizeInBytes; - tableSizeDetails._estimatedSizeInBytes += tableSizeDetails._offlineSegments._estimatedSizeInBytes; - + // taking max(0,value) as values as set to -1 if all the segments are in error + tableSizeDetails._reportedSizeInBytes += Math.max(tableSizeDetails._offlineSegments._reportedSizeInBytes, 0L); + tableSizeDetails._estimatedSizeInBytes += Math.max(tableSizeDetails._offlineSegments._estimatedSizeInBytes, 0L); + tableSizeDetails._reportedSizePerReplicaInBytes += + Math.max(tableSizeDetails._offlineSegments._reportedSizePerReplicaInBytes, 0L); + isMissingAllOfflineSegments = + (tableSizeDetails._offlineSegments._missingSegments == tableSizeDetails._offlineSegments._segments.size()); _controllerMetrics.setValueOfTableGauge(offlineTableName, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER, tableSizeDetails._offlineSegments._estimatedSizeInBytes); _controllerMetrics.setValueOfTableGauge(offlineTableName, ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER, - tableSizeDetails._offlineSegments._estimatedSizeInBytes / _helixResourceManager.getNumReplicas( - offlineTableConfig)); + tableSizeDetails._offlineSegments._estimatedSizeInBytes / _helixResourceManager + .getNumReplicas(offlineTableConfig)); long largestSegmentSizeOnServer = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; for (SegmentSizeDetails segmentSizeDetail : tableSizeDetails._offlineSegments._segments.values()) { @@ -132,12 +144,19 @@ public TableSizeDetails getTableSizeDetails(@Nonnull String tableName, @Nonnegat } } if (largestSegmentSizeOnServer != DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { - _controllerMetrics.setValueOfTableGauge(offlineTableName, - ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER, + _controllerMetrics.setValueOfTableGauge(offlineTableName, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER, largestSegmentSizeOnServer); } } + // Set the top level sizes to DEFAULT_SIZE_WHEN_MISSING_OR_ERROR when all segments are error + if ((hasRealtimeTableConfig && hasOfflineTableConfig && isMissingAllRealtimeSegments && isMissingAllOfflineSegments) + || (hasOfflineTableConfig && !hasRealtimeTableConfig && isMissingAllOfflineSegments) || (hasRealtimeTableConfig + && !hasOfflineTableConfig && isMissingAllRealtimeSegments)) { + tableSizeDetails._reportedSizeInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; + tableSizeDetails._estimatedSizeInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; + tableSizeDetails._reportedSizePerReplicaInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; + } return tableSizeDetails; } @@ -158,6 +177,10 @@ static public class TableSizeDetails { @JsonProperty("estimatedSizeInBytes") public long _estimatedSizeInBytes = 0; + // reported size per replica + @JsonProperty("reportedSizePerReplicaInBytes") + public long _reportedSizePerReplicaInBytes = 0; + @JsonProperty("offlineSegments") public TableSubTypeSizeDetails _offlineSegments; @@ -186,6 +209,10 @@ static public class TableSubTypeSizeDetails { @JsonProperty("missingSegments") public int _missingSegments = 0; + // reported size per replica + @JsonProperty("reportedSizePerReplicaInBytes") + public long _reportedSizePerReplicaInBytes = 0; + @JsonProperty("segments") public Map _segments = new HashMap<>(); } @@ -198,6 +225,10 @@ static public class SegmentSizeDetails { @JsonProperty("estimatedSizeInBytes") public long _estimatedSizeInBytes = 0; + // Max Reported size per replica + @JsonProperty("maxReportedSizePerReplicaInBytes") + public long _maxReportedSizePerReplicaInBytes = 0; + @JsonProperty("serverInfo") public Map _serverInfo = new HashMap<>(); } @@ -248,12 +279,13 @@ public TableSubTypeSizeDetails getTableSubtypeSize(String tableNameWithType, int String segment = entry.getKey(); SegmentSizeDetails sizeDetails = entry.getValue(); // Iterate over all segment size info, update reported size, track max segment size and number of errored servers - long segmentLevelMax = -1L; + sizeDetails._maxReportedSizePerReplicaInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; int errors = 0; for (SegmentSizeInfo sizeInfo : sizeDetails._serverInfo.values()) { if (sizeInfo.getDiskSizeInBytes() != DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { sizeDetails._reportedSizeInBytes += sizeInfo.getDiskSizeInBytes(); - segmentLevelMax = Math.max(segmentLevelMax, sizeInfo.getDiskSizeInBytes()); + sizeDetails._maxReportedSizePerReplicaInBytes = + Math.max(sizeDetails._maxReportedSizePerReplicaInBytes, sizeInfo.getDiskSizeInBytes()); } else { errors++; } @@ -261,12 +293,15 @@ public TableSubTypeSizeDetails getTableSubtypeSize(String tableNameWithType, int // Update estimated size, track segments that are missing from all servers if (errors != sizeDetails._serverInfo.size()) { // Use max segment size from other servers to estimate the segment size not reported - sizeDetails._estimatedSizeInBytes = sizeDetails._reportedSizeInBytes + errors * segmentLevelMax; + sizeDetails._estimatedSizeInBytes = + sizeDetails._reportedSizeInBytes + (errors * sizeDetails._maxReportedSizePerReplicaInBytes); subTypeSizeDetails._reportedSizeInBytes += sizeDetails._reportedSizeInBytes; subTypeSizeDetails._estimatedSizeInBytes += sizeDetails._estimatedSizeInBytes; + subTypeSizeDetails._reportedSizePerReplicaInBytes += sizeDetails._maxReportedSizePerReplicaInBytes; } else { // Segment is missing from all servers missingSegments.add(segment); + sizeDetails._maxReportedSizePerReplicaInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; sizeDetails._reportedSizeInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; sizeDetails._estimatedSizeInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; subTypeSizeDetails._missingSegments++; @@ -284,6 +319,7 @@ public TableSubTypeSizeDetails getTableSubtypeSize(String tableNameWithType, int LOGGER.warn("Failed to get size report for all {} segments of table: {}", numSegments, tableNameWithType); subTypeSizeDetails._reportedSizeInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; subTypeSizeDetails._estimatedSizeInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; + subTypeSizeDetails._reportedSizePerReplicaInBytes = DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; } else { LOGGER.warn("Missing size report for {} out of {} segments for table {}", subTypeSizeDetails._missingSegments, numSegments, tableNameWithType); diff --git a/pinot-controller/src/main/resources/app/App.tsx b/pinot-controller/src/main/resources/app/App.tsx index af04a0cd4bc..7645957787b 100644 --- a/pinot-controller/src/main/resources/app/App.tsx +++ b/pinot-controller/src/main/resources/app/App.tsx @@ -17,49 +17,48 @@ * under the License. */ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { CircularProgress, createStyles, makeStyles, MuiThemeProvider } from '@material-ui/core'; -import { Switch, Route, HashRouter as Router, Redirect } from 'react-router-dom'; -import theme from './theme'; +import React, { useEffect, useState } from 'react'; +import { Switch, Route, Redirect, useHistory } from 'react-router-dom'; import Layout from './components/Layout'; import RouterData from './router'; import PinotMethodUtils from './utils/PinotMethodUtils'; -import CustomNotification from './components/CustomNotification'; -import { NotificationContextProvider } from './components/Notification/NotificationContextProvider'; import app_state from './app_state'; +import { useAuthProvider } from './components/auth/AuthProvider'; +import { AppLoadingIndicator } from './components/AppLoadingIndicator'; import { AuthWorkflow } from 'Models'; -const useStyles = makeStyles(() => - createStyles({ - loader: { - position: 'fixed', - left: '50%', - top: '30%' - }, - }) -); +export const App = () => { + const [clusterName, setClusterName] = useState(''); + const [loading, setLoading] = useState(true); + const [isAuthenticated, setIsAuthenticated] = useState(null); + const [role, setRole] = useState(''); + const { authUserName, authUserEmail, authenticated, authWorkflow } = useAuthProvider(); + const history = useHistory(); + + useEffect(() => { + // authentication already handled by authProvider + if (authUserEmail && authenticated) { + // Authenticated with an auth method that supports user identity + // Any code that needs user identity can go here + } -const App = () => { - const [clusterName, setClusterName] = React.useState(''); - const [loading, setLoading] = React.useState(true); - const oidcSignInFormRef = React.useRef(null); - const [isAuthenticated, setIsAuthenticated] = React.useState(null); - const [issuer, setIssuer] = React.useState(null); - const [redirectUri, setRedirectUri] = React.useState(null); - const [clientId, setClientId] = React.useState(null); - const [authWorkflow, setAuthWorkflow] = React.useState(null); - const [authorizationEndpoint, setAuthorizationEndpoint] = React.useState( - null - ); - const [role, setRole] = React.useState(''); + if (authenticated) { + setIsAuthenticated(true); + } + }, [authUserName, authUserEmail, authenticated]); - const fetchUserRole = async()=>{ + useEffect(() => { + if(authWorkflow === AuthWorkflow.BASIC) { + setLoading(false); + } + }, [authWorkflow]) + + const fetchUserRole = async () => { const userListResponse = await PinotMethodUtils.getUserList(); let userObj = userListResponse.users; let userData = []; for (let key in userObj) { - if(userObj.hasOwnProperty(key)){ + if (userObj.hasOwnProperty(key)) { userData.push(userObj[key]); } } @@ -91,8 +90,8 @@ const App = () => { }; const getRouterData = () => { - if(app_state.queryConsoleOnlyView){ - return RouterData.filter((routeObj)=>{return routeObj.path === '/query'}); + if (app_state.queryConsoleOnlyView) { + return RouterData.filter((routeObj) => { return routeObj.path === '/query' }); } if (app_state.hideQueryConsoleTab) { return RouterData.filter((routeObj) => routeObj.path !== '/query'); @@ -100,85 +99,7 @@ const App = () => { return RouterData; }; - const getAuthInfo = async () => { - const authInfoResponse = await PinotMethodUtils.getAuthInfo(); - // Issuer URL, if available - setIssuer( - authInfoResponse && authInfoResponse.issuer ? authInfoResponse.issuer : '' - ); - // Redirect URI, if available - setRedirectUri( - authInfoResponse && authInfoResponse.redirectUri - ? authInfoResponse.redirectUri - : '' - ); - // Client Id, if available - setClientId( - authInfoResponse && authInfoResponse.clientId - ? authInfoResponse.clientId - : '' - ); - // Authentication workflow - setAuthWorkflow( - authInfoResponse && authInfoResponse.workflow - ? authInfoResponse.workflow - : AuthWorkflow.NONE - ); - }; - - const initAuthWorkflow = async () => { - switch (authWorkflow) { - case AuthWorkflow.NONE: { - // No authentication required - setIsAuthenticated(true); - - break; - } - case AuthWorkflow.BASIC: { - // Basic authentication, handled by login page - setLoading(false); - - break; - } - case AuthWorkflow.OIDC: { - // OIDC authentication, check to see if access token is available in the URL - const accessToken = PinotMethodUtils.getAccessTokenFromHashParams(); - if (accessToken) { - app_state.authWorkflow = AuthWorkflow.OIDC; - app_state.authToken = accessToken; - - setIsAuthenticated(true); - } else { - // Set authorization endpoint - setAuthorizationEndpoint(`${issuer}/auth`); - - setLoading(false); - } - - break; - } - default: { - // Empty - } - } - }; - - React.useEffect(() => { - getAuthInfo(); - }, []); - - React.useEffect(() => { - initAuthWorkflow(); - }, [authWorkflow]); - - React.useEffect(() => { - if (authorizationEndpoint && oidcSignInFormRef && oidcSignInFormRef.current) { - // Authorization endpoint available; submit sign in form - oidcSignInFormRef.current.submit(); - } - }, [authorizationEndpoint]); - - React.useEffect(() => { + useEffect(() => { if (isAuthenticated) { fetchClusterConfig(); fetchClusterName(); @@ -187,9 +108,14 @@ const App = () => { }, [isAuthenticated]); const loginRender = (Component, props) => { + if(isAuthenticated) { + history.push("/"); + return; + } + return (
    - +
    ) }; @@ -204,71 +130,36 @@ const App = () => { ) }; - const classes = useStyles(); + if (loading) { + return ; + } return ( - - - - {/* OIDC auth workflow */} - {authWorkflow && authWorkflow === AuthWorkflow.OIDC && !isAuthenticated ? ( - <> - {/* OIDC sign in form */} - - - - - - - - - - - ) : ( - <> - {/* Non-OIDC/authenticated workflow */} - {loading ? ( - - ) : ( - - - {getRouterData().map(({ path, Component }, key) => ( - { - if (path === '/login') { - return loginRender(Component, props); - } else if (isAuthenticated) { - // default render - return componentRender(Component, props, role); - } else { - return ; - } - }} - /> - ))} - - - - - - )} - - )} - - + + {getRouterData().map(({ path, Component }, key) => ( + { + if (path === '/login') { + return loginRender(Component, props); + } else if (isAuthenticated) { + // default render + return componentRender(Component, props, role); + } else { + return ; + } + }} + /> + ))} + + + + ); }; - -ReactDOM.render(, document.getElementById('app')); diff --git a/pinot-controller/src/main/resources/app/components/AppLoadingIndicator.tsx b/pinot-controller/src/main/resources/app/components/AppLoadingIndicator.tsx new file mode 100644 index 00000000000..773b21aabfe --- /dev/null +++ b/pinot-controller/src/main/resources/app/components/AppLoadingIndicator.tsx @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { CircularProgress, makeStyles } from "@material-ui/core"; + +export const useAppLoadingIndicatorStyles = makeStyles({ + appLoadingIndicator: { + height: "100vh", + width: "100%", + display: "flex", + flex: 1, + alignItems: "center", + justifyContent: "center", + }, +}); + +export const AppLoadingIndicator = () => { + const classes = useAppLoadingIndicatorStyles(); + return ( +
    + +
    + ) +} diff --git a/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx b/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx index d886d3b1286..8e73d422d7c 100644 --- a/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx +++ b/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx @@ -66,7 +66,8 @@ const useStyles = makeStyles((theme: Theme) => }, leftPanel: { width: 300, - padding: '0 20px' + padding: '0 20px', + wordBreak: 'break-all', }, }), ); @@ -101,6 +102,7 @@ const Sidebar = ({ tableList, fetchSQLData, tableSchema, selectedTable, queryLoa cellClickCallback={fetchSQLData} isCellClickable showSearchBox={true} + inAccordionFormat /> {!queryLoader && tableSchema.records.length ? ( @@ -109,6 +111,7 @@ const Sidebar = ({ tableList, fetchSQLData, tableSchema, selectedTable, queryLoa data={tableSchema} highlightBackground showSearchBox={true} + inAccordionFormat /> ) : null} diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx b/pinot-controller/src/main/resources/app/components/Table.tsx index acb7171493b..b145bfab6d2 100644 --- a/pinot-controller/src/main/resources/app/components/Table.tsx +++ b/pinot-controller/src/main/resources/app/components/Table.tsx @@ -281,7 +281,7 @@ export default function CustomizedTables({ accordionToggleObject, tooltipData }: Props) { - // Separate the initial and final data into two separte state variables. + // Separate the initial and final data into two separated state variables. // This way we can filter and sort the data without affecting the original data. // If the component receives new data, we can simply set the new data to the initial data, // and the filters and sorts will be applied to the new data. @@ -333,7 +333,8 @@ export default function CustomizedTables({ } return false; }); - setFinalData(filteredRescords); + let filteredData = {...initialData, records: filteredRescords}; + setFinalData(Utils.tableFormat(filteredData)); } }, [initialData, setFinalData]); @@ -341,6 +342,9 @@ export default function CustomizedTables({ clearTimeout(timeoutId.current); timeoutId.current = setTimeout(() => { filterSearchResults(search.toLowerCase()); + // Table.tsx currently doesn't support sorting after filtering. So for now, we just + // remove the visual indicator of the sorted column until users sort again. + setColumnClicked('') }, 200); return () => { @@ -394,6 +398,9 @@ export default function CustomizedTables({ /> ); } + if (str.search('\n') !== -1) { + return (
    {str.toString()}
    ); + } return ({str.toString()}); }; diff --git a/pinot-controller/src/main/resources/app/components/auth/AuthProvider.tsx b/pinot-controller/src/main/resources/app/components/auth/AuthProvider.tsx new file mode 100644 index 00000000000..c510438bc0d --- /dev/null +++ b/pinot-controller/src/main/resources/app/components/auth/AuthProvider.tsx @@ -0,0 +1,281 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AuthLocalStorageKeys, AuthWorkflow } from 'Models'; +import React, { createContext, useContext, useEffect, useState } from 'react' +import { useHistory, useLocation } from 'react-router'; +import { baseApi, getAxiosErrorInterceptor, getAxiosRequestInterceptor, getAxiosResponseInterceptor, transformApi } from '../../utils/axios-config'; +import PinotMethodUtils from '../../utils/PinotMethodUtils'; +import { AppLoadingIndicator } from '../AppLoadingIndicator'; + +interface AuthProviderContextProps { + accessToken: string; + authUserName: string; + authUserEmail: string; + authenticated: boolean; + authWorkflow: AuthWorkflow +} + +export const AuthProvider = ({ children }) => { + const [loading, setLoading] = useState(true); + const [redirectUri, setRedirectUri] = useState(null); + const [clientId, setClientId] = useState(null); + const [authWorkflow, setAuthWorkflow] = useState(null); + const [accessToken, setAccessToken] = useState(""); + const [authUserName, setAuthUserName] = useState(""); + const [authUserEmail, setAuthUserEmail] = useState(""); + const [authenticated, setAuthenticated] = useState(false); + const [authorizationEndpoint, setAuthorizationEndpoint] = useState(null); + const [autoLogout, setAutoLogout] = useState(false); + const history = useHistory(); + const location = useLocation(); + const [axiosRequestInterceptorIds, setAxiosRequestInterceptorIds] = + useState([0, 1]); + const [axiosResponseInterceptorIds, setAxiosResponseInterceptorIds] = + useState([0, 1]); + const oidcSignInFormRef = React.useRef(null); + + useEffect(() => { + initAuthDetails(); + }, []); + + useEffect(() => { + if (loading || authenticated) { + return; + } + + initOidcAuth(); + }, [loading, authenticated]); + + useEffect(() => { + if (!autoLogout) { + return; + } + + submitLoginForm(); + }, [autoLogout]) + + const initAuthDetails = async () => { + // fetch auth info details + const authInfoResponse = await PinotMethodUtils.getAuthInfo(); + + const authWorkFlowInternal = + authInfoResponse && authInfoResponse.workflow + ? authInfoResponse.workflow + : AuthWorkflow.NONE; + + // set auth workflow + setAuthWorkflow(authWorkFlowInternal); + + if (authWorkFlowInternal === AuthWorkflow.NONE) { + // No authentication required + setAuthenticated(true); + } + + if (authWorkFlowInternal === AuthWorkflow.BASIC) { + // basic auth is handled by login page + } + + // set OIDC auth details + if (authWorkFlowInternal === AuthWorkflow.OIDC) { + const issuer = + authInfoResponse && authInfoResponse.issuer ? authInfoResponse.issuer : ''; + + setAuthorizationEndpoint(`${issuer}/auth`); + setRedirectUri( + authInfoResponse && authInfoResponse.redirectUri + ? authInfoResponse.redirectUri + : '' + ); + setClientId( + authInfoResponse && authInfoResponse.clientId + ? authInfoResponse.clientId + : '' + ); + } + + // auth loading complete + setLoading(false); + } + + const initOidcAuth = () => { + // access token already available in the localStorage + const accessToken = getAuthLocalStorageValue(AuthLocalStorageKeys.AccessToken); + if (accessToken) { + setAccessToken(accessToken); + setAuthUserName(PinotMethodUtils.getAuthUserNameFromAccessToken(accessToken.replace("Bearer ", ""))) + setAuthUserEmail(PinotMethodUtils.getAuthUserEmailFromAccessToken(accessToken.replace("Bearer ", ""))) + + initAxios(accessToken); + setAuthenticated(true); + + return; + } + + // access token available in hash params + const accessTokenFromHashParam = PinotMethodUtils.getAccessTokenFromHashParams(); + if (accessTokenFromHashParam) { + const accessToken = `Bearer ${accessTokenFromHashParam}`; + setAccessToken(accessToken); + setAuthUserName(PinotMethodUtils.getAuthUserNameFromAccessToken(accessTokenFromHashParam)) + setAuthUserEmail(PinotMethodUtils.getAuthUserEmailFromAccessToken(accessTokenFromHashParam)) + + setAuthLocalStorageValue(AuthLocalStorageKeys.AccessToken, accessToken); + initAxios(accessToken); + setAuthenticated(true); + redirectToApp(); + + return; + } + + // no access token available + const redirectPathAfterLogin = location.pathname; + // save current path to redirect after login + setAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation, redirectPathAfterLogin); + // login + submitLoginForm(); + } + + const submitLoginForm = () => { + // submit auth login form + if (clientId && authorizationEndpoint && redirectUri && oidcSignInFormRef && oidcSignInFormRef.current) { + oidcSignInFormRef.current.submit(); + } + } + + const handleUnauthenticatedAccess = () => { + setAuthLocalStorageValue(AuthLocalStorageKeys.AccessToken, ""); + setAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation, ""); + + setAutoLogout(true); + } + + // initialize axios instance with authToken + const initAxios = (accessToken: string) => { + + // Clear existing interceptors + baseApi.interceptors.request.eject(axiosRequestInterceptorIds[0]); + baseApi.interceptors.response.eject(axiosResponseInterceptorIds[0]); + + transformApi.interceptors.request.eject(axiosRequestInterceptorIds[1]); + transformApi.interceptors.response.eject(axiosResponseInterceptorIds[1]); + + const requestInterceptorId1 = baseApi.interceptors.request.use( + getAxiosRequestInterceptor(accessToken), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + const requestInterceptorId2 = transformApi.interceptors.request.use( + getAxiosRequestInterceptor(accessToken), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + const responseInterceptor1 = baseApi.interceptors.response.use( + getAxiosResponseInterceptor(), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + const responseInterceptor2 = transformApi.interceptors.response.use( + getAxiosResponseInterceptor(), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + // Set new interceptors + setAxiosRequestInterceptorIds([requestInterceptorId1, requestInterceptorId2]); + setAxiosResponseInterceptorIds([responseInterceptor1, responseInterceptor2]); + } + + // redirect to app with appropriate location after login + const redirectToApp = () => { + const redirectLocation = getAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation); + if (redirectLocation && redirectLocation !== "/login" && redirectLocation !== "/logout") { + setAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation, ""); + history.push(redirectLocation); + } + } + + const getAuthLocalStorageValue = (key: AuthLocalStorageKeys) => { + return (localStorage.getItem(key) || ""); + } + + const setAuthLocalStorageValue = (key: AuthLocalStorageKeys, value: string) => { + localStorage.setItem(key, value); + } + + const authProvider: AuthProviderContextProps = { + authWorkflow: authWorkflow, + accessToken: accessToken, + authUserName: authUserName, + authUserEmail: authUserEmail, + authenticated: authenticated + } + + if (loading) { + return + } + + + return ( + + {children} + + {authWorkflow === AuthWorkflow.OIDC && (
    + {/* Login form */} + + + + + + + + + +
    )} +
    + ) +} + +const AuthProviderContext = createContext( + {} as AuthProviderContextProps +); + +export const useAuthProvider = (): AuthProviderContextProps => { + return useContext(AuthProviderContext); +}; diff --git a/pinot-controller/src/main/resources/app/index.tsx b/pinot-controller/src/main/resources/app/index.tsx new file mode 100644 index 00000000000..d06d57bbc49 --- /dev/null +++ b/pinot-controller/src/main/resources/app/index.tsx @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MuiThemeProvider } from "@material-ui/core"; +import { AuthProvider } from "./components/auth/AuthProvider"; +import CustomNotification from "./components/CustomNotification"; +import { NotificationContextProvider } from "./components/Notification/NotificationContextProvider"; +import theme from "./theme"; +import React from "react"; +import ReactDOM from "react-dom"; +import { App } from "./App"; +import { HashRouter } from "react-router-dom"; + +ReactDOM.render( + + + + + + + + + + , + document.getElementById('app') +); diff --git a/pinot-controller/src/main/resources/app/interfaces/types.d.ts b/pinot-controller/src/main/resources/app/interfaces/types.d.ts index 4be7e6f76bc..e24b58456e4 100644 --- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts +++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts @@ -47,6 +47,11 @@ declare module 'Models' { hostName: string; enabled: boolean; port: number; + grpcPort: number; + adminPort: number; + queryServicePort: number; + queryMailboxPort: number; + queriesDisabled: boolean; tags: Array; pools?: string; }; @@ -165,6 +170,11 @@ declare module 'Models' { OIDC = 'OIDC', } + export const enum AuthLocalStorageKeys { + RedirectLocation = "redirectLocation", + AccessToken = "AccessToken", + } + export type TableList = { tables: Array } diff --git a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx index ca1e83c909c..1039655a3dd 100644 --- a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx +++ b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx @@ -120,6 +120,11 @@ const InstanceDetails = ({ match }: RouteComponentProps) => { type: instanceType, tags: instanceDetails.tags, pools: instanceDetails.pools, + grpcPort: instanceDetails.grpcPort, + adminPort: instanceDetails.adminPort, + queryServicePort: instanceDetails.queryServicePort, + queryMailboxPort: instanceDetails.queryMailboxPort, + queriesDisabled: instanceDetails.queriesDisabled, }; setState({enabled: instanceDetails.enabled}); setInstanceDetails(JSON.stringify(instancePutObj, null, 2)); diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx index 9d3d7632dd3..b554bd8376f 100644 --- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx +++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx @@ -384,7 +384,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { setShowReloadStatusModal(true); const [reloadStatusData, tableJobsData] = await Promise.all([ PinotMethodUtils.reloadStatusOp(tableName, tableType), - PinotMethodUtils.fetchTableJobs(tableName, "RELOAD_SEGMENT,RELOAD_ALL_SEGMENTS"), + PinotMethodUtils.fetchTableJobs(tableName, "RELOAD_SEGMENT"), ]); if(reloadStatusData.error || tableJobsData.error) { diff --git a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts index cf7defca44e..07175ad49c1 100644 --- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts +++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts @@ -17,6 +17,7 @@ * under the License. */ +import jwtDecode from "jwt-decode"; import { get, map, each, isEqual, isArray, keys, union } from 'lodash'; import { DataTable, SQLResult } from 'Models'; import moment from 'moment'; @@ -577,6 +578,7 @@ const getSegmentDetails = (tableName, segmentName) => { const segmentMetaDataJson = { ...segmentMetaData } delete segmentMetaDataJson.indexes delete segmentMetaDataJson.columns + const indexes = get(segmentMetaData, 'indexes', {}) return { replicaSet: { @@ -585,7 +587,7 @@ const getSegmentDetails = (tableName, segmentName) => { }, indexes: { columns: ['Field Name', 'Bloom Filter', 'Dictionary', 'Forward Index', 'Sorted', 'Inverted Index', 'JSON Index', 'Null Value Vector Reader', 'Range Index'], - records: Object.keys(segmentMetaData.indexes).map(fieldName => [ + records: Object.keys(indexes).map(fieldName => [ fieldName, segmentMetaData.indexes[fieldName]["bloom-filter"] === "YES", segmentMetaData.indexes[fieldName]["dictionary"] === "YES", @@ -1002,7 +1004,9 @@ const verifyAuth = (authToken) => { const getAccessTokenFromHashParams = () => { let accessToken = ''; - const urlSearchParams = new URLSearchParams(location.hash.substr(1)); + const hashParam = removeAllLeadingForwardSlash(location.hash.substring(1)); + + const urlSearchParams = new URLSearchParams(hashParam); if (urlSearchParams.has('access_token')) { accessToken = urlSearchParams.get('access_token') as string; } @@ -1010,6 +1014,13 @@ const getAccessTokenFromHashParams = () => { return accessToken; }; +const removeAllLeadingForwardSlash = (string: string) => { + if(!string) { + return ""; + } + + return string.replace(new RegExp("^/+", "g"), ""); +} // validates app redirect path with known routes const validateRedirectPath = (path: string): boolean => { @@ -1144,6 +1155,52 @@ const updateUser = (userObject, passwordChanged) =>{ }) } +const getAuthUserNameFromAccessToken = ( + accessToken: string +): string => { + if (!accessToken) { + return ""; + } + + let decoded; + try { + decoded = jwtDecode(accessToken); + } catch (e) { + return ""; + } + + if (!decoded) { + return ""; + } + + const name = get(decoded, "name") || ""; + return name; +}; + +const getAuthUserEmailFromAccessToken = ( + accessToken: string +): string => { + if (!accessToken) { + return ""; + } + + let decoded; + try { + decoded = jwtDecode(accessToken); + } catch (e) { + return ""; + } + + if (!decoded) { + return ""; + } + + const email = + get(decoded, "email") || ""; + + return email; +}; + export default { getTenantsData, getAllInstances, @@ -1226,5 +1283,7 @@ export default { getUserList, addUser, deleteUser, - updateUser + updateUser, + getAuthUserNameFromAccessToken, + getAuthUserEmailFromAccessToken }; diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx index 2153c9175bd..582b897033f 100644 --- a/pinot-controller/src/main/resources/app/utils/Utils.tsx +++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx @@ -22,7 +22,7 @@ import React from 'react'; import ReactDiffViewer, {DiffMethod} from 'react-diff-viewer'; import { map, isEqual, findIndex, findLast } from 'lodash'; import app_state from '../app_state'; -import { DISPLAY_SEGMENT_STATUS, SEGMENT_STATUS } from 'Models'; +import {DISPLAY_SEGMENT_STATUS, SEGMENT_STATUS, TableData} from 'Models'; const sortArray = function (sortingArr, keyName, ascendingFlag) { if (ascendingFlag) { @@ -47,13 +47,13 @@ const sortArray = function (sortingArr, keyName, ascendingFlag) { }); }; -const tableFormat = (data) => { +const tableFormat = (data: TableData): Array<{ [key: string]: any }> => { const rows = data.records; const header = data.columns; - const results = []; + const results: Array<{ [key: string]: any }> = []; rows.forEach((singleRow) => { - const obj = {}; + const obj: { [key: string]: any } = {}; singleRow.forEach((val: any, index: number) => { obj[header[index]+app_state.columnNameSeparator+index] = val; }); diff --git a/pinot-controller/src/main/resources/app/utils/axios-config.ts b/pinot-controller/src/main/resources/app/utils/axios-config.ts index 595628c4fe4..491c5e270b2 100644 --- a/pinot-controller/src/main/resources/app/utils/axios-config.ts +++ b/pinot-controller/src/main/resources/app/utils/axios-config.ts @@ -17,60 +17,81 @@ * under the License. */ -/* eslint-disable no-console */ - import axios from 'axios'; import { AuthWorkflow } from 'Models'; import app_state from '../app_state'; +import { AxiosError, AxiosRequestConfig } from "axios"; const isDev = process.env.NODE_ENV !== 'production'; -const handleError = (error: any) => { - if (isDev) { - console.log(error); - } - return error.response || error; -}; +// Returns axios request interceptor +export const getAxiosRequestInterceptor = ( + accessToken?: string +): ((requestConfig: AxiosRequestConfig) => AxiosRequestConfig) => { + const requestInterceptor = ( + requestConfig: AxiosRequestConfig + ): AxiosRequestConfig => { + // If access token is available, attach it to the request + // basic auth + if (app_state.authWorkflow === AuthWorkflow.BASIC && app_state.authToken) { + requestConfig.headers = { + Authorization: app_state.authToken, + }; + } + + // OIDC auth + if (accessToken) { + requestConfig.headers = { + Authorization: accessToken, + }; + } -const handleResponse = (response: any) => { - if (isDev) { - console.log(response); - } - return response; + return requestConfig; + }; + + return requestInterceptor; }; -const handleConfig = (config: any) => { - // Attach auth token for basic auth - if (app_state.authWorkflow === AuthWorkflow.BASIC && app_state.authToken) { - Object.assign(config.headers, { Authorization: app_state.authToken }); - } +// Returns axios rejected response interceptor +export const getAxiosErrorInterceptor = ( + unauthenticatedAccessFn?: () => void +): ((error: AxiosError) => void) => { + const rejectedResponseInterceptor = (error: AxiosError): any => { + if (error && error.response && (error.response.status === 401 || error.response.status === 403)) { + // Unauthenticated access + unauthenticatedAccessFn && unauthenticatedAccessFn(); + } + + return error.response || error; + }; - // Attach auth token for OIDC auth - if (app_state.authWorkflow === AuthWorkflow.OIDC && app_state.authToken) { - Object.assign(config.headers, { - Authorization: `Bearer ${app_state.authToken}`, - }); - } + return rejectedResponseInterceptor; +}; - if (isDev) { - console.log(config); - } +// Returns axios fulfilled response interceptor +export const getAxiosResponseInterceptor = (): (( + response: T +) => T | Promise) => { + const fulfilledResponseInterceptor = (response: T): T | Promise => { + // Forward the fulfilled response + return response; + }; - return config; + return fulfilledResponseInterceptor; }; export const baseApi = axios.create({ baseURL: '/' }); -baseApi.interceptors.request.use(handleConfig, handleError); -baseApi.interceptors.response.use(handleResponse, handleError); +baseApi.interceptors.request.use(getAxiosRequestInterceptor(), getAxiosErrorInterceptor()); +baseApi.interceptors.response.use(getAxiosResponseInterceptor(), getAxiosErrorInterceptor()); export const transformApi = axios.create({baseURL: '/', transformResponse: [data => data]}); -transformApi.interceptors.request.use(handleConfig, handleError); -transformApi.interceptors.response.use(handleResponse, handleError); +transformApi.interceptors.request.use(getAxiosRequestInterceptor(), getAxiosErrorInterceptor()); +transformApi.interceptors.response.use(getAxiosResponseInterceptor(), getAxiosErrorInterceptor()); // baseApi axios instance does not throw an error when API fails hence the control will never go to catch block // changing the handleError method of baseApi will cause current UI to break (as UI might have not handle error properly) // creating a new axios instance baseApiWithErrors which can be used when adding new API's // NOTE: It is an add-on utility and can be used in case you want to handle/show UI when API fails. export const baseApiWithErrors = axios.create({ baseURL: '/' }); -baseApiWithErrors.interceptors.request.use(handleConfig); -baseApiWithErrors.interceptors.response.use(handleResponse); +baseApiWithErrors.interceptors.request.use(getAxiosRequestInterceptor()); +baseApiWithErrors.interceptors.response.use(getAxiosResponseInterceptor()); diff --git a/pinot-controller/src/main/resources/package-lock.json b/pinot-controller/src/main/resources/package-lock.json index f755f3ab855..dc5f099f980 100644 --- a/pinot-controller/src/main/resources/package-lock.json +++ b/pinot-controller/src/main/resources/package-lock.json @@ -5419,6 +5419,11 @@ "object.assign": "^4.1.0" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", diff --git a/pinot-controller/src/main/resources/package.json b/pinot-controller/src/main/resources/package.json index fa905012071..f886dfb40fd 100644 --- a/pinot-controller/src/main/resources/package.json +++ b/pinot-controller/src/main/resources/package.json @@ -7,6 +7,7 @@ "start": "npm-run-all --parallel lint dev", "build": "webpack --mode production", "build-dev": "webpack --mode development", + "build-ci": "if [ \"$CI\" = true ]; then npm run-script build; else npm run-script build-dev; fi", "build-analyze": "webpack --mode production --analyze", "lint": "eslint 'app/**/*.{js,ts,tsx,jsx}' --quiet --fix", "test": "echo \"Error: no test specified\" && exit 1" @@ -78,6 +79,7 @@ "file": "0.2.2", "json-bigint": "1.0.0", "jsonlint": "1.6.3", + "jwt-decode": "^3.1.2", "lodash": "4.17.21", "moment": "2.29.3", "prop-types": "15.8.1", diff --git a/pinot-controller/src/main/resources/webpack.config.js b/pinot-controller/src/main/resources/webpack.config.js index 6ed7a39660b..65e89e366ab 100644 --- a/pinot-controller/src/main/resources/webpack.config.js +++ b/pinot-controller/src/main/resources/webpack.config.js @@ -38,7 +38,7 @@ module.exports = (env, argv) => { extensions: ['.ts', '.tsx', '.js'], modules: ['./app', 'node_modules'], }, - entry: './app/App.tsx', + entry: './app/index.tsx', output: { path: path.resolve(__dirname, 'dist/webapp'), filename: './js/main.js' diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java index 159af6dc004..49494b914e3 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java @@ -158,6 +158,29 @@ public void shouldBeAbleToDisableUsingNewConfig() { Assert.assertEquals(conf.getTaskManagerFrequencyInSeconds(), -1); } + @Test + public void shouldBeAbleToSetDataDir() { + Map controllerConfig = new HashMap<>(); + ControllerConf conf = new ControllerConf(controllerConfig); + Assert.assertEquals(conf.getDataDir(), null); + + // test for the dataDir s3 value with ending slash + conf.setDataDir("s3:///controller/"); + Assert.assertEquals(conf.getDataDir(), "s3:///controller"); + + // test for the dataDir s3 value without ending slash + conf.setDataDir("s3:///controller"); + Assert.assertEquals(conf.getDataDir(), "s3:///controller"); + + // test for the dataDir non-s3 value without ending slash + conf.setDataDir("/tmp/PinotController"); + Assert.assertEquals(conf.getDataDir(), "/tmp/PinotController"); + + // test for the dataDir non-s3 value with ending slash + conf.setDataDir("/tmp/PinotController/"); + Assert.assertEquals(conf.getDataDir(), "/tmp/PinotController"); + } + private void assertOnDurations(ControllerConf conf, long expectedDuration, Map controllerConfig) { int segmentLevelValidationIntervalInSeconds = conf.getSegmentLevelValidationIntervalInSeconds(); int segmentRelocatorFrequencyInSeconds = conf.getSegmentRelocatorFrequencyInSeconds(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java index 38334bb25a8..570c59438cf 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java @@ -21,15 +21,19 @@ import com.fasterxml.jackson.core.type.TypeReference; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.assignment.InstancePartitionsUtils; +import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.utils.config.TagNameUtils; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.assignment.InstanceAssignmentConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.config.table.assignment.InstanceReplicaGroupPartitionConfig; @@ -59,6 +63,8 @@ public class PinotInstanceAssignmentRestletResourceStatelessTest extends Control private static final String RAW_TABLE_NAME = "testTable"; private static final String TIME_COLUMN_NAME = "daysSinceEpoch"; + private static final String TIER_NAME = "tier1"; + @BeforeClass public void setUp() throws Exception { @@ -112,15 +118,15 @@ public void testInstanceAssignment() // Add OFFLINE instance assignment config to the offline table config InstanceAssignmentConfig offlineInstanceAssignmentConfig = new InstanceAssignmentConfig( new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME), false, 0, null), null, - new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false)); + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null)); offlineTableConfig.setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, offlineInstanceAssignmentConfig)); + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), offlineInstanceAssignmentConfig)); _helixResourceManager.setExistingTableConfig(offlineTableConfig); // OFFLINE instance partitions should be generated - Map instancePartitionsMap = getInstancePartitionsMap(); + Map instancePartitionsMap = getInstancePartitionsMap(); assertEquals(instancePartitionsMap.size(), 1); - InstancePartitions offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + InstancePartitions offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); @@ -130,74 +136,110 @@ public void testInstanceAssignment() // Add CONSUMING instance assignment config to the real-time table config InstanceAssignmentConfig consumingInstanceAssignmentConfig = new InstanceAssignmentConfig( new InstanceTagPoolConfig(TagNameUtils.getRealtimeTagForTenant(SERVER_TENANT_NAME), false, 0, null), null, - new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false)); + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null)); realtimeTableConfig.setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.CONSUMING, consumingInstanceAssignmentConfig)); + Collections.singletonMap(InstancePartitionsType.CONSUMING.toString(), consumingInstanceAssignmentConfig)); _helixResourceManager.setExistingTableConfig(realtimeTableConfig); // CONSUMING instance partitions should be generated instancePartitionsMap = getInstancePartitionsMap(); assertEquals(instancePartitionsMap.size(), 2); - offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - InstancePartitions consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING); + InstancePartitions consumingInstancePartitions = + instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()); assertNotNull(consumingInstancePartitions); assertEquals(consumingInstancePartitions.getNumReplicaGroups(), 1); assertEquals(consumingInstancePartitions.getNumPartitions(), 1); assertEquals(consumingInstancePartitions.getInstances(0, 0).size(), 1); String consumingInstanceId = consumingInstancePartitions.getInstances(0, 0).get(0); + // Add tier config and tier instance assignment config to the offline table config + offlineTableConfig.setTierConfigsList(Collections.singletonList( + new TierConfig(TIER_NAME, TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "7d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME), null, + null))); + InstanceAssignmentConfig tierInstanceAssignmentConfig = new InstanceAssignmentConfig( + new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME), false, 0, null), null, + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null)); + Map instanceAssignmentConfigMap = new HashMap<>(); + instanceAssignmentConfigMap.put(InstancePartitionsType.OFFLINE.toString(), offlineInstanceAssignmentConfig); + instanceAssignmentConfigMap.put(TIER_NAME, tierInstanceAssignmentConfig); + offlineTableConfig.setInstanceAssignmentConfigMap(instanceAssignmentConfigMap); + _helixResourceManager.setExistingTableConfig(offlineTableConfig); + + // tier instance partitions should be generated + Map tierInstancePartitionsMap = getInstancePartitionsMap(); + assertEquals(tierInstancePartitionsMap.size(), 3); + InstancePartitions tierInstancePartitions = tierInstancePartitionsMap.get(TIER_NAME); + assertNotNull(tierInstancePartitions); + assertEquals(tierInstancePartitions.getNumReplicaGroups(), 1); + assertEquals(tierInstancePartitions.getNumPartitions(), 1); + assertEquals(tierInstancePartitions.getInstances(0, 0).size(), 1); + // Use OFFLINE instance assignment config as the COMPLETED instance assignment config - realtimeTableConfig.setInstanceAssignmentConfigMap( - new TreeMap() {{ - put(InstancePartitionsType.CONSUMING, consumingInstanceAssignmentConfig); - put(InstancePartitionsType.COMPLETED, offlineInstanceAssignmentConfig); - }}); + realtimeTableConfig.setInstanceAssignmentConfigMap(new TreeMap() {{ + put(InstancePartitionsType.CONSUMING.toString(), consumingInstanceAssignmentConfig); + put(InstancePartitionsType.COMPLETED.toString(), offlineInstanceAssignmentConfig); + }}); _helixResourceManager.setExistingTableConfig(realtimeTableConfig); // COMPLETED instance partitions should be generated instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 3); - offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + assertEquals(instancePartitionsMap.size(), 4); + offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING); + consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()); assertNotNull(consumingInstancePartitions); assertEquals(consumingInstancePartitions.getNumReplicaGroups(), 1); assertEquals(consumingInstancePartitions.getNumPartitions(), 1); assertEquals(consumingInstancePartitions.getInstances(0, 0), Collections.singletonList(consumingInstanceId)); - InstancePartitions completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED); + InstancePartitions completedInstancePartitions = + instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()); assertEquals(completedInstancePartitions.getNumReplicaGroups(), 1); assertEquals(completedInstancePartitions.getNumPartitions(), 1); assertEquals(completedInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); + InstancePartitions tInstancePartitions = instancePartitionsMap.get(TIER_NAME); + assertEquals(tInstancePartitions.getNumReplicaGroups(), 1); + assertEquals(tInstancePartitions.getNumPartitions(), 1); + assertEquals(tInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); // Test fetching instance partitions by table name with type suffix instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest( _controllerRequestURLBuilder.forInstancePartitions(TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME), null))); - assertEquals(instancePartitionsMap.size(), 1); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE)); + assertEquals(instancePartitionsMap.size(), 2); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + assertTrue(instancePartitionsMap.containsKey(TIER_NAME)); instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest( _controllerRequestURLBuilder.forInstancePartitions(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null))); assertEquals(instancePartitionsMap.size(), 2); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING)); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING.toString())); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // Test fetching instance partitions by table name and instance partitions type for (InstancePartitionsType instancePartitionsType : InstancePartitionsType.values()) { - instancePartitionsMap = deserializeInstancePartitionsMap( - sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, instancePartitionsType))); + instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest( + _controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, instancePartitionsType.toString()))); assertEquals(instancePartitionsMap.size(), 1); - assertEquals(instancePartitionsMap.get(instancePartitionsType).getInstancePartitionsName(), + assertEquals(instancePartitionsMap.get(instancePartitionsType.toString()).getInstancePartitionsName(), instancePartitionsType.getInstancePartitionsName(RAW_TABLE_NAME)); } + // Test fetching instance partitions by table name and tier name + instancePartitionsMap = deserializeInstancePartitionsMap( + sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, TIER_NAME))); + assertEquals(instancePartitionsMap.size(), 1); + assertEquals(instancePartitionsMap.get(TIER_NAME).getInstancePartitionsName(), + InstancePartitionsUtils.getInstancePartitionsNameForTier(RAW_TABLE_NAME, TIER_NAME)); + // Remove the instance partitions for both offline and real-time table sendDeleteRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null)); try { @@ -210,21 +252,25 @@ public void testInstanceAssignment() // Assign instances without instance partitions type (dry run) instancePartitionsMap = deserializeInstancePartitionsMap( sendPostRequest(_controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, null, true), null)); - assertEquals(instancePartitionsMap.size(), 3); - offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + assertEquals(instancePartitionsMap.size(), 4); + offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING); + consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()); assertNotNull(consumingInstancePartitions); assertEquals(consumingInstancePartitions.getNumReplicaGroups(), 1); assertEquals(consumingInstancePartitions.getNumPartitions(), 1); assertEquals(consumingInstancePartitions.getInstances(0, 0), Collections.singletonList(consumingInstanceId)); - completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED); + completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()); assertEquals(completedInstancePartitions.getNumReplicaGroups(), 1); assertEquals(completedInstancePartitions.getNumPartitions(), 1); assertEquals(completedInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); + tInstancePartitions = instancePartitionsMap.get(TIER_NAME); + assertEquals(tInstancePartitions.getNumReplicaGroups(), 1); + assertEquals(tInstancePartitions.getNumPartitions(), 1); + assertEquals(tInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); // Instance partitions should not be persisted try { @@ -239,34 +285,36 @@ public void testInstanceAssignment() // Instance partitions should be persisted instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 3); + assertEquals(instancePartitionsMap.size(), 4); // Remove the instance partitions for real-time table sendDeleteRequest( _controllerRequestURLBuilder.forInstancePartitions(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null)); instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 1); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE)); + assertEquals(instancePartitionsMap.size(), 2); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + assertTrue(instancePartitionsMap.containsKey(TIER_NAME)); // Assign instances for COMPLETED segments instancePartitionsMap = deserializeInstancePartitionsMap(sendPostRequest( _controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, InstancePartitionsType.COMPLETED, false), null)); assertEquals(instancePartitionsMap.size(), 1); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // There should be OFFLINE and COMPLETED instance partitions persisted instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 2); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE)); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertEquals(instancePartitionsMap.size(), 3); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + assertTrue(instancePartitionsMap.containsKey(TIER_NAME)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // Replace OFFLINE instance with CONSUMING instance for COMPLETED instance partitions instancePartitionsMap = deserializeInstancePartitionsMap(sendPostRequest( _controllerRequestURLBuilder.forInstanceReplace(RAW_TABLE_NAME, InstancePartitionsType.COMPLETED, offlineInstanceId, consumingInstanceId), null)); assertEquals(instancePartitionsMap.size(), 1); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); // Replace the instance again using real-time table name (old instance does not exist) @@ -284,26 +332,27 @@ public void testInstanceAssignment() sendPutRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null), consumingInstancePartitions.toJsonString())); assertEquals(instancePartitionsMap.size(), 1); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); // OFFLINE instance partitions should have OFFLINE instance, CONSUMING and COMPLETED instance partitions should have // CONSUMING instance instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 3); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.OFFLINE).getInstances(0, 0), + assertEquals(instancePartitionsMap.size(), 4); + assertEquals(instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()).getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(TIER_NAME).getInstances(0, 0), Collections.singletonList(offlineInstanceId)); + assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); // Delete the offline table _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); instancePartitionsMap = getInstancePartitionsMap(); assertEquals(instancePartitionsMap.size(), 2); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING)); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING.toString())); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // Delete the real-time table _helixResourceManager.deleteRealtimeTable(RAW_TABLE_NAME); @@ -315,18 +364,16 @@ public void testInstanceAssignment() } } - private Map getInstancePartitionsMap() + private Map getInstancePartitionsMap() throws Exception { return deserializeInstancePartitionsMap( sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null))); } - private Map deserializeInstancePartitionsMap( - String instancePartitionsMapString) + private Map deserializeInstancePartitionsMap(String instancePartitionsMapString) throws Exception { - return JsonUtils.stringToObject(instancePartitionsMapString, - new TypeReference>() { - }); + return JsonUtils.stringToObject(instancePartitionsMapString, new TypeReference>() { + }); } @AfterClass diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java index 9b84c134ebb..3bf0e2c70dd 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java @@ -19,24 +19,34 @@ package org.apache.pinot.controller.api; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.annotation.Nullable; +import org.apache.pinot.controller.api.resources.InstanceTagUpdateRequest; +import org.apache.pinot.controller.api.resources.OperationValidationResponse; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.spi.config.instance.Instance; import org.apache.pinot.spi.config.instance.InstanceType; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.CommonConstants.Helix; import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; @@ -47,22 +57,24 @@ */ public class PinotInstanceRestletResourceTest extends ControllerTest { + private ControllerRequestURLBuilder _urlBuilder = null; + @BeforeClass public void setUp() throws Exception { DEFAULT_INSTANCE.setupSharedStateAndValidate(); + _urlBuilder = DEFAULT_INSTANCE.getControllerRequestURLBuilder(); } @Test public void testInstanceListingAndCreation() throws Exception { - ControllerRequestURLBuilder requestURLBuilder = DEFAULT_INSTANCE.getControllerRequestURLBuilder(); - String listInstancesUrl = requestURLBuilder.forInstanceList(); + String listInstancesUrl = _urlBuilder.forInstanceList(); int expectedNumInstances = 1 + DEFAULT_NUM_BROKER_INSTANCES + DEFAULT_NUM_SERVER_INSTANCES; checkNumInstances(listInstancesUrl, expectedNumInstances); // Create untagged broker and server instances - String createInstanceUrl = requestURLBuilder.forInstanceCreate(); + String createInstanceUrl = _urlBuilder.forInstanceCreate(); Instance brokerInstance1 = new Instance("1.2.3.4", 1234, InstanceType.BROKER, null, null, 0, 0, 0, 0, false); sendPostRequest(createInstanceUrl, brokerInstance1.toJsonString()); Instance serverInstance1 = @@ -110,14 +122,14 @@ public void testInstanceListingAndCreation() new Instance("1.2.3.4", 1234, InstanceType.BROKER, Collections.singletonList(newBrokerTag), null, 0, 0, 0, 0, false); String brokerInstanceId = "Broker_1.2.3.4_1234"; - String brokerInstanceUrl = requestURLBuilder.forInstance(brokerInstanceId); + String brokerInstanceUrl = _urlBuilder.forInstance(brokerInstanceId); sendPutRequest(brokerInstanceUrl, newBrokerInstance.toJsonString()); String newServerTag = "new-server-tag"; Instance newServerInstance = new Instance("1.2.3.4", 2345, InstanceType.SERVER, Collections.singletonList(newServerTag), null, 28090, 28091, 28092, 28093, true); String serverInstanceId = "Server_1.2.3.4_2345"; - String serverInstanceUrl = requestURLBuilder.forInstance(serverInstanceId); + String serverInstanceUrl = _urlBuilder.forInstance(serverInstanceId); sendPutRequest(serverInstanceUrl, newServerInstance.toJsonString()); checkInstanceInfo(brokerInstanceId, "1.2.3.4", 1234, new String[]{newBrokerTag}, null, -1, -1, -1, -1, false); @@ -126,9 +138,9 @@ public void testInstanceListingAndCreation() // Test Instance updateTags API String brokerInstanceUpdateTagsUrl = - requestURLBuilder.forInstanceUpdateTags(brokerInstanceId, Lists.newArrayList("tag_BROKER", "newTag_BROKER")); + _urlBuilder.forInstanceUpdateTags(brokerInstanceId, Lists.newArrayList("tag_BROKER", "newTag_BROKER")); sendPutRequest(brokerInstanceUpdateTagsUrl); - String serverInstanceUpdateTagsUrl = requestURLBuilder.forInstanceUpdateTags(serverInstanceId, + String serverInstanceUpdateTagsUrl = _urlBuilder.forInstanceUpdateTags(serverInstanceId, Lists.newArrayList("tag_REALTIME", "newTag_OFFLINE", "newTag_REALTIME")); sendPutRequest(serverInstanceUpdateTagsUrl); checkInstanceInfo(brokerInstanceId, "1.2.3.4", 1234, new String[]{"tag_BROKER", "newTag_BROKER"}, null, -1, -1, -1, @@ -137,10 +149,10 @@ public void testInstanceListingAndCreation() new String[]{"tag_REALTIME", "newTag_OFFLINE", "newTag_REALTIME"}, null, 28090, 28091, 28092, 28093, true); // Test DELETE instance API - sendDeleteRequest(requestURLBuilder.forInstance("Broker_1.2.3.4_1234")); - sendDeleteRequest(requestURLBuilder.forInstance("Server_1.2.3.4_2345")); - sendDeleteRequest(requestURLBuilder.forInstance("Broker_2.3.4.5_1234")); - sendDeleteRequest(requestURLBuilder.forInstance("Server_2.3.4.5_2345")); + sendDeleteRequest(_urlBuilder.forInstance("Broker_1.2.3.4_1234")); + sendDeleteRequest(_urlBuilder.forInstance("Server_1.2.3.4_2345")); + sendDeleteRequest(_urlBuilder.forInstance("Broker_2.3.4.5_1234")); + sendDeleteRequest(_urlBuilder.forInstance("Server_2.3.4.5_2345")); checkNumInstances(listInstancesUrl, expectedNumInstances); } @@ -163,7 +175,7 @@ private void checkInstanceInfo(String instanceName, String hostName, int port, S boolean queriesDisabled) throws Exception { JsonNode response = JsonUtils.stringToJsonNode( - ControllerTest.sendGetRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forInstance(instanceName))); + ControllerTest.sendGetRequest(_urlBuilder.forInstance(instanceName))); assertEquals(response.get("instanceName").asText(), instanceName); assertEquals(response.get("hostName").asText(), hostName); assertTrue(response.get("enabled").asBoolean()); @@ -193,6 +205,95 @@ private void checkInstanceInfo(String instanceName, String hostName, int port, S } } + @Test + public void instanceRetagHappyPathTest() + throws IOException { + Map> currentInstanceTagsMap = getCurrentInstanceTagsMap(); + List request = new ArrayList<>(); + currentInstanceTagsMap.forEach((instance, tags) -> { + if (instance.startsWith(Helix.PREFIX_OF_SERVER_INSTANCE) + || instance.startsWith(Helix.PREFIX_OF_BROKER_INSTANCE)) { + InstanceTagUpdateRequest payload = new InstanceTagUpdateRequest(); + payload.setInstanceName(instance); + payload.setNewTags(tags); + request.add(payload); + } + }); + List response = Arrays.asList(new ObjectMapper().readValue( + sendPostRequest(_urlBuilder.forUpdateTagsValidation(), JsonUtils.objectToString(request)), + OperationValidationResponse[].class)); + assertNotNull(response); + response.forEach(item -> assertTrue(item.isSafe())); + } + + @Test + public void instanceRetagServerDeficiencyTest() + throws Exception { + String tableName = "testTable"; + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(tableName) + .setNumReplicas(2).build(); + // create table with replication as 2 so that DefaultTenant has a minimum server requirement as 2. + DEFAULT_INSTANCE.addTableConfig(tableConfig); + Map> currentInstanceTagsMap = getCurrentInstanceTagsMap(); + List request = new ArrayList<>(); + currentInstanceTagsMap.forEach((instance, tags) -> { + if (instance.startsWith(Helix.PREFIX_OF_SERVER_INSTANCE) + || instance.startsWith(Helix.PREFIX_OF_BROKER_INSTANCE)) { + InstanceTagUpdateRequest payload = new InstanceTagUpdateRequest(); + payload.setInstanceName(instance); + payload.setNewTags(Lists.newArrayList()); + request.add(payload); + } + }); + List response = Arrays.asList(new ObjectMapper().readValue( + sendPostRequest(_urlBuilder.forUpdateTagsValidation(), JsonUtils.objectToString(request)), + OperationValidationResponse[].class)); + assertNotNull(response); + + int deficientServers = 2; + int deficientBrokers = 1; + for (OperationValidationResponse item : response) { + String instanceName = item.getInstanceName(); + boolean validity = item.isSafe(); + if (!validity) { + List issues = item.getIssues(); + assertEquals(issues.size(), 1); + assertEquals(issues.get(0).getCode(), OperationValidationResponse.ErrorCode.MINIMUM_INSTANCE_UNSATISFIED); + if (instanceName.startsWith(Helix.PREFIX_OF_SERVER_INSTANCE)) { + deficientServers--; + } else if (instanceName.startsWith(Helix.PREFIX_OF_BROKER_INSTANCE)) { + deficientBrokers--; + } + } + } + assertEquals(deficientServers, 0); + assertEquals(deficientBrokers, 0); + DEFAULT_INSTANCE.dropOfflineTable(tableName); + } + + private Map> getCurrentInstanceTagsMap() + throws IOException { + String listInstancesUrl = _urlBuilder.forInstanceList(); + JsonNode response = JsonUtils.stringToJsonNode(sendGetRequest(listInstancesUrl)); + JsonNode instances = response.get("instances"); + Map> map = new HashMap<>(instances.size()); + for (int i = 0; i < instances.size(); i++) { + String instance = instances.get(i).asText(); + map.put(instance, getInstanceTags(instance)); + } + return map; + } + + private List getInstanceTags(String instance) + throws IOException { + String getInstancesUrl = _urlBuilder.forInstance(instance); + List tags = new ArrayList<>(); + for (JsonNode tag : JsonUtils.stringToJsonNode(sendGetRequest(getInstancesUrl)).get("tags")) { + tags.add(tag.asText()); + } + return tags; + } + @AfterClass public void tearDown() throws Exception { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java index d0903160315..c8ba1dbd73a 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java @@ -20,15 +20,19 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.AfterClass; @@ -36,14 +40,13 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; public class PinotSegmentRestletResourceTest { private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); - private static final String TABLE_NAME = "pinotSegmentRestletResourceTestTable"; - private static final String TABLE_NAME_OFFLINE = TABLE_NAME + "_OFFLINE"; @BeforeClass public void setUp() @@ -55,45 +58,42 @@ public void setUp() public void testListSegmentLineage() throws Exception { // Adding table + String rawTableName = "lineageTestTable"; + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setNumReplicas(1).build(); - TEST_INSTANCE.getHelixResourceManager().addTable(tableConfig); - - // Wait for the table addition - while (!TEST_INSTANCE.getHelixResourceManager().hasOfflineTable(TABLE_NAME)) { - Thread.sleep(100); - } - - Map segmentMetadataTable = new HashMap<>(); + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setNumReplicas(1).build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); // Upload Segments + Map segmentMetadataTable = new HashMap<>(); for (int i = 0; i < 4; i++) { - SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TABLE_NAME, "s" + i); - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), segmentMetadata, "downloadUrl"); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName, "s" + i); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); segmentMetadataTable.put(segmentMetadata.getName(), segmentMetadata); } // There should be no segment lineage at this point. - String segmentLineageResponse = ControllerTest.sendGetRequest(TEST_INSTANCE.getControllerRequestURLBuilder() - .forListAllSegmentLineages(TABLE_NAME, TableType.OFFLINE.toString())); + ControllerRequestURLBuilder urlBuilder = TEST_INSTANCE.getControllerRequestURLBuilder(); + String segmentLineageResponse = + ControllerTest.sendGetRequest(urlBuilder.forListAllSegmentLineages(rawTableName, TableType.OFFLINE.name())); assertEquals(segmentLineageResponse, ""); // Now starts to replace segments. List segmentsFrom = Arrays.asList("s0", "s1"); - List segmentsTo = Arrays.asList("some_segment"); - String segmentLineageId = TEST_INSTANCE.getHelixResourceManager() - .startReplaceSegments(TABLE_NAME_OFFLINE, segmentsFrom, segmentsTo, false); + List segmentsTo = Collections.singletonList("some_segment"); + String segmentLineageId = resourceManager.startReplaceSegments(offlineTableName, segmentsFrom, segmentsTo, false, + null); // Replace more segments to add another entry to segment lineage. segmentsFrom = Arrays.asList("s2", "s3"); - segmentsTo = Arrays.asList("another_segment"); - String nextSegmentLineageId = TEST_INSTANCE.getHelixResourceManager() - .startReplaceSegments(TABLE_NAME_OFFLINE, segmentsFrom, segmentsTo, false); + segmentsTo = Collections.singletonList("another_segment"); + String nextSegmentLineageId = + resourceManager.startReplaceSegments(offlineTableName, segmentsFrom, segmentsTo, false, null); // There should now be two segment lineage entries resulting from the operations above. - segmentLineageResponse = ControllerTest.sendGetRequest(TEST_INSTANCE.getControllerRequestURLBuilder() - .forListAllSegmentLineages(TABLE_NAME, TableType.OFFLINE.toString())); + segmentLineageResponse = + ControllerTest.sendGetRequest(urlBuilder.forListAllSegmentLineages(rawTableName, TableType.OFFLINE.toString())); assertTrue(segmentLineageResponse.contains("\"state\":\"IN_PROGRESS\"")); assertTrue(segmentLineageResponse.contains("\"segmentsFrom\":[\"s0\",\"s1\"]")); assertTrue(segmentLineageResponse.contains("\"segmentsTo\":[\"some_segment\"]")); @@ -103,93 +103,110 @@ public void testListSegmentLineage() assertTrue(segmentLineageResponse.indexOf(segmentLineageId) < segmentLineageResponse.indexOf(nextSegmentLineageId)); // List segment lineage should fail for non-existing table - assertThrows(IOException.class, () -> ControllerTest.sendGetRequest(TEST_INSTANCE.getControllerRequestURLBuilder() - .forListAllSegmentLineages("non-existing-table", TableType.OFFLINE.toString()))); - - // List segment lineage should also fail for invalid table type. assertThrows(IOException.class, () -> ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forListAllSegmentLineages(TABLE_NAME, "invalid-type"))); - - // Delete segments - TEST_INSTANCE.getHelixResourceManager().deleteSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), - segmentMetadataTable.values().iterator().next().getName()); + urlBuilder.forListAllSegmentLineages("non-existing-table", TableType.OFFLINE.toString()))); - // Delete offline table - TEST_INSTANCE.getHelixResourceManager().deleteOfflineTable(TABLE_NAME); + // List segment lineage should also fail for invalid table type. + assertThrows(IOException.class, + () -> ControllerTest.sendGetRequest(urlBuilder.forListAllSegmentLineages(rawTableName, "invalid-type"))); } @Test public void testSegmentCrcApi() throws Exception { // Adding table + String rawTableName = "crcTestTable"; + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setNumReplicas(1).build(); - TEST_INSTANCE.getHelixResourceManager().addTable(tableConfig); - - // Wait for the table addition - while (!TEST_INSTANCE.getHelixResourceManager().hasOfflineTable(TABLE_NAME)) { - Thread.sleep(100); - } + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setNumReplicas(1).build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); // Check when there is no segment. Map segmentMetadataTable = new HashMap<>(); - checkCrcRequest(segmentMetadataTable, 0); + checkCrcRequest(rawTableName, segmentMetadataTable, 0); // Upload Segments for (int i = 0; i < 5; i++) { - SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TABLE_NAME); - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), segmentMetadata, "downloadUrl"); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); segmentMetadataTable.put(segmentMetadata.getName(), segmentMetadata); } // Get crc info from API and check that they are correct. - checkCrcRequest(segmentMetadataTable, 5); + checkCrcRequest(rawTableName, segmentMetadataTable, 5); // validate the segment metadata - Map.Entry entry = - (Map.Entry) segmentMetadataTable.entrySet().toArray()[0]; + String sampleSegment = segmentMetadataTable.keySet().iterator().next(); String resp = ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(TABLE_NAME, entry.getKey())); + TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(rawTableName, sampleSegment)); Map fetchedMetadata = JsonUtils.stringToObject(resp, Map.class); assertEquals(fetchedMetadata.get("segment.download.url"), "downloadUrl"); // use table name with table type resp = ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(TABLE_NAME + "_OFFLINE", entry.getKey())); + TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(offlineTableName, sampleSegment)); fetchedMetadata = JsonUtils.stringToObject(resp, Map.class); assertEquals(fetchedMetadata.get("segment.download.url"), "downloadUrl"); // Add more segments for (int i = 0; i < 5; i++) { - SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TABLE_NAME); - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), segmentMetadata, "downloadUrl"); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); segmentMetadataTable.put(segmentMetadata.getName(), segmentMetadata); } // Get crc info from API and check that they are correct. - checkCrcRequest(segmentMetadataTable, 10); + checkCrcRequest(rawTableName, segmentMetadataTable, 10); - // Delete segments - TEST_INSTANCE.getHelixResourceManager().deleteSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), - segmentMetadataTable.values().iterator().next().getName()); + // Delete one segment + resourceManager.deleteSegment(offlineTableName, sampleSegment); // Check crc api - checkCrcRequest(segmentMetadataTable, 9); + checkCrcRequest(rawTableName, segmentMetadataTable, 9); + } - // Delete offline table - TEST_INSTANCE.getHelixResourceManager().deleteOfflineTable(TABLE_NAME); + @Test + public void testDeleteSegmentsWithTimeWindow() + throws Exception { + // Adding table and segment + String rawTableName = "deleteWithTimeWindowTestTable"; + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setNumReplicas(1) + .setDeletedSegmentsRetentionPeriod("0d").build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName, + 10L, 20L, TimeUnit.MILLISECONDS); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); + + // Send query and verify + ControllerRequestURLBuilder urlBuilder = TEST_INSTANCE.getControllerRequestURLBuilder(); + // case 1: no overlapping + String reply = ControllerTest.sendDeleteRequest(urlBuilder.forSegmentDeleteWithTimeWindowAPI( + rawTableName, 0L, 10L)); + assertTrue(reply.contains("Deleted 0 segments")); + + // case 2: partial overlapping + reply = ControllerTest.sendDeleteRequest(urlBuilder.forSegmentDeleteWithTimeWindowAPI( + rawTableName, 10L, 20L)); + assertTrue(reply.contains("Deleted 0 segments")); + + // case 3: fully within the time window + reply = ControllerTest.sendDeleteRequest(urlBuilder.forSegmentDeleteWithTimeWindowAPI( + rawTableName, 10L, 21L)); + assertTrue(reply.contains("Deleted 1 segments")); } - private void checkCrcRequest(Map metadataTable, int expectedSize) + private void checkCrcRequest(String tableName, Map metadataTable, int expectedSize) throws Exception { String crcMapStr = ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forListAllCrcInformationForTable(TABLE_NAME)); + TEST_INSTANCE.getControllerRequestURLBuilder().forListAllCrcInformationForTable(tableName)); Map crcMap = JsonUtils.stringToObject(crcMapStr, Map.class); for (String segmentName : crcMap.keySet()) { SegmentMetadata metadata = metadataTable.get(segmentName); - assertTrue(metadata != null); + assertNotNull(metadata); assertEquals(crcMap.get(segmentName), metadata.getCrc()); } assertEquals(crcMap.size(), expectedSize); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java index d34c5e6806e..a55e2235513 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java @@ -19,24 +19,33 @@ package org.apache.pinot.controller.api; import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.utils.config.TagNameUtils; import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; +import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; -public class PinotTenantRestletResourceTest { +public class PinotTenantRestletResourceTest extends ControllerTest { private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); private static final String TABLE_NAME = "restletTable_OFFLINE"; + private static final String RAW_TABLE_NAME = "toggleTable"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + @BeforeClass public void setUp() @@ -82,6 +91,83 @@ public void testTableListForTenant() assertEquals(tables.size(), 0); } + @Test + public void testListInstance() + throws Exception { + String listInstancesUrl = + TEST_INSTANCE.getControllerRequestURLBuilder().forTenantGet(TagNameUtils.DEFAULT_TENANT_NAME); + JsonNode instanceList = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listInstancesUrl)); + assertEquals(instanceList.get("ServerInstances").size(), DEFAULT_NUM_SERVER_INSTANCES); + assertEquals(instanceList.get("BrokerInstances").size(), DEFAULT_NUM_BROKER_INSTANCES); + } + + @Test + public void testToggleTenantState() + throws Exception { + // Create an offline table + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(DEFAULT_MIN_NUM_REPLICAS) + .build(); + sendPostRequest(TEST_INSTANCE.getControllerRequestURLBuilder().forTableCreate(), tableConfig.toJsonString()); + assertEquals(TEST_INSTANCE.getHelixAdmin() + .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), CommonConstants.Helix.BROKER_RESOURCE_INSTANCE) + .getInstanceSet(OFFLINE_TABLE_NAME).size(), DEFAULT_NUM_BROKER_INSTANCES); + + // Add segments + for (int i = 0; i < DEFAULT_NUM_SERVER_INSTANCES; i++) { + TEST_INSTANCE.getHelixResourceManager() + .addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(RAW_TABLE_NAME), + "downloadUrl"); + assertEquals(TEST_INSTANCE.getHelixAdmin() + .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), OFFLINE_TABLE_NAME).getNumPartitions(), i + 1); + } + + // Disable server instances + String disableServerInstanceUrl = + TEST_INSTANCE.getControllerRequestURLBuilder().forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, + "server", "disable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(disableServerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(OFFLINE_TABLE_NAME, 0); + + // Enable server instances + String enableServerInstanceUrl = + TEST_INSTANCE.getControllerRequestURLBuilder().forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, + "server", "enable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(enableServerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(OFFLINE_TABLE_NAME, DEFAULT_NUM_SERVER_INSTANCES); + + // Disable broker instances + String disableBrokerInstanceUrl = + TEST_INSTANCE.getControllerRequestURLBuilder().forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, + "broker", "disable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(disableBrokerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(CommonConstants.Helix.BROKER_RESOURCE_INSTANCE, 0); + + // Enable broker instances + String enableBrokerInstanceUrl = + TEST_INSTANCE.getControllerRequestURLBuilder().forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, + "broker", "enable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(enableBrokerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(CommonConstants.Helix.BROKER_RESOURCE_INSTANCE, + DEFAULT_NUM_BROKER_INSTANCES); + + // Delete table + sendDeleteRequest(TEST_INSTANCE.getControllerRequestURLBuilder().forTableDelete(RAW_TABLE_NAME)); + + // Check exception in case of enum mismatch of State + try { + String mismatchStateBrokerInstanceUrl = + TEST_INSTANCE.getControllerRequestURLBuilder().forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, + "broker", "random"); + sendPostRequest(mismatchStateBrokerInstanceUrl); + fail("Passing invalid state to tenant toggle state does not fail."); + } catch (IOException e) { + // Expected 500 Bad Request + assertTrue(e.getMessage().contains("Error: State mentioned random is wrong. " + + "Valid States: Enable, Disable")); + } + } + @AfterClass public void tearDown() { TEST_INSTANCE.cleanup(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java index a506f38ac32..2ee5bb13617 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java @@ -485,6 +485,46 @@ public void testUpdateConfig() sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsDelete(tableName)); } + @Test + public void testForceUpdateTableSchemaAndConfigs() + throws IOException { + String tableName = "testUpdate1"; + TableConfig offlineTableConfig = createOfflineTableConfig(tableName); + Schema schema = createDummySchema(tableName); + TableConfigs tableConfigs = new TableConfigs(tableName, schema, offlineTableConfig, null); + + sendPostRequest(_createTableConfigsUrl, tableConfigs.toPrettyJsonString()); + String response = sendGetRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsGet(tableName)); + TableConfigs tableConfigsResponse = JsonUtils.stringToObject(response, TableConfigs.class); + Assert.assertNotNull(tableConfigs.getOffline()); + + // Remove field from schema and try to update schema without the 'forceTableSchemaUpdate' option + schema.removeField("dimA"); + tableConfigs = + new TableConfigs(tableName, schema, tableConfigsResponse.getOffline(), tableConfigsResponse.getRealtime()); + + String tableConfigUpdateUrl = DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsUpdate(tableName); + try { + sendPutRequest(tableConfigUpdateUrl, tableConfigs.toPrettyJsonString()); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().contains("is not backward-compatible with the existing schema")); + } + + // Skip validate table configs – Exception is still thrown + String newTableConfigUpdateUrl = tableConfigUpdateUrl + "?validationTypesToSkip=ALL"; + try { + sendPutRequest(newTableConfigUpdateUrl, tableConfigs.toPrettyJsonString()); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().contains("is not backward-compatible with the existing schema")); + } + + // Skip table config validation as well as force update the table schema – no exceptions are thrown + newTableConfigUpdateUrl = tableConfigUpdateUrl + "?validationTypesToSkip=ALL&forceTableSchemaUpdate=true"; + response = sendPutRequest(newTableConfigUpdateUrl, tableConfigs.toPrettyJsonString()); + Assert.assertTrue(response.contains("TableConfigs updated for testUpdate1")); + sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsDelete(tableName)); + } + @Test public void testDeleteConfig() throws Exception { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java index 7b460161dbd..dc82319ed7c 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java @@ -81,25 +81,25 @@ public class TableSizeReaderTest { private PinotHelixResourceManager _helix; @BeforeClass - public void setUp() - throws IOException { + public void setUp() throws IOException { _helix = mock(PinotHelixResourceManager.class); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable").setNumReplicas(NUM_REPLICAS).build(); ZkHelixPropertyStore mockPropertyStore = mock(ZkHelixPropertyStore.class); - when(mockPropertyStore.get(ArgumentMatchers.anyString(), ArgumentMatchers.eq(null), - ArgumentMatchers.eq(AccessOption.PERSISTENT))).thenAnswer((Answer) invocationOnMock -> { - String path = (String) invocationOnMock.getArguments()[0]; - if (path.contains("realtime_REALTIME")) { - return TableConfigUtils.toZNRecord(tableConfig); - } - if (path.contains("offline_OFFLINE")) { - return TableConfigUtils.toZNRecord(tableConfig); - } - return null; - }); + when(mockPropertyStore + .get(ArgumentMatchers.anyString(), ArgumentMatchers.eq(null), ArgumentMatchers.eq(AccessOption.PERSISTENT))) + .thenAnswer((Answer) invocationOnMock -> { + String path = (String) invocationOnMock.getArguments()[0]; + if (path.contains("realtime_REALTIME")) { + return TableConfigUtils.toZNRecord(tableConfig); + } + if (path.contains("offline_OFFLINE")) { + return TableConfigUtils.toZNRecord(tableConfig); + } + return null; + }); when(_helix.getPropertyStore()).thenReturn(mockPropertyStore); when(_helix.getNumReplicas(ArgumentMatchers.eq(tableConfig))).thenReturn(NUM_REPLICAS); @@ -152,8 +152,7 @@ public void tearDown() { private HttpHandler createHandler(final int status, final List segmentSizes, final int sleepTimeMs) { return new HttpHandler() { @Override - public void handle(HttpExchange httpExchange) - throws IOException { + public void handle(HttpExchange httpExchange) throws IOException { if (sleepTimeMs > 0) { try { Thread.sleep(sleepTimeMs); @@ -219,8 +218,7 @@ private BiMap serverEndpoints(String... servers) { } @Test - public void testNoSuchTable() - throws InvalidConfigException { + public void testNoSuchTable() throws InvalidConfigException { TableSizeReader reader = new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _helix); assertNull(reader.getTableSizeDetails("mytable", 5000)); } @@ -229,16 +227,14 @@ private TableSizeReader.TableSizeDetails testRunner(final String[] servers, Stri throws InvalidConfigException { when(_helix.getServerToSegmentsMap(anyString())).thenAnswer(new Answer() { @Override - public Object answer(InvocationOnMock invocationOnMock) - throws Throwable { + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { return subsetOfServerSegments(servers); } }); when(_helix.getDataInstanceAdminEndpoints(ArgumentMatchers.anySet())).thenAnswer(new Answer() { @Override - public Object answer(InvocationOnMock invocationOnMock) - throws Throwable { + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { return serverEndpoints(servers); } }); @@ -252,11 +248,7 @@ private Map> segmentToServers(final String... servers) { for (String server : servers) { List segments = _serverMap.get(server)._segments; for (String segment : segments) { - List segServers = segmentServers.get(segment); - if (segServers == null) { - segServers = new ArrayList(); - segmentServers.put(segment, segServers); - } + List segServers = segmentServers.computeIfAbsent(segment, k -> new ArrayList()); segServers.add(server); } } @@ -267,7 +259,7 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub Map> segmentServers = segmentToServers(servers); long reportedSize = 0; long estimatedSize = 0; - long maxSegmentSize = 0; + long reportedSizePerReplica = 0L; boolean hasErrors = false; for (Map.Entry> segmentEntry : segmentServers.entrySet()) { final String segmentName = segmentEntry.getKey(); @@ -278,6 +270,9 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub if (segmentDetails._estimatedSizeInBytes != TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { estimatedSize += segmentDetails._estimatedSizeInBytes; } + if (segmentDetails._maxReportedSizePerReplicaInBytes != TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { + reportedSizePerReplica += segmentDetails._maxReportedSizePerReplicaInBytes; + } assertNotNull(segmentDetails); final List expectedServers = segmentEntry.getValue(); @@ -293,13 +288,17 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub if (numResponses != 0) { assertEquals(segmentDetails._reportedSizeInBytes, numResponses * expectedSegmentSize); assertEquals(segmentDetails._estimatedSizeInBytes, expectedServers.size() * expectedSegmentSize); + assertEquals(segmentDetails._maxReportedSizePerReplicaInBytes, expectedSegmentSize); } else { assertEquals(segmentDetails._reportedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); assertEquals(segmentDetails._estimatedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); + assertEquals(segmentDetails._maxReportedSizePerReplicaInBytes, + TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); } } assertEquals(tableSize._reportedSizeInBytes, reportedSize); assertEquals(tableSize._estimatedSizeInBytes, estimatedSize); + assertEquals(tableSize._reportedSizePerReplicaInBytes, reportedSizePerReplica); if (hasErrors) { assertTrue(tableSize._reportedSizeInBytes != tableSize._estimatedSizeInBytes); assertTrue(tableSize._missingSegments > 0); @@ -307,8 +306,7 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub } @Test - public void testGetTableSubTypeSizeAllSuccess() - throws InvalidConfigException { + public void testGetTableSubTypeSizeAllSuccess() throws InvalidConfigException { final String[] servers = {"server0", "server1"}; String table = "offline"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, table); @@ -320,23 +318,24 @@ public void testGetTableSubTypeSizeAllSuccess() assertNull(tableSizeDetails._realtimeSegments); assertEquals(tableSizeDetails._reportedSizeInBytes, offlineSizes._reportedSizeInBytes); assertEquals(tableSizeDetails._estimatedSizeInBytes, offlineSizes._estimatedSizeInBytes); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, offlineSizes._reportedSizePerReplicaInBytes); String tableNameWithType = TableNameBuilder.OFFLINE.tableNameWithType(table); assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 0); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), offlineSizes._estimatedSizeInBytes); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 160); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + offlineSizes._estimatedSizeInBytes); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), + 160); } @Test - public void testGetTableSubTypeSizeAllErrors() - throws InvalidConfigException { + public void testGetTableSubTypeSizeAllErrors() throws InvalidConfigException { final String[] servers = {"server2", "server5"}; String table = "offline"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, table); @@ -346,24 +345,23 @@ public void testGetTableSubTypeSizeAllErrors() assertEquals(offlineSizes._segments.size(), 3); assertEquals(offlineSizes._reportedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); assertEquals(tableSizeDetails._estimatedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); String tableNameWithType = TableNameBuilder.OFFLINE.tableNameWithType(table); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 100); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), offlineSizes._estimatedSizeInBytes); - assertFalse( - MetricValueUtils.tableGaugeExists(_controllerMetrics, tableNameWithType, - ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER)); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 100); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + offlineSizes._estimatedSizeInBytes); + assertFalse(MetricValueUtils + .tableGaugeExists(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER)); } @Test - public void testGetTableSubTypeSizesWithErrors() - throws InvalidConfigException { + public void testGetTableSubTypeSizesWithErrors() throws InvalidConfigException { final String[] servers = {"server0", "server1", "server2", "server5"}; String table = "offline"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, "offline"); @@ -374,23 +372,25 @@ public void testGetTableSubTypeSizesWithErrors() validateTableSubTypeSize(servers, offlineSizes); assertNull(tableSizeDetails._realtimeSegments); String tableNameWithType = TableNameBuilder.OFFLINE.tableNameWithType(table); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 20); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), offlineSizes._estimatedSizeInBytes); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 160); + assertEquals(tableSizeDetails._reportedSizeInBytes, offlineSizes._reportedSizeInBytes); + assertEquals(tableSizeDetails._estimatedSizeInBytes, offlineSizes._estimatedSizeInBytes); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, offlineSizes._reportedSizePerReplicaInBytes); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 20); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + offlineSizes._estimatedSizeInBytes); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), + 160); } @Test - public void getTableSizeDetailsRealtimeOnly() - throws InvalidConfigException { + public void getTableSizeDetailsRealtimeOnly() throws InvalidConfigException { final String[] servers = {"server3", "server4"}; String table = "realtime"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, table); @@ -399,15 +399,19 @@ public void getTableSizeDetailsRealtimeOnly() assertEquals(realtimeSegments._segments.size(), 2); assertEquals(realtimeSegments._estimatedSizeInBytes, realtimeSegments._reportedSizeInBytes); validateTableSubTypeSize(servers, realtimeSegments); + assertEquals(tableSizeDetails._reportedSizeInBytes, realtimeSegments._reportedSizeInBytes); + assertEquals(tableSizeDetails._estimatedSizeInBytes, realtimeSegments._estimatedSizeInBytes); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, realtimeSegments._reportedSizePerReplicaInBytes); String tableNameWithType = TableNameBuilder.REALTIME.tableNameWithType(table); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), realtimeSegments._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), realtimeSegments._estimatedSizeInBytes); - assertEquals( - MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, - ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 120); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + realtimeSegments._estimatedSizeInBytes / NUM_REPLICAS); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + realtimeSegments._estimatedSizeInBytes); + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), + 120); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResourceTest.java new file mode 100644 index 00000000000..764b1d53d16 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResourceTest.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.resources; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Map; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.UriInfo; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.api.exception.ControllerApplicationException; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.controller.helix.core.minion.PinotHelixTaskResourceManager; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertThrows; + + +public class PinotTaskRestletResourceTest { + @Mock + PinotHelixResourceManager _pinotHelixResourceManager; + @Mock + PinotHelixTaskResourceManager _pinotHelixTaskResourceManager; + @Mock + ControllerConf _controllerConf; + @Mock + UriInfo _uriInfo; + + @InjectMocks + PinotTaskRestletResource _pinotTaskRestletResource; + + @BeforeMethod + public void init() + throws URISyntaxException { + MockitoAnnotations.openMocks(this); + when(_uriInfo.getRequestUri()).thenReturn(new URI("http://localhost:9000")); + } + + @Test + public void testGetSubtaskWithGivenStateProgressWhenMinionWorkerIdsAreNotSpecified() + throws JsonProcessingException { + Map minionWorkerEndpoints + = invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints(null); + assertEquals(minionWorkerEndpoints, + ImmutableMap.of("minion1", "http://minion1:9514", "minion2", "http://minion2:9514")); + } + + @Test + public void testGetSubtaskWithGivenStateProgressWhenAllMinionWorkerIdsAreSpecified() + throws JsonProcessingException { + // use minion worker ids with spaces ensure they will be trimmed. + Map minionWorkerEndpoints + = invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints(" minion1 , minion2 "); + assertEquals(minionWorkerEndpoints, + ImmutableMap.of("minion1", "http://minion1:9514", "minion2", "http://minion2:9514")); + } + + @Test + public void testGetSubtaskWithGivenStateProgressWhenOneMinionWorkerIdIsSpecified() + throws JsonProcessingException { + Map minionWorkerEndpoints + = invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints("minion1"); + assertEquals(minionWorkerEndpoints, + ImmutableMap.of("minion1", "http://minion1:9514")); + } + + private Map invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints( + String minionWorkerIds) + throws JsonProcessingException { + InstanceConfig minion1 = createInstanceConfig("minion1", "minion1", "9514"); + InstanceConfig minion2 = createInstanceConfig("minion2", "minion2", "9514"); + when(_pinotHelixResourceManager.getAllMinionInstanceConfigs()).thenReturn(ImmutableList.of(minion1, minion2)); + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(httpHeaders.getRequestHeaders()).thenReturn(new MultivaluedHashMap<>()); + ArgumentCaptor> minionWorkerEndpointsCaptor = ArgumentCaptor.forClass(Map.class); + when(_pinotHelixTaskResourceManager.getSubtaskOnWorkerProgress(anyString(), any(), any(), + minionWorkerEndpointsCaptor.capture(), anyMap(), anyInt())) + .thenReturn(Collections.emptyMap()); + String progress = + _pinotTaskRestletResource.getSubtaskOnWorkerProgress(httpHeaders, "IN_PROGRESS", minionWorkerIds); + assertEquals(progress, "{}"); + return minionWorkerEndpointsCaptor.getValue(); + } + + private InstanceConfig createInstanceConfig(String instanceId, String hostName, String port) { + InstanceConfig instanceConfig = new InstanceConfig(instanceId); + instanceConfig.setHostName(hostName); + instanceConfig.setPort(port); + return instanceConfig; + } + + + @Test + public void testGetSubtaskWithGivenStateProgressWithException() + throws JsonProcessingException { + when(_pinotHelixResourceManager.getAllMinionInstanceConfigs()).thenReturn(Collections.emptyList()); + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(httpHeaders.getRequestHeaders()).thenReturn(new MultivaluedHashMap<>()); + when(_pinotHelixTaskResourceManager + .getSubtaskOnWorkerProgress(anyString(), any(), any(), anyMap(), anyMap(), anyInt())) + .thenThrow(new RuntimeException()); + assertThrows(ControllerApplicationException.class, + () -> _pinotTaskRestletResource.getSubtaskOnWorkerProgress(httpHeaders, "IN_PROGRESS", null)); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java index 746966ec47d..6fe3e565761 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java @@ -19,10 +19,7 @@ package org.apache.pinot.controller.helix; import java.io.IOException; -import java.util.Set; -import org.apache.helix.model.ExternalView; import org.apache.pinot.common.utils.config.TagNameUtils; -import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -35,7 +32,6 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; public class ControllerInstanceToggleTest extends ControllerTest { @@ -120,7 +116,8 @@ private void toggleInstanceState(String instanceName, String state) { // It may take time for an instance to toggle the state. TestUtils.waitForCondition(aVoid -> { try { - sendPostRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forInstanceState(instanceName), state); + sendPutRequest( + DEFAULT_INSTANCE.getControllerRequestURLBuilder().forInstanceState(instanceName) + "?state=" + state); } catch (IOException ioe) { // receive non-200 status code return false; @@ -129,21 +126,6 @@ private void toggleInstanceState(String instanceName, String state) { }, TIMEOUT_MS, "Failed to toggle instance state: '" + state + "' for instance: " + instanceName); } - private void checkNumOnlineInstancesFromExternalView(String resourceName, int expectedNumOnlineInstances) - throws InterruptedException { - long endTime = System.currentTimeMillis() + TIMEOUT_MS; - while (System.currentTimeMillis() < endTime) { - ExternalView resourceExternalView = DEFAULT_INSTANCE.getHelixAdmin() - .getResourceExternalView(DEFAULT_INSTANCE.getHelixClusterName(), resourceName); - Set instanceSet = HelixHelper.getOnlineInstanceFromExternalView(resourceExternalView); - if (instanceSet.size() == expectedNumOnlineInstances) { - return; - } - Thread.sleep(100L); - } - fail("Failed to reach " + expectedNumOnlineInstances + " online instances for resource: " + resourceName); - } - @AfterClass public void tearDown() { DEFAULT_INSTANCE.cleanup(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java index 3259c5148fa..43aef8af8bf 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -42,6 +43,7 @@ import org.apache.helix.InstanceType; import org.apache.helix.NotificationContext; import org.apache.helix.model.ClusterConfig; +import org.apache.helix.model.ExternalView; import org.apache.helix.model.HelixConfigScope; import org.apache.helix.model.Message; import org.apache.helix.model.ResourceConfig; @@ -57,6 +59,7 @@ import org.apache.pinot.common.utils.SimpleHttpResponse; import org.apache.pinot.common.utils.ZkStarter; import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.common.utils.http.HttpClient; import org.apache.pinot.controller.BaseControllerStarter; import org.apache.pinot.controller.ControllerConf; @@ -76,6 +79,7 @@ import org.apache.pinot.spi.utils.NetUtils; import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.util.TestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,6 +88,7 @@ import static org.apache.pinot.spi.utils.CommonConstants.Server.DEFAULT_ADMIN_API_PORT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; public class ControllerTest { @@ -103,6 +108,8 @@ public class ControllerTest { // NOTE: To add HLC realtime table, number of Server instances must be multiple of replicas public static final int DEFAULT_NUM_SERVER_INSTANCES = 4; + public static final long TIMEOUT_MS = 10_000L; + /** * default static instance used to access all wrapped static instances. */ @@ -112,8 +119,8 @@ public class ControllerTest { protected static HttpClient _httpClient = null; - protected int _controllerPort; - protected String _controllerBaseApiUrl; + private int _controllerPort; + private String _controllerBaseApiUrl; protected ControllerConf _controllerConfig; protected ControllerRequestURLBuilder _controllerRequestURLBuilder; @@ -210,9 +217,14 @@ public Map getDefaultControllerConfiguration() { properties.put(ControllerConf.HELIX_CLUSTER_NAME, getHelixClusterName()); // Enable groovy on the controller properties.put(ControllerConf.DISABLE_GROOVY, false); + overrideControllerConf(properties); return properties; } + protected void overrideControllerConf(Map properties) { + // do nothing, to be overridden by tests if they need something specific + } + public void startController() throws Exception { startController(getDefaultControllerConfiguration()); @@ -615,6 +627,11 @@ public void addSchema(Schema schema) getControllerRequestClient().addSchema(schema); } + public void updateSchema(Schema schema) + throws IOException { + getControllerRequestClient().updateSchema(schema); + } + public Schema getSchema(String schemaName) { Schema schema = _helixResourceManager.getSchema(schemaName); assertNotNull(schema); @@ -658,6 +675,11 @@ public void dropRealtimeTable(String tableName) getControllerRequestClient().deleteTable(TableNameBuilder.REALTIME.tableNameWithType(tableName)); } + public void waitForEVToDisappear(String tableNameWithType) { + TestUtils.waitForCondition(aVoid -> _helixResourceManager.getTableExternalView(tableNameWithType) == null, 60_000L, + "Failed to clean up the external view for table: " + tableNameWithType); + } + public List listSegments(String tableName) throws IOException { return listSegments(tableName, null, false); @@ -754,6 +776,10 @@ public static String sendGetRequestRaw(String urlString) return IOUtils.toString(new URL(urlString).openStream()); } + public static String sendPostRequest(String urlString) + throws IOException { + return sendPostRequest(urlString, null); + } public static String sendPostRequest(String urlString, String payload) throws IOException { return sendPostRequest(urlString, payload, Collections.emptyMap()); @@ -948,6 +974,24 @@ public void stopSharedTestSetup() { stopZk(); } + /** + * Checks if the number of online instances for a given resource matches the expected num of instances or not. + */ + public void checkNumOnlineInstancesFromExternalView(String resourceName, int expectedNumOnlineInstances) + throws InterruptedException { + long endTime = System.currentTimeMillis() + TIMEOUT_MS; + while (System.currentTimeMillis() < endTime) { + ExternalView resourceExternalView = DEFAULT_INSTANCE.getHelixAdmin() + .getResourceExternalView(DEFAULT_INSTANCE.getHelixClusterName(), resourceName); + Set instanceSet = HelixHelper.getOnlineInstanceFromExternalView(resourceExternalView); + if (instanceSet.size() == expectedNumOnlineInstances) { + return; + } + Thread.sleep(100L); + } + fail("Failed to reach " + expectedNumOnlineInstances + " online instances for resource: " + resourceName); + } + /** * Make sure shared state is setup and valid before each test case class is run. */ @@ -979,19 +1023,28 @@ public void setupSharedStateAndValidate() * test functionality. */ public void cleanup() { - - // Delete all tables. - List tables = getHelixResourceManager().getAllTables(); - for (String table : tables) { - getHelixResourceManager().deleteOfflineTable(table); - getHelixResourceManager().deleteRealtimeTable(table); + // Delete all tables + List tables = _helixResourceManager.getAllTables(); + for (String tableNameWithType : tables) { + if (TableNameBuilder.isOfflineTableResource(tableNameWithType)) { + _helixResourceManager.deleteOfflineTable(tableNameWithType); + } else { + _helixResourceManager.deleteRealtimeTable(tableNameWithType); + } } + // Wait for all external views to disappear + Set tablesWithEV = new HashSet<>(tables); + TestUtils.waitForCondition(aVoid -> { + tablesWithEV.removeIf(t -> _helixResourceManager.getTableExternalView(t) == null); + return tablesWithEV.isEmpty(); + }, 60_000L, "Failed to clean up all the external views"); + // Delete all schemas. - List schemaNames = getHelixResourceManager().getSchemaNames(); + List schemaNames = _helixResourceManager.getSchemaNames(); if (CollectionUtils.isNotEmpty(schemaNames)) { for (String schemaName : schemaNames) { - getHelixResourceManager().deleteSchema(getHelixResourceManager().getSchema(schemaName)); + getHelixResourceManager().deleteSchema(schemaName); } } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java index 445e43a8712..6f4c87de04c 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java @@ -23,10 +23,12 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.Future; import org.apache.helix.model.IdealState; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; @@ -36,65 +38,64 @@ import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import static org.testng.Assert.*; + public class PinotResourceManagerTest { - private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); - private static final String OFFLINE_TABLE_NAME = "offlineResourceManagerTestTable_OFFLINE"; - private static final String REALTIME_TABLE_NAME = "realtimeResourceManagerTestTable_REALTIME"; - private static final String NUM_REPLICAS_STRING = "2"; - private static final String PARTITION_COLUMN = "Partition_Column"; + private static final String RAW_TABLE_NAME = "testTable"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); + private static final int NUM_REPLICAS = 2; + private static final String PARTITION_COLUMN = "partitionColumn"; + + private final ControllerTest _testInstance = ControllerTest.getInstance(); + private PinotHelixResourceManager _resourceManager; @BeforeClass public void setUp() throws Exception { - TEST_INSTANCE.setupSharedStateAndValidate(); + _testInstance.setupSharedStateAndValidate(); + _resourceManager = _testInstance.getHelixResourceManager(); // Adding an offline table - TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(OFFLINE_TABLE_NAME).build(); - TEST_INSTANCE.getHelixResourceManager().addTable(offlineTableConfig); + TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + _resourceManager.addTable(offlineTableConfig); // Adding an upsert enabled realtime table which consumes from a stream with 2 partitions - Schema dummySchema = TEST_INSTANCE.createDummySchema(REALTIME_TABLE_NAME); - TEST_INSTANCE.addSchema(dummySchema); + Schema dummySchema = ControllerTest.createDummySchema(RAW_TABLE_NAME); + _testInstance.addSchema(dummySchema); Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); TableConfig realtimeTableConfig = - new TableConfigBuilder(TableType.REALTIME).setStreamConfigs(streamConfigs).setTableName(REALTIME_TABLE_NAME) - .setSchemaName(dummySchema.getSchemaName()).build(); - realtimeTableConfig.getValidationConfig().setReplicasPerPartition(NUM_REPLICAS_STRING); - realtimeTableConfig.getValidationConfig() - .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1)); - realtimeTableConfig.setUpsertConfig(new UpsertConfig(UpsertConfig.Mode.FULL)); - TEST_INSTANCE.getHelixResourceManager().addTable(realtimeTableConfig); + new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) + .setStreamConfigs(streamConfigs) + .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1)) + .setUpsertConfig(new UpsertConfig(UpsertConfig.Mode.FULL)).build(); + _resourceManager.addTable(realtimeTableConfig); } @Test public void testTableCleanupAfterRealtimeClusterException() throws Exception { - String invalidRealtimeTable = "invalidTable_REALTIME"; - Schema dummySchema = TEST_INSTANCE.createDummySchema(invalidRealtimeTable); - TEST_INSTANCE.addSchema(dummySchema); + String invalidRawTableName = "invalidTable"; + Schema dummySchema = ControllerTest.createDummySchema(invalidRawTableName); + _testInstance.addSchema(dummySchema); - // Missing replicasPerPartition + // Missing stream config TableConfig invalidRealtimeTableConfig = - new TableConfigBuilder(TableType.REALTIME).setTableName(invalidRealtimeTable) - .setSchemaName(dummySchema.getSchemaName()).build(); - + new TableConfigBuilder(TableType.REALTIME).setTableName(invalidRawTableName).build(); try { - TEST_INSTANCE.getHelixResourceManager().addTable(invalidRealtimeTableConfig); - Assert.fail( - "Table creation should have thrown exception due to missing stream config and replicasPerPartition in " - + "validation config"); + _resourceManager.addTable(invalidRealtimeTableConfig); + fail("Table creation should have thrown exception due to missing stream config in validation config"); } catch (Exception e) { // expected } // Verify invalid table config is cleaned up - Assert.assertNull(TEST_INSTANCE.getHelixResourceManager().getTableConfig(invalidRealtimeTable)); + assertNull(_resourceManager.getTableConfig(invalidRealtimeTableConfig.getTableName())); } @Test @@ -102,144 +103,130 @@ public void testUpdateSegmentZKMetadata() { SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata("testSegment"); // Segment ZK metadata does not exist - Assert.assertFalse(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata, 0)); + assertFalse(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata, 0)); // Set segment ZK metadata - Assert.assertTrue(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata)); + assertTrue(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata)); // Update ZK metadata - Assert.assertEquals(TEST_INSTANCE.getHelixResourceManager() - .getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME + "_OFFLINE", "testSegment").getVersion(), 0); - Assert.assertTrue(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata, 0)); - Assert.assertEquals(TEST_INSTANCE.getHelixResourceManager() - .getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME + "_OFFLINE", "testSegment").getVersion(), 1); - Assert.assertFalse(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata, 0)); + ZNRecord segmentMetadataZnRecord = _resourceManager.getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME, "testSegment"); + assertNotNull(segmentMetadataZnRecord); + assertEquals(segmentMetadataZnRecord.getVersion(), 0); + assertTrue(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata, 0)); + segmentMetadataZnRecord = _resourceManager.getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME, "testSegment"); + assertNotNull(segmentMetadataZnRecord); + assertEquals(segmentMetadataZnRecord.getVersion(), 1); + assertFalse(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata, 0)); } /** * First tests basic segment adding/deleting. - * Then creates 3 threads that concurrently try to add 10 segments each, and asserts that we have - * 100 segments in the end. Then launches 5 threads again that concurrently try to delete all segments, - * and makes sure that we have zero segments left in the end. - * @throws Exception + * Then creates 3 threads that concurrently try to add 10 segments each, and asserts that we have 30 segments in the + * end. Then launches 3 threads again that concurrently try to delete all segments, and makes sure that we have 0 + * segments left in the end. */ - @Test public void testBasicAndConcurrentAddingAndDeletingSegments() throws Exception { - final String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(OFFLINE_TABLE_NAME); + PinotHelixResourceManager resourceManager = _resourceManager; - // Basic add/delete case + // Basic add/delete for (int i = 1; i <= 2; i++) { - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), - "downloadUrl"); + resourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), "downloadUrl"); } - IdealState idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); + IdealState idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); Set segments = idealState.getPartitionSet(); - Assert.assertEquals(segments.size(), 2); + assertEquals(segments.size(), 2); for (String segmentName : segments) { - TEST_INSTANCE.getHelixResourceManager().deleteSegment(offlineTableName, segmentName); + resourceManager.deleteSegment(OFFLINE_TABLE_NAME, segmentName); } - idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); - Assert.assertEquals(idealState.getPartitionSet().size(), 0); + idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); + assertEquals(idealState.getNumPartitions(), 0); - // Concurrent segment deletion - ExecutorService addSegmentExecutor = Executors.newFixedThreadPool(3); + // Concurrent add/deletion + ExecutorService executor = Executors.newFixedThreadPool(3); + Future[] futures = new Future[3]; for (int i = 0; i < 3; i++) { - addSegmentExecutor.execute(new Runnable() { - @Override - public void run() { - for (int i = 0; i < 10; i++) { - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), - "downloadUrl"); - } + futures[i] = executor.submit(() -> { + for (int i1 = 0; i1 < 10; i1++) { + resourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), "downloadUrl"); } }); } - addSegmentExecutor.shutdown(); - addSegmentExecutor.awaitTermination(1, TimeUnit.MINUTES); - - idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); - Assert.assertEquals(idealState.getPartitionSet().size(), 30); - - ExecutorService deleteSegmentExecutor = Executors.newFixedThreadPool(3); - for (final String segmentName : idealState.getPartitionSet()) { - deleteSegmentExecutor.execute(new Runnable() { - @Override - public void run() { - TEST_INSTANCE.getHelixResourceManager().deleteSegment(offlineTableName, segmentName); - } - }); + for (int i = 0; i < 3; i++) { + futures[i].get(); + } + idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); + segments = idealState.getPartitionSet(); + assertEquals(segments.size(), 30); + + futures = new Future[30]; + int index = 0; + for (String segment : segments) { + futures[index++] = executor.submit(() -> resourceManager.deleteSegment(OFFLINE_TABLE_NAME, segment)); + } + for (int i = 0; i < 30; i++) { + futures[i].get(); } - deleteSegmentExecutor.shutdown(); - deleteSegmentExecutor.awaitTermination(1, TimeUnit.MINUTES); + idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); + assertEquals(idealState.getNumPartitions(), 0); - idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); - Assert.assertEquals(idealState.getPartitionSet().size(), 0); + executor.shutdown(); } @Test public void testAddingRealtimeTableSegmentsWithPartitionIdInZkMetadata() { // Add three segments: two from partition 0 and 1 from partition 1; - String partition0Segment0 = "realtimeResourceManagerTestTable__aa"; - String partition0Segment1 = "realtimeResourceManagerTestTable__bb"; - String partition1Segment1 = "realtimeResourceManagerTestTable__cc"; - TEST_INSTANCE.getHelixResourceManager().addNewSegment(REALTIME_TABLE_NAME, SegmentMetadataMockUtils - .mockSegmentMetadataWithPartitionInfo(REALTIME_TABLE_NAME, partition0Segment0, PARTITION_COLUMN, 0), - "downloadUrl"); - TEST_INSTANCE.getHelixResourceManager().addNewSegment(REALTIME_TABLE_NAME, SegmentMetadataMockUtils - .mockSegmentMetadataWithPartitionInfo(REALTIME_TABLE_NAME, partition0Segment1, PARTITION_COLUMN, 0), - "downloadUrl"); - TEST_INSTANCE.getHelixResourceManager().addNewSegment(REALTIME_TABLE_NAME, SegmentMetadataMockUtils - .mockSegmentMetadataWithPartitionInfo(REALTIME_TABLE_NAME, partition1Segment1, PARTITION_COLUMN, 1), - "downloadUrl"); - Map segment2PartitionId = new HashMap<>(); - segment2PartitionId.put(partition0Segment0, 0); - segment2PartitionId.put(partition0Segment1, 0); - segment2PartitionId.put(partition1Segment1, 1); - - IdealState idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), - TableNameBuilder.REALTIME.tableNameWithType(REALTIME_TABLE_NAME)); + String partition0Segment0 = "p0s0"; + String partition0Segment1 = "p0s1"; + String partition1Segment0 = "p1s0"; + _resourceManager.addNewSegment(REALTIME_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithPartitionInfo(RAW_TABLE_NAME, partition0Segment0, + PARTITION_COLUMN, 0), "downloadUrl"); + _resourceManager.addNewSegment(REALTIME_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithPartitionInfo(RAW_TABLE_NAME, partition0Segment1, + PARTITION_COLUMN, 0), "downloadUrl"); + _resourceManager.addNewSegment(REALTIME_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithPartitionInfo(RAW_TABLE_NAME, partition1Segment0, + PARTITION_COLUMN, 1), "downloadUrl"); + + IdealState idealState = _resourceManager.getTableIdealState(REALTIME_TABLE_NAME); + assertNotNull(idealState); Set segments = idealState.getPartitionSet(); - Assert.assertEquals(segments.size(), 5); - Assert.assertTrue(segments.contains(partition0Segment0)); - Assert.assertTrue(segments.contains(partition0Segment1)); - Assert.assertTrue(segments.contains(partition1Segment1)); + // 2 consuming segments, 3 uploaded segments + assertEquals(segments.size(), 5); + assertTrue(segments.contains(partition0Segment0)); + assertTrue(segments.contains(partition0Segment1)); + assertTrue(segments.contains(partition1Segment0)); // Check the segments of the same partition is assigned to the same set of servers. - Map> segmentAssignment = new HashMap<>(); + Map> partitionIdToServersMap = new HashMap<>(); for (String segment : segments) { - Integer partitionId; + int partitionId; LLCSegmentName llcSegmentName = LLCSegmentName.of(segment); if (llcSegmentName != null) { partitionId = llcSegmentName.getPartitionGroupId(); } else { - partitionId = segment2PartitionId.get(segment); + partitionId = Integer.parseInt(segment.substring(1, 2)); } - Assert.assertNotNull(partitionId); Set instances = idealState.getInstanceSet(segment); - if (segmentAssignment.containsKey(partitionId)) { - Assert.assertEquals(instances, segmentAssignment.get(partitionId)); + if (partitionIdToServersMap.containsKey(partitionId)) { + assertEquals(instances, partitionIdToServersMap.get(partitionId)); } else { - segmentAssignment.put(partitionId, instances); + partitionIdToServersMap.put(partitionId, instances); } } } @AfterClass public void tearDown() { - TEST_INSTANCE.cleanup(); + _testInstance.cleanup(); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java index a2ae5aaa963..22ddedd2573 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java @@ -696,6 +696,69 @@ public void noSegments() noSegmentsInternal(-1); } + @Test + public void lessThanOnePercentSegmentsUnavailableTest() + throws Exception { + String tableName = "myTable_OFFLINE"; + int numSegments = 200; + List allTableNames = new ArrayList(); + allTableNames.add(tableName); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(tableName).setNumReplicas(1).build(); + + IdealState idealState = new IdealState(tableName); + for (int i = 0; i < numSegments; i++) { + idealState.setPartitionState("myTable_" + i, "pinot1", "ONLINE"); + } + idealState.setReplicas("1"); + idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED); + + ExternalView externalView = new ExternalView(tableName); + externalView.setState("myTable_0", "pinot1", "OFFLINE"); + for (int i = 1; i < numSegments; i++) { + externalView.setState("myTable_" + i, "pinot1", "ONLINE"); + } + + { + _helixResourceManager = mock(PinotHelixResourceManager.class); + when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); + when(_helixResourceManager.getTableConfig(tableName)).thenReturn(tableConfig); + when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(externalView); + } + { + _helixPropertyStore = mock(ZkHelixPropertyStore.class); + when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); + SegmentLineage segmentLineage = new SegmentLineage(tableName); + when(_helixPropertyStore.get(eq("/SEGMENT_LINEAGE/" + tableName), any(), eq(AccessOption.PERSISTENT))) + .thenReturn(segmentLineage.toZNRecord()); + } + { + _config = mock(ControllerConf.class); + when(_config.getStatusCheckerFrequencyInSeconds()).thenReturn(300); + when(_config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300); + } + { + _leadControllerManager = mock(LeadControllerManager.class); + when(_leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); + } + { + _tableSizeReader = mock(TableSizeReader.class); + when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); + } + PinotMetricUtils.cleanUp(); + _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); + _controllerMetrics = new ControllerMetrics(_metricsRegistry); + _segmentStatusChecker = + new SegmentStatusChecker(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics, + _executorService); + _segmentStatusChecker.setTableSizeReader(_tableSizeReader); + _segmentStatusChecker.start(); + _segmentStatusChecker.run(); + Assert.assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 99); + } + public void noSegmentsInternal(final int nReplicas) throws Exception { final String tableName = "myTable_REALTIME"; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java index 9f2024dd99c..17880643e02 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java @@ -98,6 +98,7 @@ public void testTableCache(boolean isCaseInsensitive) // Add a table config TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setSchemaName(SCHEMA_NAME).build(); + TEST_INSTANCE.waitForEVToDisappear(tableConfig.getTableName()); TEST_INSTANCE.getHelixResourceManager().addTable(tableConfig); // Wait for at most 10 seconds for the callback to add the table config to the cache TestUtils.waitForCondition( @@ -128,7 +129,7 @@ public void testTableCache(boolean isCaseInsensitive) // Update the schema schema.addField(new DimensionFieldSpec("newColumn", DataType.LONG, true)); - TEST_INSTANCE.getHelixResourceManager().updateSchema(schema, false); + TEST_INSTANCE.getHelixResourceManager().updateSchema(schema, false, false); // Wait for at most 10 seconds for the callback to update the schema in the cache // NOTE: // - Schema should never be null during the transitioning @@ -196,7 +197,7 @@ public void testTableCache(boolean isCaseInsensitive) assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); // Remove the schema - TEST_INSTANCE.getHelixResourceManager().deleteSchema(schema); + TEST_INSTANCE.getHelixResourceManager().deleteSchema(SCHEMA_NAME); // Wait for at most 10 seconds for the callback to remove the schema from the cache // NOTE: // - Verify if the callback is fully done by checking the schema change lister because it is the last step of the diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerAssignmentTest.java new file mode 100644 index 00000000000..2c3b92ebc58 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerAssignmentTest.java @@ -0,0 +1,167 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.helix.model.IdealState; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.tier.TierFactory; +import org.apache.pinot.common.utils.helix.HelixHelper; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.TierConfig; +import org.apache.pinot.spi.config.tenant.Tenant; +import org.apache.pinot.spi.config.tenant.TenantRole; +import org.apache.pinot.spi.utils.CommonConstants.Helix; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + + +@Test(groups = "stateless") +public class PinotHelixResourceManagerAssignmentTest extends ControllerTest { + private static final int NUM_BROKER_INSTANCES = 3; + private static final int NUM_OFFLINE_SERVER_INSTANCES = 2; + private static final int NUM_OFFLINE_COLD_SERVER_INSTANCES = 2; + private static final int NUM_REALTIME_SERVER_INSTANCES = 2; + private static final int NUM_SERVER_INSTANCES = + NUM_OFFLINE_SERVER_INSTANCES + NUM_REALTIME_SERVER_INSTANCES + NUM_OFFLINE_COLD_SERVER_INSTANCES; + private static final String BROKER_TENANT_NAME = "brokerTenant"; + private static final String SERVER_TENANT_NAME = "serverTenant"; + private static final String SERVER_COLD_TENANT_NAME = "coldServerTenant"; + + private static final String RAW_TABLE_NAME = "testTable"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + + @BeforeClass + public void setUp() + throws Exception { + startZk(); + + Map properties = getDefaultControllerConfiguration(); + properties.put(ControllerConf.CONTROLLER_ENABLE_TIERED_SEGMENT_ASSIGNMENT, true); + properties.put(ControllerConf.CLUSTER_TENANT_ISOLATION_ENABLE, false); + startController(properties); + + addFakeBrokerInstancesToAutoJoinHelixCluster(NUM_BROKER_INSTANCES, false); + addFakeServerInstancesToAutoJoinHelixCluster(NUM_SERVER_INSTANCES, false); + + resetBrokerTags(); + resetServerTags(); + } + + private void untagBrokers() { + for (String brokerInstance : _helixResourceManager.getAllInstancesForBrokerTenant(BROKER_TENANT_NAME)) { + _helixResourceManager.updateInstanceTags(brokerInstance, Helix.UNTAGGED_BROKER_INSTANCE, false); + } + } + + private void resetBrokerTags() { + untagBrokers(); + assertEquals(_helixResourceManager.getOnlineUnTaggedBrokerInstanceList().size(), NUM_BROKER_INSTANCES); + Tenant brokerTenant = new Tenant(TenantRole.BROKER, BROKER_TENANT_NAME, NUM_BROKER_INSTANCES, 0, 0); + _helixResourceManager.createBrokerTenant(brokerTenant); + assertEquals(_helixResourceManager.getOnlineUnTaggedBrokerInstanceList().size(), 0); + } + + private void untagServers() { + for (String serverInstance : _helixResourceManager.getAllInstancesForServerTenant(SERVER_TENANT_NAME)) { + _helixResourceManager.updateInstanceTags(serverInstance, Helix.UNTAGGED_SERVER_INSTANCE, false); + } + + for (String serverInstance : _helixResourceManager.getAllInstancesForServerTenant(SERVER_COLD_TENANT_NAME)) { + _helixResourceManager.updateInstanceTags(serverInstance, Helix.UNTAGGED_SERVER_INSTANCE, false); + } + } + + private void resetServerTags() { + untagServers(); + assertEquals(_helixResourceManager.getOnlineUnTaggedServerInstanceList().size(), NUM_SERVER_INSTANCES); + + // Create default tenant + Tenant serverTenant = + new Tenant(TenantRole.SERVER, SERVER_TENANT_NAME, NUM_SERVER_INSTANCES - NUM_OFFLINE_COLD_SERVER_INSTANCES, + NUM_OFFLINE_SERVER_INSTANCES, NUM_REALTIME_SERVER_INSTANCES); + _helixResourceManager.createServerTenant(serverTenant); + + // Create cold tenant + Tenant coldTenant = + new Tenant(TenantRole.SERVER, SERVER_COLD_TENANT_NAME, NUM_OFFLINE_COLD_SERVER_INSTANCES, + NUM_OFFLINE_COLD_SERVER_INSTANCES, 0); + _helixResourceManager.createServerTenant(coldTenant); + + assertEquals(_helixResourceManager.getOnlineUnTaggedServerInstanceList().size(), 0); + } + + @Test + public void testAssignTargetTier() + throws Exception { + String coldOfflineServerTag = SERVER_COLD_TENANT_NAME + "_OFFLINE"; + TierConfig tierConfig = + new TierConfig("tier1", TierFactory.FIXED_SEGMENT_SELECTOR_TYPE, null, Collections.singletonList("testSegment"), + TierFactory.PINOT_SERVER_STORAGE_TYPE, coldOfflineServerTag, null, null); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setTierConfigList(Collections.singletonList(tierConfig)).setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); + _helixResourceManager.addTable(tableConfig); + + String segmentName = "testSegment"; + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, new SegmentZKMetadata(segmentName)); + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, segmentName), "downloadUrl"); + + List retrievedSegmentsZKMetadata = + _helixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME); + SegmentZKMetadata retrievedSegmentZKMetadata = retrievedSegmentsZKMetadata.get(0); + assertEquals(retrievedSegmentZKMetadata.getSegmentName(), segmentName); + assertEquals(retrievedSegmentZKMetadata.getTier(), "tier1"); + + // Retrieve current assignment of the table and ensure segment's presence there + IdealState idealState = HelixHelper.getTableIdealState(_helixManager, OFFLINE_TABLE_NAME); + assertNotNull(idealState); + Map> currentAssignment = idealState.getRecord().getMapFields(); + assertTrue(currentAssignment.size() == 1 && currentAssignment.get(segmentName).size() == 1); + + // Ensure that the server instance belongs to the cold tenant + String coldServerName = currentAssignment.get(segmentName).keySet().iterator().next(); + InstanceConfig coldServerConfig = HelixHelper.getInstanceConfig(_helixManager, coldServerName); + assertTrue(coldServerConfig.containsTag(coldOfflineServerTag)); + } + + @AfterClass + public void tearDown() { + stopFakeInstances(); + stopController(); + stopZk(); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java index 3e66b5c6aca..5a96a4eea97 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java @@ -46,6 +46,8 @@ import org.apache.pinot.common.lineage.SegmentLineageAccessHelper; import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.restlet.resources.EndReplaceSegmentsRequest; +import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.common.utils.config.InstanceUtils; import org.apache.pinot.common.utils.config.TagNameUtils; @@ -63,6 +65,7 @@ import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TagOverrideConfig; import org.apache.pinot.spi.config.table.TenantConfig; +import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.ingestion.BatchIngestionConfig; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; import org.apache.pinot.spi.config.tenant.Tenant; @@ -239,6 +242,7 @@ public void testUpdateBrokerResource() TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(offlineTableConfig.getTableName()); _helixResourceManager.addTable(offlineTableConfig); checkBrokerResource(taggedBrokers); @@ -296,6 +300,7 @@ public void testRebuildBrokerResourceFromHelixTags() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); // Untag all Brokers assigned to broker tenant @@ -334,6 +339,7 @@ public void testGetLiveBrokers() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); waitForTableOnlineInBrokerResourceEV(OFFLINE_TABLE_NAME); @@ -362,6 +368,7 @@ public void testGetLiveBrokers() new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME) .setStreamConfigs(FakeStreamConfigUtils.getDefaultHighLevelStreamConfigs().getStreamConfigsMap()).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); waitForTableOnlineInBrokerResourceEV(REALTIME_TABLE_NAME); @@ -533,6 +540,26 @@ public void testValidateTenantConfig() { // Valid tenant config without tagOverrideConfig _helixResourceManager.validateTableTenantConfig(offlineTableConfig); + // Invalid serverTag in tierConfigs + TierConfig tierConfig = new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, "Unknown_OFFLINE", null, null); + offlineTableConfig.setTierConfigsList(Collections.singletonList(tierConfig)); + assertThrows(InvalidTableConfigException.class, + () -> _helixResourceManager.validateTableTenantConfig(offlineTableConfig)); + + // A null serverTag has no instances associated with it, so it's invalid. + tierConfig = new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, null, null, null); + offlineTableConfig.setTierConfigsList(Collections.singletonList(tierConfig)); + assertThrows(InvalidTableConfigException.class, + () -> _helixResourceManager.validateTableTenantConfig(offlineTableConfig)); + + // Valid serverTag in tierConfigs + tierConfig = new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, SERVER_TENANT_NAME + "_OFFLINE", null, null); + offlineTableConfig.setTierConfigsList(Collections.singletonList(tierConfig)); + _helixResourceManager.validateTableTenantConfig(offlineTableConfig); + TableConfig realtimeTableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); @@ -652,7 +679,7 @@ public void testLeadControllerResource() { if (stateMap.size() != 1) { return false; } - String instanceId = LeadControllerUtils.generateParticipantInstanceId(LOCAL_HOST, _controllerPort); + String instanceId = LeadControllerUtils.generateParticipantInstanceId(LOCAL_HOST, getControllerPort()); return MasterSlaveSMD.States.MASTER.name().equals(stateMap.get(instanceId)); } return true; @@ -724,6 +751,81 @@ public void testLeadControllerAssignment() { } } + @Test + public void testUpdateTargetTier() + throws Exception { + TierConfig tierConfig = + new TierConfig("tier1", TierFactory.FIXED_SEGMENT_SELECTOR_TYPE, null, Collections.singletonList("testSegment"), + TierFactory.PINOT_SERVER_STORAGE_TYPE, SERVER_TENANT_NAME + "_OFFLINE", null, null); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setTierConfigList(Collections.singletonList(tierConfig)).setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); + _helixResourceManager.addTable(tableConfig); + + String segmentName = "testSegment"; + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segmentName); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, segmentZKMetadata); + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "testSegment"), "downloadUrl"); + assertNull(segmentZKMetadata.getTier()); + + // Move on to new tier + _helixResourceManager.updateTargetTier("j1", tableConfig.getTableName(), tableConfig); + List retrievedSegmentsZKMetadata = + _helixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME); + SegmentZKMetadata retrievedSegmentZKMetadata = retrievedSegmentsZKMetadata.get(0); + assertEquals(retrievedSegmentZKMetadata.getTier(), "tier1"); + + // Move back to default tier + tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setServerTenant(SERVER_TENANT_NAME).build(); + _helixResourceManager.updateTableConfig(tableConfig); + _helixResourceManager.updateTargetTier("j2", tableConfig.getTableName(), tableConfig); + retrievedSegmentsZKMetadata = _helixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME); + retrievedSegmentZKMetadata = retrievedSegmentsZKMetadata.get(0); + assertNull(retrievedSegmentZKMetadata.getTier()); + } + + /** + * Tests the code path where a subset of merged segments (from the original segmentsTo list) + * is passed to the endReplace API. + * @throws Exception + */ + @Test + public void testSegmentReplacementWithCustomToSegments() throws Exception { + // Create the table + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); + _helixResourceManager.addTable(tableConfig); + + List segmentsFrom = Collections.emptyList(); + List segmentsTo = Arrays.asList("s20", "s21"); + String lineageEntryId = + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom, segmentsTo, false, null); + assertThrows(RuntimeException.class, + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId, + new EndReplaceSegmentsRequest(Arrays.asList("s9", "s6"), null))); + // Try after new segments added to the table + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s20"), "downloadUrl"); + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s21"), "downloadUrl"); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId, + new EndReplaceSegmentsRequest(Arrays.asList("s21"), null)); + SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId)); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId).getSegmentsFrom(), segmentsFrom); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId).getState(), LineageEntryState.COMPLETED); + // Delete the table + _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); + segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertNull(segmentLineage); + } + @Test public void testSegmentReplacementRegular() throws Exception { @@ -731,6 +833,7 @@ public void testSegmentReplacementRegular() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); // Add 5 segments @@ -744,7 +847,7 @@ public void testSegmentReplacementRegular() List segmentsFrom1 = Collections.emptyList(); List segmentsTo1 = Arrays.asList("s5", "s6"); String lineageEntryId1 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false, null); SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId1)); assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getSegmentsFrom(), segmentsFrom1); @@ -752,35 +855,39 @@ public void testSegmentReplacementRegular() assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getState(), LineageEntryState.IN_PROGRESS); // Check invalid segmentsTo - assertThrows(IllegalArgumentException.class, + assertThrows(IllegalStateException.class, () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, Arrays.asList("s1", "s2"), - Arrays.asList("s3", "s4"), false)); - assertThrows(IllegalArgumentException.class, + Arrays.asList("s3", "s4"), false, null)); + assertThrows(IllegalStateException.class, () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, Arrays.asList("s1", "s2"), - Collections.singletonList("s2"), false)); + Collections.singletonList("s2"), false, null)); // Check invalid segmentsFrom - assertThrows(IllegalArgumentException.class, + assertThrows(IllegalStateException.class, () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, Arrays.asList("s1", "s6"), - Collections.singletonList("s7"), false)); + Collections.singletonList("s7"), false, null)); // Invalid table assertThrows(RuntimeException.class, - () -> _helixResourceManager.endReplaceSegments(REALTIME_TABLE_NAME, lineageEntryId1)); + () -> _helixResourceManager.endReplaceSegments(REALTIME_TABLE_NAME, lineageEntryId1, null)); + // Invalid table + assertThrows(RuntimeException.class, + () -> _helixResourceManager.endReplaceSegments(REALTIME_TABLE_NAME, lineageEntryId1, null)); // Invalid lineage entry id - assertThrows(RuntimeException.class, () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, "invalid")); + assertThrows(RuntimeException.class, () -> _helixResourceManager. + endReplaceSegments(OFFLINE_TABLE_NAME, "invalid", null)); // New segments not available in the table assertThrows(RuntimeException.class, - () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1)); + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, null)); // Try after new segments added to the table _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s5"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s6"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId1)); assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getSegmentsFrom(), segmentsFrom1); @@ -791,7 +898,7 @@ public void testSegmentReplacementRegular() List segmentsFrom2 = Arrays.asList("s1", "s2"); List segmentsTo2 = Arrays.asList("merged_t1_0", "merged_t1_1"); String lineageEntryId2 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2); assertEquals(segmentLineage.getLineageEntry(lineageEntryId2).getSegmentsFrom(), segmentsFrom2); @@ -807,10 +914,10 @@ public void testSegmentReplacementRegular() // Revert the entry with partial data uploaded without forceRevert assertThrows(RuntimeException.class, - () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, false)); + () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, false, null)); // Revert the entry with partial data uploaded with forceRevert - _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, true); + _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId2).getState(), LineageEntryState.REVERTED); @@ -823,7 +930,7 @@ public void testSegmentReplacementRegular() List segmentsFrom3 = Arrays.asList("s1", "s2"); List segmentsTo3 = Arrays.asList("merged_t2_0", "merged_t2_1"); String lineageEntryId3 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3); assertEquals(segmentLineage.getLineageEntry(lineageEntryId3).getSegmentsFrom(), segmentsFrom3); @@ -838,11 +945,11 @@ public void testSegmentReplacementRegular() List segmentsFrom4 = Arrays.asList("s1", "s2"); List segmentsTo4 = Arrays.asList("merged_t3_0", "merged_t3_1"); assertThrows(RuntimeException.class, - () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, false)); + () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, false, null)); // Test force clean up case String lineageEntryId4 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4); @@ -865,7 +972,7 @@ public void testSegmentReplacementRegular() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "merged_t3_1"), "downloadUrl"); // Finish the replacement - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4); @@ -878,7 +985,7 @@ public void testSegmentReplacementRegular() List segmentsFrom5 = Collections.emptyList(); List segmentsTo5 = Arrays.asList("s7", "s8"); String lineageEntryId5 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4, lineageEntryId5); @@ -891,7 +998,7 @@ public void testSegmentReplacementRegular() List segmentsFrom6 = Collections.emptyList(); List segmentsTo6 = Arrays.asList("s7", "s8"); String lineageEntryId6 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4, lineageEntryId6); @@ -908,7 +1015,7 @@ public void testSegmentReplacementRegular() List segmentsFrom7 = Collections.emptyList(); List segmentsTo7 = Arrays.asList("s9", "s10"); String lineageEntryId7 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 6); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getState(), LineageEntryState.IN_PROGRESS); @@ -919,7 +1026,7 @@ public void testSegmentReplacementRegular() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s9"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s10"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId7); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId7, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 6); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getState(), LineageEntryState.IN_PROGRESS); @@ -932,7 +1039,7 @@ public void testSegmentReplacementRegular() List segmentsFrom8 = Arrays.asList("s9", "s10"); List segmentsTo8 = Arrays.asList("s11", "s12"); String lineageEntryId8 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 7); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getSegmentsFrom(), segmentsFrom8); @@ -948,7 +1055,7 @@ public void testSegmentReplacementRegular() List segmentsFrom9 = Arrays.asList("s0", "s9"); List segmentsTo9 = Arrays.asList("s13", "s14"); String lineageEntryId9 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 8); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getState(), LineageEntryState.REVERTED); @@ -959,7 +1066,7 @@ public void testSegmentReplacementRegular() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s13"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s14"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 8); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsFrom(), segmentsFrom9); @@ -967,9 +1074,23 @@ public void testSegmentReplacementRegular() assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getState(), LineageEntryState.COMPLETED); // Check endReplaceSegments is idempotent - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9, null); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getState(), LineageEntryState.COMPLETED); + // Test empty segmentsTo. This is a special case where we are atomically removing segments. + List segmentsFrom10 = Arrays.asList("s13", "s14"); + List segmentsTo10 = Collections.emptyList(); + String lineageEntryId10 = + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom10, segmentsTo10, true, null); + segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertEquals(segmentLineage.getLineageEntryIds().size(), 9); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getState(), LineageEntryState.IN_PROGRESS); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId10, null); + segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsFrom(), segmentsFrom10); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsTo(), segmentsTo10); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getState(), LineageEntryState.COMPLETED); + // Delete the table _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); @@ -985,6 +1106,7 @@ public void testSegmentReplacementForRefresh() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).setIngestionConfig(ingestionConfig).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); // Add 3 segments @@ -999,7 +1121,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom1 = Arrays.asList("s0", "s1", "s2"); List segmentsTo1 = Arrays.asList("s3", "s4", "s5"); String lineageEntryId1 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false, null); SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId1)); assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getSegmentsFrom(), segmentsFrom1); @@ -1017,7 +1139,7 @@ public void testSegmentReplacementForRefresh() assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s0", "s1", "s2"); // Call end segment replacements - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, null); assertEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false).size(), 6); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s3", "s4", "s5"); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); @@ -1031,7 +1153,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom2 = Arrays.asList("s3", "s4", "s5"); List segmentsTo2 = Arrays.asList("s6", "s7", "s8"); String lineageEntryId2 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 2); assertEquals(segmentLineage.getLineageEntry(lineageEntryId2).getSegmentsFrom(), segmentsFrom2); @@ -1042,7 +1164,7 @@ public void testSegmentReplacementForRefresh() // Reverting the first entry should fail assertThrows(RuntimeException.class, - () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, false)); + () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, false, null)); // Add partial segments to indicate incomplete protocol _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, @@ -1058,7 +1180,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom3 = Arrays.asList("s3", "s4", "s5"); List segmentsTo3 = Arrays.asList("s9", "s10", "s11"); String lineageEntryId3 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 3); @@ -1074,7 +1196,7 @@ public void testSegmentReplacementForRefresh() // Invoking end segment replacement for the reverted entry should fail assertThrows(RuntimeException.class, - () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2)); + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, null)); // Add new segments for (int i = 9; i < 12; i++) { @@ -1083,7 +1205,7 @@ public void testSegmentReplacementForRefresh() } // Call end segment replacements - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3, null); assertEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false).size(), 6); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s9", "s10", "s11"); @@ -1093,7 +1215,7 @@ public void testSegmentReplacementForRefresh() // Call revert segment replacements (s3, s4, s5) <- (s9, s10, s11) to check if the revertReplaceSegments correctly // deleted (s9, s10, s11). - _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3, false); + _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3, false, null); assertEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false).size(), 3); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s3", "s4", "s5"); @@ -1109,7 +1231,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom4 = Arrays.asList("s3", "s4", "s5"); List segmentsTo4 = Arrays.asList("s12", "s13", "s14"); String lineageEntryId4 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true, null); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false), "s3", "s4", "s5"); // Upload the new segments (s12, s13, s14) @@ -1119,7 +1241,7 @@ public void testSegmentReplacementForRefresh() } // Call endReplaceSegments to start to use (s12, s13, s14) - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4, null); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false), "s3", "s4", "s5", "s12", "s13", "s14"); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s12", "s13", "s14"); @@ -1129,7 +1251,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom5 = Collections.emptyList(); List segmentsTo5 = Arrays.asList("s15", "s16"); String lineageEntryId5 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId5).getSegmentsFrom(), segmentsFrom5); assertEquals(segmentLineage.getLineageEntry(lineageEntryId5).getSegmentsTo(), segmentsTo5); @@ -1144,7 +1266,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom6 = Collections.emptyList(); List segmentsTo6 = Arrays.asList("s17", "s18"); String lineageEntryId6 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId5).getState(), LineageEntryState.IN_PROGRESS); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getState(), LineageEntryState.IN_PROGRESS); @@ -1154,7 +1276,7 @@ public void testSegmentReplacementForRefresh() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s17"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s18"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId6); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId6, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getSegmentsFrom(), segmentsFrom6); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getSegmentsTo(), segmentsTo6); @@ -1165,7 +1287,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom7 = Arrays.asList("s17", "s18"); List segmentsTo7 = Arrays.asList("s19", "s20"); String lineageEntryId7 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId7).getSegmentsFrom(), segmentsFrom7); assertEquals(segmentLineage.getLineageEntry(lineageEntryId7).getSegmentsTo(), segmentsTo7); @@ -1180,7 +1302,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom8 = Arrays.asList("s14", "s17"); List segmentsTo8 = Arrays.asList("s21", "s22"); String lineageEntryId8 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId7).getState(), LineageEntryState.REVERTED); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getState(), LineageEntryState.IN_PROGRESS); @@ -1191,7 +1313,7 @@ public void testSegmentReplacementForRefresh() _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s22"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId8); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId8, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getSegmentsFrom(), segmentsFrom8); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getSegmentsTo(), segmentsTo8); @@ -1202,7 +1324,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom9 = Arrays.asList("s21", "s22"); List segmentsTo9 = Arrays.asList("s23", "s24"); String lineageEntryId9 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsFrom(), segmentsFrom9); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsTo(), segmentsTo9); @@ -1219,7 +1341,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom10 = Arrays.asList("s21", "s22"); List segmentsTo10 = Arrays.asList("s24", "s25"); String lineageEntryId10 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom10, segmentsTo10, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom10, segmentsTo10, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsFrom(), segmentsFrom9); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsTo(), Collections.singletonList("s23")); @@ -1233,7 +1355,7 @@ public void testSegmentReplacementForRefresh() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s24"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s25"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId10); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId10, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsFrom(), segmentsFrom10); assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsTo(), segmentsTo10); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java index 4715271fc55..4335d80b147 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java @@ -350,9 +350,9 @@ public void testPoolBased() { // Assign to 2 replica-groups so that each replica-group is assigned to one pool int numReplicaGroups = numPools; InstanceReplicaGroupPartitionConfig replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false, null); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))).build(); InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig); @@ -405,7 +405,7 @@ public void testPoolBased() { // Select all 3 pools in pool selection tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Math.abs("myTable_OFFLINE".hashCode()) % 3 = 2 @@ -427,7 +427,7 @@ public void testPoolBased() { // Select pool 0 and 1 in pool selection tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, Arrays.asList(0, 1)); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 @@ -448,8 +448,8 @@ public void testPoolBased() { // Assign instances from 2 pools to 3 replica-groups numReplicaGroups = numPools; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 @@ -477,9 +477,9 @@ public void testPoolBased() { // Reset the number of replica groups to 2 and pools to 2. numReplicaGroups = 2; numPools = 2; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true); + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Reset the instance configs to have only two pools. instanceConfigs.clear(); @@ -528,7 +528,7 @@ public void testPoolBased() { // Select pool 0 and 1 in pool selection tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, Arrays.asList(0, 1)); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Get the latest existingInstancePartitions from last computation. @@ -554,8 +554,8 @@ public void testPoolBased() { // Assign instances from 2 pools to 3 replica-groups numReplicaGroups = 3; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Get the latest existingInstancePartitions from last computation. @@ -633,8 +633,8 @@ public void testPoolBased() { // Reduce number of replica groups from 3 to 2. numReplicaGroups = 2; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); // Get the latest existingInstancePartitions from last computation. @@ -760,8 +760,8 @@ public void testIllegalConfig() { InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, false, 0, null); InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig = - new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // No instance with correct tag @@ -791,7 +791,7 @@ public void testIllegalConfig() { // Enable pool tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // No instance has correct pool configured @@ -825,7 +825,7 @@ public void testIllegalConfig() { assertEquals(instancePartitions.getInstances(0, 0), expectedInstances); tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 3, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Ask for too many pools @@ -837,7 +837,7 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, Arrays.asList(0, 2)); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Ask for pool that does not exist @@ -849,8 +849,9 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, null); - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(false, 6, 0, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(false, 6, 0, 0, 0, 0, false, null + ); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Ask for too many instances @@ -862,8 +863,9 @@ public void testIllegalConfig() { } // Enable replica-group - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 0, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 0, 0, 0, 0, false, null + ); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Number of replica-groups must be positive @@ -874,8 +876,8 @@ public void testIllegalConfig() { assertEquals(e.getMessage(), "Number of replica-groups must be positive"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 11, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 11, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Ask for too many replica-groups @@ -887,8 +889,8 @@ public void testIllegalConfig() { "Not enough qualified instances from pool: 0, cannot select 6 replica-groups from 5 instances"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 3, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 3, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Ask for too many instances @@ -899,8 +901,8 @@ public void testIllegalConfig() { assertEquals(e.getMessage(), "Not enough qualified instances from pool: 0 (5 in the pool, asked for 6)"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 3, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 3, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Ask for too many instances per partition @@ -912,8 +914,8 @@ public void testIllegalConfig() { "Number of instances per partition: 3 must be smaller or equal to number of instances per replica-group: 2"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); // Math.abs("myTable_OFFLINE".hashCode()) % 5 = 3 @@ -948,11 +950,12 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); InstanceReplicaGroupPartitionConfig replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, + 0, false, null); try { tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, "ILLEGAL_SELECTOR"))).build(); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), @@ -977,9 +980,10 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, + 0, 0, false, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1009,9 +1013,10 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, + numInstancesPerReplicaGroup, 0, 0, false, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1049,9 +1054,10 @@ public void testIllegalConfig() { tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, + numInstancesPerReplicaGroup, 0, 0, false, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1087,9 +1093,9 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 7 instances InstanceReplicaGroupPartitionConfig replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig); @@ -1159,9 +1165,9 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 7 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1237,12 +1243,12 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 7 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); String partitionColumnName = "partition"; SegmentPartitionConfig segmentPartitionConfig = new SegmentPartitionConfig( Collections.singletonMap(partitionColumnName, new ColumnPartitionConfig("Modulo", numPartitionsSegment, null))); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(partitionColumnName, numInstancesPerReplicaGroup)) @@ -1310,13 +1316,13 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 3 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate for testing InstanceConstraintConfig instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); @@ -1367,12 +1373,12 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 3 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); // Do not rotate for testing instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); @@ -1433,13 +1439,13 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 5 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); @@ -1499,13 +1505,13 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 5 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); @@ -1571,12 +1577,12 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 1 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate for testing instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); @@ -1621,13 +1627,13 @@ public void testPoolBasedFDAware() { // Assign to 6 replica-groups so that each replica-group is assigned 2 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); @@ -1685,13 +1691,13 @@ public void testPoolBasedFDAware() { // Assign to 6 replica-groups so that each replica-group is assigned 2 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) .build(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java index 981472feff0..5706d402abf 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java @@ -18,13 +18,17 @@ */ package org.apache.pinot.controller.helix.core.minion; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.helix.task.JobConfig; import org.apache.helix.task.JobContext; @@ -35,6 +39,8 @@ import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.util.CompletionServiceHelper; import org.apache.pinot.spi.utils.JsonUtils; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.testng.annotations.Test; import static org.mockito.ArgumentMatchers.any; @@ -42,6 +48,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -199,6 +206,72 @@ public void testGetSubtaskProgressPending() assertEquals(taskProgress, "No worker has run this subtask"); } + @Test + public void testGetSubtaskWithGivenStateProgressNoWorker() + throws JsonProcessingException { + CompletionServiceHelper httpHelper = mock(CompletionServiceHelper.class); + PinotHelixTaskResourceManager mgr = + new PinotHelixTaskResourceManager(mock(PinotHelixResourceManager.class), mock(TaskDriver.class)); + // No worker to run subtasks. + Map selectedMinionWorkerEndpoints = new HashMap<>(); + Map progress = + mgr.getSubtaskOnWorkerProgress("IN_PROGRESS", httpHelper, + selectedMinionWorkerEndpoints, Collections.emptyMap(), 1000); + assertTrue(progress.isEmpty()); + verify(httpHelper, Mockito.never()).doMultiGetRequest(any(), any(), anyBoolean(), any(), anyInt()); + } + + @Test + public void testGetSubtaskWithGivenStateProgress() + throws IOException { + CompletionServiceHelper httpHelper = mock(CompletionServiceHelper.class); + CompletionServiceHelper.CompletionServiceResponse httpResp = + new CompletionServiceHelper.CompletionServiceResponse(); + String taskIdPrefix = "Task_SegmentGenerationAndPushTask_someone"; + String workerIdPrefix = "worker"; + String[] subtaskIds = new String[6]; + String[] workerIds = new String[3]; + Map selectedMinionWorkerEndpoints = new HashMap<>(); + for (int i = 0; i < 3; i++) { + workerIds[i] = workerIdPrefix + i; + String workerEndpoint = "http://" + workerIds[i] + ":9000"; + selectedMinionWorkerEndpoints.put(workerIds[i], workerEndpoint); + + subtaskIds[2 * i] = taskIdPrefix + "_" + (2 * i); + subtaskIds[2 * i + 1] = taskIdPrefix + "_" + (2 * i + 1); + // Notice that for testing purpose, we map subtask names to empty strings. In reality, subtask names will be + // mapped to jsonized org.apache.pinot.minion.event.MinionEventObserver + httpResp._httpResponses.put( + String.format("%s/tasks/subtask/state/progress?subTaskState=IN_PROGRESS", workerEndpoint), + JsonUtils.objectToString(ImmutableMap.of(subtaskIds[2 * i], "", subtaskIds[2 * i + 1], ""))); + } + httpResp._failedResponseCount = 1; + ArgumentCaptor> workerEndpointCaptor = ArgumentCaptor.forClass(List.class); + when(httpHelper.doMultiGetRequest(workerEndpointCaptor.capture(), any(), anyBoolean(), any(), anyInt())) + .thenReturn(httpResp); + + PinotHelixTaskResourceManager mgr = + new PinotHelixTaskResourceManager(mock(PinotHelixResourceManager.class), mock(TaskDriver.class)); + + Map progress = + mgr.getSubtaskOnWorkerProgress("IN_PROGRESS", httpHelper, selectedMinionWorkerEndpoints, + Collections.emptyMap(), 1000); + List value = workerEndpointCaptor.getValue(); + Set expectedWorkerUrls = selectedMinionWorkerEndpoints.values().stream() + .map(workerEndpoint + -> String.format("%s/tasks/subtask/state/progress?subTaskState=IN_PROGRESS", workerEndpoint)) + .collect(Collectors.toSet()); + assertEquals(new HashSet<>(value), expectedWorkerUrls); + assertEquals(progress.size(), 3); + for (int i = 0; i < 3; i++) { + Object responseFromMinionWorker = progress.get(workerIds[i]); + Map subtaskProgressMap = (Map) responseFromMinionWorker; + assertEquals(subtaskProgressMap.size(), 2); + assertTrue(subtaskProgressMap.containsKey(subtaskIds[2 * i])); + assertTrue(subtaskProgressMap.containsKey(subtaskIds[2 * i + 1])); + } + } + @Test public void testGetTableTaskCount() { String taskType = "TestTask"; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java index 6b6e0406ef9..85e49766399 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java @@ -25,6 +25,7 @@ import java.util.function.Predicate; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.util.TableSizeReader; import org.apache.pinot.core.common.MinionConstants; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableTaskConfig; @@ -95,6 +96,7 @@ public void testSkipLateCronSchedule() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTaskConfig( new TableTaskConfig( ImmutableMap.of("SegmentGenerationAndPushTask", ImmutableMap.of("schedule", "0 * * ? * * *")))).build(); + waitForEVToDisappear(tableConfig.getTableName()); addTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), jgn -> jgn.size() == 1 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE), @@ -128,6 +130,7 @@ public void testPinotTaskManagerSchedulerWithUpdate() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTaskConfig( new TableTaskConfig( ImmutableMap.of("SegmentGenerationAndPushTask", ImmutableMap.of("schedule", "0 */10 * ? * * *")))).build(); + waitForEVToDisappear(tableConfig.getTableName()); addTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), jgn -> jgn.size() == 1 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE), @@ -193,6 +196,7 @@ public void testPinotTaskManagerSchedulerWithRestart() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTaskConfig( new TableTaskConfig( ImmutableMap.of("SegmentGenerationAndPushTask", ImmutableMap.of("schedule", "0 */10 * ? * * *")))).build(); + waitForEVToDisappear(tableConfig.getTableName()); addTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), jgn -> jgn.size() == 1 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE), @@ -206,7 +210,7 @@ public void testPinotTaskManagerSchedulerWithRestart() TestUtils.waitForCondition((aVoid) -> { try { long tableSize = getTableSize(OFFLINE_TABLE_NAME); - return tableSize >= 0; + return tableSize == TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; } catch (Exception e) { return false; } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java index eee1f2a8a80..2223c9e3414 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Random; import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -89,7 +90,12 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; public class PinotLLCRealtimeSegmentManagerTest { @@ -948,6 +954,7 @@ public void testUploadToSegmentStore() ControllerConf controllerConfig = new ControllerConf(); controllerConfig.setProperty( ControllerConf.ControllerPeriodicTasksConf.ENABLE_DEEP_STORE_RETRY_UPLOAD_LLC_SEGMENT, true); + controllerConfig.setDataDir(TEMP_DIR.toString()); FakePinotLLCRealtimeSegmentManager segmentManager = new FakePinotLLCRealtimeSegmentManager(pinotHelixResourceManager, controllerConfig); Assert.assertTrue(segmentManager.isDeepStoreLLCSegmentUploadRetryEnabled()); @@ -990,11 +997,17 @@ public void testUploadToSegmentStore() "segments", REALTIME_TABLE_NAME, segmentsZKMetadata.get(0).getSegmentName(), - "upload"); - String segmentDownloadUrl0 = String.format("segmentDownloadUr_%s", segmentsZKMetadata.get(0) - .getSegmentName()); + "upload") + "?uploadTimeoutMs=-1"; + // tempSegmentFileLocation is the location where the segment uploader will upload the segment. This usually ends + // with a random UUID + File tempSegmentFileLocation = new File(TEMP_DIR, segmentsZKMetadata.get(0).getSegmentName() + UUID.randomUUID()); + FileUtils.write(tempSegmentFileLocation, "test"); + // After the deep-store retry task gets the segment location returned by Pinot server, it will move the segment to + // its final location. This is the expected segment location. + String expectedSegmentLocation = segmentManager.createSegmentPath(RAW_TABLE_NAME, + segmentsZKMetadata.get(0).getSegmentName()).toString(); when(segmentManager._mockedFileUploadDownloadClient - .uploadToSegmentStore(serverUploadRequestUrl0)).thenReturn(segmentDownloadUrl0); + .uploadToSegmentStore(serverUploadRequestUrl0)).thenReturn(tempSegmentFileLocation.getPath()); // Change 2nd segment status to be DONE, but with default peer download url. // Verify later the download url isn't fixed after upload failure. @@ -1013,7 +1026,7 @@ public void testUploadToSegmentStore() "segments", REALTIME_TABLE_NAME, segmentsZKMetadata.get(1).getSegmentName(), - "upload"); + "upload") + "?uploadTimeoutMs=-1"; when(segmentManager._mockedFileUploadDownloadClient .uploadToSegmentStore(serverUploadRequestUrl1)) .thenThrow(new HttpErrorStatusException( @@ -1041,11 +1054,15 @@ public void testUploadToSegmentStore() when(pinotHelixResourceManager.getTableConfig(REALTIME_TABLE_NAME)) .thenReturn(segmentManager._tableConfig); + // Verify the result segmentManager.uploadToDeepStoreIfMissing(segmentManager._tableConfig, segmentsZKMetadata); assertEquals( segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(0), null).getDownloadUrl(), - segmentDownloadUrl0); + expectedSegmentLocation); + assertFalse(tempSegmentFileLocation.exists(), + "Deep-store retry task should move the file from temp location to permanent location"); + assertEquals( segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(1), null).getDownloadUrl(), CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java index 2ada570ac87..4d85223b411 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java @@ -659,6 +659,81 @@ public void testWinnerOnTimeLimit() Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD); } + @Test + public void testWinnerOnForceCommit() + throws Exception { + SegmentCompletionProtocol.Response response; + Request.Params params; + // S1 comes to force commit + _segmentCompletionMgr._seconds = 10L; + params = new Request.Params().withInstanceId(S_1).withStreamPartitionMsgOffset(_s1Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // Still need to wait since we haven't hit time limit or heard from all servers + Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD); + + // S2 comes with a higher offset 1 second later + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // Still need to wait since we haven't hit time limit or heard from all servers + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.HOLD); + + // S3 comes with a lower offset than S2 3 seconds later + _segmentCompletionMgr._seconds += 3; + params = new Request.Params().withInstanceId(S_3).withStreamPartitionMsgOffset(_s3Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // We've met winner criteria, but it should be S_2 with the highest offset. S_3 should catch up. + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.CATCH_UP); + + // S1 comes back at the same offset + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_1).withStreamPartitionMsgOffset(_s1Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // We've met winner criteria, but it should be S2 with the highest offset. S1 should catch up. + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.CATCH_UP); + + // S2 comes back at the same offset + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // S2 is told to commit + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.COMMIT); + + // S2 comes back to commit the segment + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr); + response = _segmentCompletionMgr.segmentCommitStart(params); + Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE); + _segmentCompletionMgr._seconds += 5; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withSegmentLocation("location"); + response = _segmentCompletionMgr + .segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params)); + Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS); + + // S3 comes back at the latest offset + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_3).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // S3 is told to keep since it caught up + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.KEEP); + + // S1 comes back with a higher offset than before, but still not caught up + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_1).withStreamPartitionMsgOffset(_s3Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // S1 is told to discard since S2 already uploaded + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.DISCARD); + } + @Test public void testWinnerOnRowLimit() throws Exception { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java index 4c006967d18..8d6da2681a8 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java @@ -195,8 +195,8 @@ public void testRebalance() InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant(null), false, 0, null); InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, NUM_REPLICAS, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + new InstanceReplicaGroupPartitionConfig(true, 0, NUM_REPLICAS, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); _helixResourceManager.updateTableConfig(tableConfig); @@ -246,13 +246,15 @@ public void testRebalance() tableConfig.setInstanceAssignmentConfigMap(null); _helixResourceManager.updateTableConfig(tableConfig); - // Without instances reassignment, the rebalance should return status NO_OP as instance partitions are already - // generated + // Without instances reassignment, the rebalance should return status NO_OP, and the existing instance partitions + // should be used rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + assertEquals(rebalanceResult.getInstanceAssignment(), instanceAssignment); + assertEquals(rebalanceResult.getSegmentAssignment(), newSegmentAssignment); - // With instances reassignment, the instance partitions should be removed, and the default instance partitions - // should be used for segment assignment + // With instances reassignment, the rebalance should return status DONE, the existing instance partitions should be + // removed, and the default instance partitions should be used rebalanceConfig = new BaseConfiguration(); rebalanceConfig.addProperty(RebalanceConfigConstants.REASSIGN_INSTANCES, true); rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig); @@ -403,6 +405,131 @@ public void testRebalanceWithTiers() assertTrue(instance.startsWith(expectedPrefix)); } } + _helixResourceManager.deleteOfflineTable(TIERED_TABLE_NAME); + } + + @Test + public void testRebalanceWithTiersAndInstanceAssignments() + throws Exception { + int numServers = 3; + for (int i = 0; i < numServers; i++) { + addFakeServerInstanceToAutoJoinHelixCluster( + "replicaAssignment" + NO_TIER_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + i, false); + } + _helixResourceManager.createServerTenant( + new Tenant(TenantRole.SERVER, "replicaAssignment" + NO_TIER_NAME, numServers, numServers, 0)); + + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(TIERED_TABLE_NAME).setNumReplicas(NUM_REPLICAS) + .setServerTenant("replicaAssignment" + NO_TIER_NAME).build(); + // Create the table + _helixResourceManager.addTable(tableConfig); + + // Add the segments + int numSegments = 10; + long nowInDays = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()); + + for (int i = 0; i < numSegments; i++) { + _helixResourceManager.addNewSegment(OFFLINE_TIERED_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithEndTimeInfo(TIERED_TABLE_NAME, SEGMENT_NAME_PREFIX + i, + nowInDays), null); + } + Map> oldSegmentAssignment = + _helixResourceManager.getTableIdealState(OFFLINE_TIERED_TABLE_NAME).getRecord().getMapFields(); + + TableRebalancer tableRebalancer = new TableRebalancer(_helixManager); + RebalanceResult rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + // Segment assignment should not change + assertEquals(rebalanceResult.getSegmentAssignment(), oldSegmentAssignment); + + // add 6 nodes tierA + for (int i = 0; i < 6; i++) { + addFakeServerInstanceToAutoJoinHelixCluster( + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + i, false); + } + _helixResourceManager.createServerTenant(new Tenant(TenantRole.SERVER, "replicaAssignment" + TIER_A_NAME, 6, 6, 0)); + // rebalance is NOOP and no change in assignment caused by new instances + rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + // Segment assignment should not change + assertEquals(rebalanceResult.getSegmentAssignment(), oldSegmentAssignment); + + // add tier config + tableConfig.setTierConfigsList(Lists.newArrayList( + new TierConfig(TIER_A_NAME, TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "0d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, "replicaAssignment" + TIER_A_NAME + "_OFFLINE", null, null))); + _helixResourceManager.updateTableConfig(tableConfig); + + // rebalance should change assignment + rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); + + // check that segments have moved to tier a + Map> tierSegmentAssignment = rebalanceResult.getSegmentAssignment(); + for (Map.Entry> entry : tierSegmentAssignment.entrySet()) { + Map instanceStateMap = entry.getValue(); + for (String instance : instanceStateMap.keySet()) { + assertTrue(instance.startsWith("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX)); + } + } + + // Test rebalance with tier instance assignment + InstanceTagPoolConfig tagPoolConfig = + new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant("replicaAssignment" + TIER_A_NAME), false, 0, + null); + InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, NUM_REPLICAS, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(TIER_A_NAME, + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + _helixResourceManager.updateTableConfig(tableConfig); + + rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); + assertTrue(rebalanceResult.getTierInstanceAssignment().containsKey(TIER_A_NAME)); + + InstancePartitions instancePartitions = rebalanceResult.getTierInstanceAssignment().get(TIER_A_NAME); + + // Math.abs("testTable_OFFLINE".hashCode()) % 6 = 2 + // [i2, i3, i4, i5, i0, i1] + // r0 r1 r2 r0 r1 r2 + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 2, + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 5)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 3, + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 0)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 4, + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 1)); + + // The assignment are based on replica-group 0 and mirrored to all the replica-groups, so server of index 0, 1, 5 + // should have the same segments assigned, and server of index 2, 3, 4 should have the same segments assigned, each + // with 5 segments + Map> newSegmentAssignment = rebalanceResult.getSegmentAssignment(); + int numSegmentsOnServer0 = 0; + for (int i = 0; i < numSegments; i++) { + String segmentName = SEGMENT_NAME_PREFIX + i; + Map instanceStateMap = newSegmentAssignment.get(segmentName); + assertEquals(instanceStateMap.size(), NUM_REPLICAS); + if (instanceStateMap.containsKey("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 0)) { + numSegmentsOnServer0++; + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 0), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 1), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 5), + ONLINE); + } else { + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 2), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 3), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 4), + ONLINE); + } + } + assertEquals(numSegmentsOnServer0, numSegments / 2); _helixResourceManager.deleteOfflineTable(TIERED_TABLE_NAME); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TestZkBasedTableRebalanceObserver.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TestZkBasedTableRebalanceObserver.java new file mode 100644 index 00000000000..74b8a4367f7 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TestZkBasedTableRebalanceObserver.java @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.rebalance; + +import java.util.Arrays; +import java.util.Map; +import java.util.TreeMap; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ERROR; +import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + + +public class TestZkBasedTableRebalanceObserver { + + @Test + // This is a test to verify if Zk stats are pushed out correctly + void testZkObserverTracking() { + PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); + // Mocking this. We will verify using numZkUpdate stat + when(pinotHelixResourceManager.addControllerJobToZK(any(), any(), any())).thenReturn(true); + ZkBasedTableRebalanceObserver observer = + new ZkBasedTableRebalanceObserver("dummy", "dummyId", pinotHelixResourceManager); + Map> source = new TreeMap<>(); + Map> target = new TreeMap<>(); + target.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + source.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE)); + + observer.onTrigger(TableRebalanceObserver.Trigger.START_TRIGGER, source, target); + assertEquals(observer.getNumUpdatesToZk(), 1); + observer.onTrigger(TableRebalanceObserver.Trigger.IDEAL_STATE_CHANGE_TRIGGER, source, source); + observer.onTrigger(TableRebalanceObserver.Trigger.EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER, source, source); + assertEquals(observer.getNumUpdatesToZk(), 1); + observer.onTrigger(TableRebalanceObserver.Trigger.IDEAL_STATE_CHANGE_TRIGGER, source, target); + observer.onTrigger(TableRebalanceObserver.Trigger.EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER, source, target); + assertEquals(observer.getNumUpdatesToZk(), 3); + } + + @Test + void testDifferenceBetweenTableRebalanceStates() { + Map> target = new TreeMap<>(); + target.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + target.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE)); + + // Stats when there's nothing to rebalance + TableRebalanceProgressStats.RebalanceStateStats stats = + ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, target); + assertEquals(stats._segmentsToRebalance, 0); + assertEquals(stats._segmentsMissing, 0); + assertEquals(stats._percentSegmentsToRebalance, 0.0); + + // Stats when there's something to converge + Map> current = new TreeMap<>(); + current.put("segment1", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1"), ONLINE)); + current.put("segment2", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2"), ONLINE)); + + stats = ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, current); + assertEquals(stats._segmentsToRebalance, 2); + assertEquals(stats._percentSegmentsToRebalance, 100.0); + assertEquals(stats._replicasToRebalance, 4); + + // Stats when there are errors + current = new TreeMap<>(); + current.put("segment1", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1"), ERROR)); + + stats = ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, current); + assertEquals(stats._segmentsToRebalance, 2); + assertEquals(stats._segmentsMissing, 1); + assertEquals(stats._replicasToRebalance, 3); + + // Stats when partially converged + current = new TreeMap<>(); + current.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + current.put("segment2", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3"), ONLINE)); + + stats = ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, current); + assertEquals(stats._percentSegmentsToRebalance, 50.0); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java index 852f69337b1..f84be9f029b 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java @@ -18,9 +18,7 @@ */ package org.apache.pinot.controller.helix.core.relocation; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -35,10 +33,7 @@ import org.apache.helix.Criteria; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.messages.SegmentReloadMessage; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ControllerMetrics; -import org.apache.pinot.common.tier.FixedTierSegmentSelector; -import org.apache.pinot.common.tier.Tier; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.LeadControllerManager; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; @@ -54,7 +49,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @@ -119,39 +113,6 @@ public void testTriggerLocalTierMigration() { } } - @Test - public void testUpdateSegmentTargetTierBackToDefault() { - String tableName = "table01_OFFLINE"; - String segmentName = "seg01"; - PinotHelixResourceManager helixMgrMock = mock(PinotHelixResourceManager.class); - when(helixMgrMock.getSegmentMetadataZnRecord(tableName, segmentName)).thenReturn( - createSegmentMetadataZNRecord(segmentName, "hotTier")); - // Move back to default as not tier configs. - List sortedTiers = new ArrayList<>(); - SegmentRelocator.updateSegmentTargetTier(tableName, "seg01", sortedTiers, helixMgrMock); - ArgumentCaptor recordCapture = ArgumentCaptor.forClass(SegmentZKMetadata.class); - verify(helixMgrMock).updateZkMetadata(eq(tableName), recordCapture.capture(), eq(10)); - SegmentZKMetadata record = recordCapture.getValue(); - assertNull(record.getTier()); - } - - @Test - public void testUpdateSegmentTargetTierToNewTier() { - String tableName = "table01_OFFLINE"; - String segmentName = "seg01"; - PinotHelixResourceManager helixMgrMock = mock(PinotHelixResourceManager.class); - when(helixMgrMock.getSegmentMetadataZnRecord(tableName, segmentName)).thenReturn( - createSegmentMetadataZNRecord(segmentName, "hotTier")); - // Move to new tier as set in tier configs. - List sortedTiers = Collections.singletonList( - new Tier("coldTier", new FixedTierSegmentSelector(null, Collections.singleton("seg01")), null)); - SegmentRelocator.updateSegmentTargetTier(tableName, "seg01", sortedTiers, helixMgrMock); - ArgumentCaptor recordCapture = ArgumentCaptor.forClass(SegmentZKMetadata.class); - verify(helixMgrMock).updateZkMetadata(eq(tableName), recordCapture.capture(), eq(10)); - SegmentZKMetadata record = recordCapture.getValue(); - assertEquals(record.getTier(), "coldTier"); - } - @Test public void testRebalanceTablesSequentially() throws InterruptedException { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java index 85aba90f36d..2f16f830b96 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java @@ -77,11 +77,10 @@ private void testDifferentTimeUnits(long pastTimeStamp, TimeUnit timeUnit, long segmentsZKMetadata.add(segmentZKMetadata); } final TableConfig tableConfig = createOfflineTableConfig(); - PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); - setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager); - LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); + PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); + setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager, leadControllerManager); when(pinotHelixResourceManager.getTableConfig(OFFLINE_TABLE_NAME)).thenReturn(tableConfig); when(pinotHelixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME)).thenReturn(segmentsZKMetadata); @@ -98,7 +97,7 @@ private void testDifferentTimeUnits(long pastTimeStamp, TimeUnit timeUnit, long SegmentDeletionManager deletionManager = pinotHelixResourceManager.getSegmentDeletionManager(); // Verify that the removeAgedDeletedSegments() method in deletion manager is actually called. - verify(deletionManager, times(1)).removeAgedDeletedSegments(); + verify(deletionManager, times(1)).removeAgedDeletedSegments(leadControllerManager); // Verify that the deleteSegments method is actually called. verify(pinotHelixResourceManager, times(1)).deleteSegments(anyString(), anyList()); @@ -161,7 +160,7 @@ private TableConfig createRealtimeTableConfig1(int replicaCount) { } private void setupPinotHelixResourceManager(TableConfig tableConfig, final List removedSegments, - PinotHelixResourceManager resourceManager) { + PinotHelixResourceManager resourceManager, LeadControllerManager leadControllerManager) { final String tableNameWithType = tableConfig.getTableName(); when(resourceManager.getAllTables()).thenReturn(Collections.singletonList(tableNameWithType)); @@ -177,7 +176,7 @@ public Void answer(InvocationOnMock invocationOnMock) throws Throwable { return null; } - }).when(deletionManager).removeAgedDeletedSegments(); + }).when(deletionManager).removeAgedDeletedSegments(leadControllerManager); when(resourceManager.getSegmentDeletionManager()).thenReturn(deletionManager); // If and when PinotHelixResourceManager.deleteSegments() is invoked, make sure that the segments deleted @@ -210,12 +209,44 @@ public void testRealtimeLLCCleanup() TableConfig tableConfig = createRealtimeTableConfig1(replicaCount); List removedSegments = new ArrayList<>(); + LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); + when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); PinotHelixResourceManager pinotHelixResourceManager = setupSegmentMetadata(tableConfig, now, initialNumSegments, removedSegments); - setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager); + setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager, leadControllerManager); + + ControllerConf conf = new ControllerConf(); + ControllerMetrics controllerMetrics = new ControllerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); + conf.setRetentionControllerFrequencyInSeconds(0); + conf.setDeletedSegmentsRetentionInDays(0); + RetentionManager retentionManager = + new RetentionManager(pinotHelixResourceManager, leadControllerManager, conf, controllerMetrics); + retentionManager.start(); + retentionManager.run(); + + SegmentDeletionManager deletionManager = pinotHelixResourceManager.getSegmentDeletionManager(); + + // Verify that the removeAgedDeletedSegments() method in deletion manager is actually called. + verify(deletionManager, times(1)).removeAgedDeletedSegments(leadControllerManager); + + // Verify that the deleteSegments method is actually called. + verify(pinotHelixResourceManager, times(1)).deleteSegments(anyString(), anyList()); + } + + // This test makes sure that we do not clean up last llc completed segments + @Test + public void testRealtimeLastLLCCleanup() + throws Exception { + final long now = System.currentTimeMillis(); + final int replicaCount = 1; + TableConfig tableConfig = createRealtimeTableConfig1(replicaCount); + List removedSegments = new ArrayList<>(); LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); + PinotHelixResourceManager pinotHelixResourceManager = + setupSegmentMetadataForPausedTable(tableConfig, now, removedSegments); + setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager, leadControllerManager); ControllerConf conf = new ControllerConf(); ControllerMetrics controllerMetrics = new ControllerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); @@ -229,7 +260,7 @@ public void testRealtimeLLCCleanup() SegmentDeletionManager deletionManager = pinotHelixResourceManager.getSegmentDeletionManager(); // Verify that the removeAgedDeletedSegments() method in deletion manager is actually called. - verify(deletionManager, times(1)).removeAgedDeletedSegments(); + verify(deletionManager, times(1)).removeAgedDeletedSegments(leadControllerManager); // Verify that the deleteSegments method is actually called. verify(pinotHelixResourceManager, times(1)).deleteSegments(anyString(), anyList()); @@ -299,6 +330,50 @@ private PinotHelixResourceManager setupSegmentMetadata(TableConfig tableConfig, return pinotHelixResourceManager; } + private PinotHelixResourceManager setupSegmentMetadataForPausedTable(TableConfig tableConfig, final long now, + List segmentsToBeDeleted) { + final int replicaCount = tableConfig.getReplication(); + + List segmentsZKMetadata = new ArrayList<>(); + + IdealState idealState = + PinotTableIdealStateBuilder.buildEmptyRealtimeIdealStateFor(REALTIME_TABLE_NAME, replicaCount, true); + + final int kafkaPartition = 5; + final long millisInDays = TimeUnit.DAYS.toMillis(1); + final String serverName = "Server_localhost_0"; + LLCSegmentName llcSegmentName0 = new LLCSegmentName(TEST_TABLE_NAME, kafkaPartition, 0, now); + SegmentZKMetadata segmentZKMetadata0 = createSegmentZKMetadata(llcSegmentName0.getSegmentName(), replicaCount, now); + segmentZKMetadata0.setTimeUnit(TimeUnit.MILLISECONDS); + segmentZKMetadata0.setStartTime(now - 30 * millisInDays); + segmentZKMetadata0.setEndTime(now - 20 * millisInDays); + segmentZKMetadata0.setStatus(CommonConstants.Segment.Realtime.Status.DONE); + segmentsZKMetadata.add(segmentZKMetadata0); + idealState.setPartitionState(llcSegmentName0.getSegmentName(), serverName, "ONLINE"); + segmentsToBeDeleted.add(llcSegmentName0.getSegmentName()); + + LLCSegmentName llcSegmentName1 = new LLCSegmentName(TEST_TABLE_NAME, kafkaPartition, 1, now); + SegmentZKMetadata segmentZKMetadata1 = createSegmentZKMetadata(llcSegmentName1.getSegmentName(), replicaCount, now); + segmentZKMetadata1.setTimeUnit(TimeUnit.MILLISECONDS); + segmentZKMetadata1.setStartTime(now - 20 * millisInDays); + segmentZKMetadata1.setEndTime(now - 10 * millisInDays); + segmentZKMetadata1.setStatus(CommonConstants.Segment.Realtime.Status.DONE); + segmentsZKMetadata.add(segmentZKMetadata1); + idealState.setPartitionState(llcSegmentName1.getSegmentName(), serverName, "ONLINE"); + + PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); + when(pinotHelixResourceManager.getTableConfig(REALTIME_TABLE_NAME)).thenReturn(tableConfig); + when(pinotHelixResourceManager.getSegmentsZKMetadata(REALTIME_TABLE_NAME)).thenReturn(segmentsZKMetadata); + when(pinotHelixResourceManager.getHelixClusterName()).thenReturn(HELIX_CLUSTER_NAME); + when(pinotHelixResourceManager.getLastLLCCompletedSegments(REALTIME_TABLE_NAME)).thenCallRealMethod(); + + HelixAdmin helixAdmin = mock(HelixAdmin.class); + when(helixAdmin.getResourceIdealState(HELIX_CLUSTER_NAME, REALTIME_TABLE_NAME)).thenReturn(idealState); + when(pinotHelixResourceManager.getHelixAdmin()).thenReturn(helixAdmin); + + return pinotHelixResourceManager; + } + private SegmentZKMetadata createSegmentZKMetadata(String segmentName, int replicaCount, long segmentCreationTime) { SegmentZKMetadata segmentMetadata = new SegmentZKMetadata(segmentName); segmentMetadata.setCreationTime(segmentCreationTime); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java index a617dd8612a..b4d0356c5af 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java @@ -38,6 +38,7 @@ import org.apache.helix.model.IdealState; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.controller.LeadControllerManager; import org.apache.pinot.controller.helix.core.SegmentDeletionManager; import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; @@ -228,9 +229,11 @@ public void testRemoveDeletedSegments() tempDir.deleteOnExit(); FakeDeletionManager deletionManager = new FakeDeletionManager( tempDir.getAbsolutePath(), helixAdmin, propertyStore, 7); + LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); + when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); // Test delete when deleted segments directory does not exists - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); // Create deleted directory String deletedDirectoryPath = tempDir + File.separator + "Deleted_Segments"; @@ -238,7 +241,7 @@ public void testRemoveDeletedSegments() deletedDirectory.mkdir(); // Test delete when deleted segments directory is empty - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); // Create dummy directories and files File dummyDir1 = new File(deletedDirectoryPath + File.separator + "dummy1"); @@ -249,7 +252,7 @@ public void testRemoveDeletedSegments() dummyDir3.mkdir(); // Test delete when there is no files but some directories exist - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); Assert.assertEquals(dummyDir1.exists(), false); Assert.assertEquals(dummyDir2.exists(), false); Assert.assertEquals(dummyDir3.exists(), false); @@ -279,7 +282,7 @@ public void testRemoveDeletedSegments() Assert.assertEquals(dummyDir3.list().length, 3); // Try to remove files with the retention of 1 days. - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); // Check that only 1 day retention file is remaining Assert.assertEquals(dummyDir1.list().length, 1); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java b/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java index 6c769d620a5..a604e7e70e1 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java @@ -19,9 +19,8 @@ package org.apache.pinot.controller.utils; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.TimeUnit; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.segment.spi.ColumnMetadata; @@ -40,26 +39,38 @@ private SegmentMetadataMockUtils() { } public static SegmentMetadata mockSegmentMetadata(String tableName, String segmentName, int numTotalDocs, - String crc) { + String crc, long startTime, long endTime, TimeUnit timeUnit) { SegmentMetadata segmentMetadata = Mockito.mock(SegmentMetadata.class); Mockito.when(segmentMetadata.getTableName()).thenReturn(tableName); Mockito.when(segmentMetadata.getName()).thenReturn(segmentName); Mockito.when(segmentMetadata.getTotalDocs()).thenReturn(numTotalDocs); Mockito.when(segmentMetadata.getCrc()).thenReturn(crc); - Mockito.when(segmentMetadata.getStartTime()).thenReturn(1L); - Mockito.when(segmentMetadata.getEndTime()).thenReturn(10L); + Mockito.when(segmentMetadata.getStartTime()).thenReturn(startTime); + Mockito.when(segmentMetadata.getEndTime()).thenReturn(endTime); Mockito.when(segmentMetadata.getTimeInterval()).thenReturn( - new Interval(TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS), - TimeUnit.MILLISECONDS.convert(10, TimeUnit.DAYS))); - Mockito.when(segmentMetadata.getTimeUnit()).thenReturn(TimeUnit.DAYS); + new Interval(TimeUnit.MILLISECONDS.convert(startTime, timeUnit), + TimeUnit.MILLISECONDS.convert(endTime, timeUnit))); + Mockito.when(segmentMetadata.getTimeUnit()).thenReturn(timeUnit); return segmentMetadata; } + public static SegmentMetadata mockSegmentMetadata(String tableName, String segmentName, int numTotalDocs, + String crc) { + return mockSegmentMetadata(tableName, segmentName, numTotalDocs, crc, 1L, 10L, TimeUnit.DAYS); + } + public static SegmentMetadata mockSegmentMetadata(String tableName) { String uniqueNumericString = Long.toString(System.nanoTime()); return mockSegmentMetadata(tableName, tableName + uniqueNumericString, 100, uniqueNumericString); } + public static SegmentMetadata mockSegmentMetadata(String tableName, long startTime, + long endTime, TimeUnit timeUnit) { + String uniqueNumericString = Long.toString(System.nanoTime()); + return mockSegmentMetadata(tableName, tableName + uniqueNumericString, 100, + uniqueNumericString, startTime, endTime, timeUnit); + } + public static SegmentMetadata mockSegmentMetadata(String tableName, String segmentName) { String uniqueNumericString = Long.toString(System.nanoTime()); return mockSegmentMetadata(tableName, segmentName, 100, uniqueNumericString); @@ -72,7 +83,7 @@ public static SegmentZKMetadata mockSegmentZKMetadata(String segmentName, long n return segmentZKMetadata; } - public static SegmentMetadata mockSegmentMetadataWithPartitionInfo(String tableName, String segmentName, + public static SegmentMetadata mockSegmentMetadataWithPartitionInfo(String rawTableName, String segmentName, String columnName, int partitionNumber) { ColumnMetadata columnMetadata = mock(ColumnMetadata.class); Set partitions = Collections.singleton(partitionNumber); @@ -83,11 +94,11 @@ public static SegmentMetadata mockSegmentMetadataWithPartitionInfo(String tableN if (columnName != null) { when(segmentMetadata.getColumnMetadataFor(columnName)).thenReturn(columnMetadata); } - when(segmentMetadata.getTableName()).thenReturn(tableName); + when(segmentMetadata.getTableName()).thenReturn(rawTableName); when(segmentMetadata.getName()).thenReturn(segmentName); when(segmentMetadata.getCrc()).thenReturn("0"); - Map columnMetadataMap = new HashMap<>(); + TreeMap columnMetadataMap = new TreeMap<>(); columnMetadataMap.put(columnName, columnMetadata); when(segmentMetadata.getColumnMetadataMap()).thenReturn(columnMetadataMap); return segmentMetadata; diff --git a/pinot-core/pom.xml b/pinot-core/pom.xml index 3c8463a05be..1a2ce38efd4 100644 --- a/pinot-core/pom.xml +++ b/pinot-core/pom.xml @@ -55,6 +55,10 @@ + + com.yscope.clp + clp-ffi + com.uber h3 diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java index 0cf6688e489..431643942aa 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java @@ -18,8 +18,6 @@ */ package org.apache.pinot.core.accounting; -import java.util.HashMap; -import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -32,33 +30,47 @@ */ public class CPUMemThreadLevelAccountingObjects { - public static class StatsDigest { - - // The current usage sampling for each thread - final long[] _currentStatsSample; - // The previous usage sampling for each thread - final long[] _lastStatSample; - // The aggregated usage sampling for the finished tasks of a (still) running queries - final HashMap _finishedTaskStatAggregator; - - StatsDigest(int numThreads) { - _currentStatsSample = new long[numThreads]; - _lastStatSample = new long[numThreads]; - _finishedTaskStatAggregator = new HashMap<>(); - } - } - /** - * Entry to track the task execution status of a worker/runner given thread + * Entry to track the task execution status and usage stats of a Thread + * (including but not limited to server worker thread, runner thread, broker jetty thread, or broker netty thread) */ - public static class TaskEntryHolder { - AtomicReference _threadTaskStatus = new AtomicReference<>(null); + public static class ThreadEntry { + // current query_id, task_id of the thread; this field is accessed by the thread itself and the accountant + AtomicReference _currentThreadTaskStatus = new AtomicReference<>(); + // current sample of thread memory usage/cputime ; this field is accessed by the thread itself and the accountant + volatile long _currentThreadCPUTimeSampleMS = 0; + volatile long _currentThreadMemoryAllocationSampleBytes = 0; + + // previous query_id, task_id of the thread, this field should only be accessed by the accountant + TaskEntry _previousThreadTaskStatus = null; + // previous cpu time and memory allocation of the thread + // these fields should only be accessed by the accountant + long _previousThreadCPUTimeSampleMS = 0; + long _previousThreadMemoryAllocationSampleBytes = 0; + + // error message store per runner/worker thread, + // will put preemption reasons in this for the killed thread to pickup + AtomicReference _errorStatus = new AtomicReference<>(); + + @Override + public String toString() { + TaskEntry taskEntry = _currentThreadTaskStatus.get(); + return "ThreadEntry{" + + "_currentThreadTaskStatus=" + (taskEntry == null ? "idle" : taskEntry.toString()) + + ", _errorStatus=" + _errorStatus + + '}'; + } /** - * set the thread tracking info to null + * set the thread tracking info to null and usage samples to zero */ public void setToIdle() { - _threadTaskStatus.set(null); + // clear task info + _currentThreadTaskStatus.set(null); + // clear CPU time + _currentThreadCPUTimeSampleMS = 0; + // clear memory usage + _currentThreadMemoryAllocationSampleBytes = 0; } /** @@ -66,16 +78,20 @@ public void setToIdle() { * @return the current query id on the thread, {@code null} if idle */ @Nullable - public TaskEntry getThreadTaskStatus() { - return _threadTaskStatus.get(); + public TaskEntry getCurrentThreadTaskStatus() { + return _currentThreadTaskStatus.get(); } - public TaskEntryHolder setThreadTaskStatus(@Nonnull String queryId, int taskId, @Nonnull Thread thread) { - _threadTaskStatus.set(new TaskEntry(queryId, taskId, thread)); - return this; + public void setThreadTaskStatus(@Nonnull String queryId, int taskId, @Nonnull Thread anchorThread) { + _currentThreadTaskStatus.set(new TaskEntry(queryId, taskId, anchorThread)); } } + /** + * Class to track the execution status of a thread. query_id is an instance level unique query_id, + * taskId is the worker thread id when we have a runner-worker thread model + * anchor thread refers to the runner in runner-worker thread model + */ public static class TaskEntry implements ThreadExecutionContext { private final String _queryId; private final int _taskId; @@ -91,17 +107,6 @@ public TaskEntry(String queryId, int taskId, Thread anchorThread) { _anchorThread = anchorThread; } - public static boolean isSameTask(TaskEntry currentTaskStatus, TaskEntry lastQueryTask) { - if (currentTaskStatus == null) { - return lastQueryTask == null; - } else if (lastQueryTask == null) { - return false; - } else { - return Objects.equals(currentTaskStatus.getQueryId(), lastQueryTask.getQueryId()) - || currentTaskStatus.getTaskId() == lastQueryTask.getTaskId(); - } - } - public String getQueryId() { return _queryId; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java index 03d510b949e..e29d33d0445 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java @@ -38,8 +38,8 @@ public class HeapUsagePublishingAccountantFactory implements ThreadAccountantFactory { @Override - public ThreadResourceUsageAccountant init(int numRunnerThreads, int numWorkerThreads, PinotConfiguration config) { - int period = config.getProperty(CommonConstants.Accounting.CONFIG_OF_HEAP_USAGE_PUBLISH_PERIOD, + public ThreadResourceUsageAccountant init(PinotConfiguration config, String instanceId) { + int period = config.getProperty(CommonConstants.Accounting.CONFIG_OF_HEAP_USAGE_PUBLISHING_PERIOD_MS, CommonConstants.Accounting.DEFAULT_HEAP_USAGE_PUBLISH_PERIOD); return new HeapUsagePublishingResourceUsageAccountant(period); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java index 4f488deca70..5b724f4a0fb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java @@ -20,27 +20,31 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; +import org.apache.pinot.common.metrics.AbstractMetrics; +import org.apache.pinot.common.metrics.BrokerGauge; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.ServerGauge; import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.core.accounting.utils.RunnerWorkerThreadOffsetProvider; import org.apache.pinot.spi.accounting.ThreadAccountantFactory; import org.apache.pinot.spi.accounting.ThreadExecutionContext; import org.apache.pinot.spi.accounting.ThreadResourceUsageAccountant; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; +import org.apache.pinot.spi.config.instance.InstanceType; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.Logger; @@ -55,8 +59,8 @@ public class PerQueryCPUMemAccountantFactory implements ThreadAccountantFactory { @Override - public ThreadResourceUsageAccountant init(int numRunnerThreads, int numWorkerThreads, PinotConfiguration config) { - return new PerQueryCPUMemResourceUsageAccountant(numRunnerThreads + numWorkerThreads, config); + public ThreadResourceUsageAccountant init(PinotConfiguration config, String instanceId) { + return new PerQueryCPUMemResourceUsageAccountant(config, instanceId); } public static class PerQueryCPUMemResourceUsageAccountant extends Tracing.DefaultThreadResourceUsageAccountant { @@ -66,6 +70,7 @@ public static class PerQueryCPUMemResourceUsageAccountant extends Tracing.Defaul */ static final MemoryMXBean MEMORY_MX_BEAN = ManagementFactory.getMemoryMXBean(); private static final Logger LOGGER = LoggerFactory.getLogger(PerQueryCPUMemResourceUsageAccountant.class); + private static final boolean IS_DEBUG_MODE_ENABLED = LOGGER.isDebugEnabled(); /** * Executor service for the thread accounting task, slightly higher priority than normal priority */ @@ -79,44 +84,55 @@ public static class PerQueryCPUMemResourceUsageAccountant extends Tracing.Defaul return thread; }); - // number of total threads - private final int _numThreads; private final PinotConfiguration _config; - private final RunnerWorkerThreadOffsetProvider _runnerWorkerThreadOffsetProvider; - // query_id, task_id per runner/worker thread - private final CPUMemThreadLevelAccountingObjects.TaskEntryHolder[] _taskStatus; + // the map to track stats entry for each thread, the entry will automatically be added when one calls + // setThreadResourceUsageProvider on the thread, including but not limited to + // server worker thread, runner thread, broker jetty thread, or broker netty thread + private final ConcurrentHashMap _threadEntriesMap + = new ConcurrentHashMap<>(); + + // For one time concurrent update of stats. This is to provide stats collection for parts that are not + // performance sensitive and query_id is not known beforehand (e.g. broker inbound netty thread) + private final ConcurrentHashMap _concurrentTaskCPUStatsAggregator = new ConcurrentHashMap<>(); + private final ConcurrentHashMap _concurrentTaskMemStatsAggregator = new ConcurrentHashMap<>(); + + // for stats aggregation of finished (worker) threads when the runner is still running + private final HashMap _finishedTaskCPUStatsAggregator = new HashMap<>(); + private final HashMap _finishedTaskMemStatsAggregator = new HashMap<>(); + + private final ThreadLocal _threadLocalEntry + = ThreadLocal.withInitial(() -> { + CPUMemThreadLevelAccountingObjects.ThreadEntry ret = + new CPUMemThreadLevelAccountingObjects.ThreadEntry(); + _threadEntriesMap.put(Thread.currentThread(), ret); + LOGGER.info("Adding thread to _threadLocalEntry: {}", Thread.currentThread().getName()); + return ret; + } + ); // ThreadResourceUsageProvider(ThreadMXBean wrapper) per runner/worker thread private final ThreadLocal _threadResourceUsageProvider; // track thread cpu time private final boolean _isThreadCPUSamplingEnabled; - // cpu time samples per runner/worker thread - private final CPUMemThreadLevelAccountingObjects.StatsDigest _cpuTimeSamplesNS; // track memory usage private final boolean _isThreadMemorySamplingEnabled; - // memory usage samples per runner/worker thread - private final CPUMemThreadLevelAccountingObjects.StatsDigest _memorySamplesBytes; - - // the last seen task_id-query_id - private final CPUMemThreadLevelAccountingObjects.TaskEntry[] _lastQueryTask; private final Set _inactiveQuery; - // error message store per runner/worker thread, - // will put preemption reasons in this for the killed thread to pickup - private final List> _errorStatus; // the periodical task that aggregates and preempts queries private final WatcherTask _watcherTask; - public PerQueryCPUMemResourceUsageAccountant(int numThreads, PinotConfiguration config) { + // instance id of the current instance, for logging purpose + private final String _instanceId; + + public PerQueryCPUMemResourceUsageAccountant(PinotConfiguration config, String instanceId) { LOGGER.info("Initializing PerQueryCPUMemResourceUsageAccountant"); - _numThreads = numThreads; _config = config; - _runnerWorkerThreadOffsetProvider = new RunnerWorkerThreadOffsetProvider(); + _instanceId = instanceId; boolean threadCpuTimeMeasurementEnabled = ThreadResourceUsageProvider.isThreadCpuTimeMeasurementEnabled(); boolean threadMemoryMeasurementEnabled = ThreadResourceUsageProvider.isThreadMemoryMeasurementEnabled(); @@ -137,29 +153,10 @@ public PerQueryCPUMemResourceUsageAccountant(int numThreads, PinotConfiguration LOGGER.info("_isThreadCPUSamplingEnabled: {}, _isThreadMemorySamplingEnabled: {}", _isThreadCPUSamplingEnabled, _isThreadMemorySamplingEnabled); - _taskStatus = new CPUMemThreadLevelAccountingObjects.TaskEntryHolder[_numThreads]; - _errorStatus = new ArrayList<>(_numThreads); - for (int i = 0; i < _numThreads; i++) { - _taskStatus[i] = new CPUMemThreadLevelAccountingObjects.TaskEntryHolder(); - _errorStatus.add(new AtomicReference<>(null)); - } - - if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS = new CPUMemThreadLevelAccountingObjects.StatsDigest(_numThreads); - } else { - _cpuTimeSamplesNS = null; - } - if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes = new CPUMemThreadLevelAccountingObjects.StatsDigest(_numThreads); - } else { - _memorySamplesBytes = null; - } - // ThreadMXBean wrapper _threadResourceUsageProvider = new ThreadLocal<>(); // task/query tracking - _lastQueryTask = new CPUMemThreadLevelAccountingObjects.TaskEntry[_numThreads]; _inactiveQuery = new HashSet<>(); _watcherTask = new WatcherTask(); @@ -171,6 +168,28 @@ public void sampleUsage() { sampleThreadCPUTime(); } + /** + * for testing only + */ + public int getEntryCount() { + return _threadEntriesMap.size(); + } + + @Override + public void updateQueryUsageConcurrently(String queryId) { + if (_isThreadCPUSamplingEnabled) { + long cpuUsageNS = getThreadResourceUsageProvider().getThreadTimeNs(); + _concurrentTaskCPUStatsAggregator.compute(queryId, + (key, value) -> (value == null) ? cpuUsageNS : (value + cpuUsageNS)); + } + if (_isThreadMemorySamplingEnabled) { + long memoryAllocatedBytes = getThreadResourceUsageProvider().getThreadAllocatedBytes(); + _concurrentTaskMemStatsAggregator.compute(queryId, + (key, value) -> (value == null) ? memoryAllocatedBytes : (value + memoryAllocatedBytes)); + } + } + + /** * The thread would need to do {@code setThreadResourceUsageProvider} first upon it is scheduled. * This is to be called from a worker or a runner thread to update its corresponding cpu usage entry @@ -178,8 +197,7 @@ public void sampleUsage() { @SuppressWarnings("ConstantConditions") public void sampleThreadCPUTime() { if (_isThreadCPUSamplingEnabled) { - int tid = _runnerWorkerThreadOffsetProvider.get(); - _cpuTimeSamplesNS._currentStatsSample[tid] = getThreadResourceUsageProvider().getThreadTimeNs(); + _threadLocalEntry.get()._currentThreadCPUTimeSampleMS = getThreadResourceUsageProvider().getThreadTimeNs(); } } @@ -190,8 +208,8 @@ public void sampleThreadCPUTime() { @SuppressWarnings("ConstantConditions") public void sampleThreadBytesAllocated() { if (_isThreadMemorySamplingEnabled) { - int tid = _runnerWorkerThreadOffsetProvider.get(); - _memorySamplesBytes._currentStatsSample[tid] = getThreadResourceUsageProvider().getThreadAllocatedBytes(); + _threadLocalEntry.get()._currentThreadMemoryAllocationSampleBytes + = getThreadResourceUsageProvider().getThreadAllocatedBytes(); } } @@ -207,22 +225,22 @@ public void setThreadResourceUsageProvider(ThreadResourceUsageProvider threadRes @Override public void createExecutionContextInner(@Nullable String queryId, int taskId, @Nullable ThreadExecutionContext parentContext) { - int tid = _runnerWorkerThreadOffsetProvider.get(); + _threadLocalEntry.get()._errorStatus.set(null); if (parentContext == null) { // is anchor thread assert queryId != null; - _taskStatus[tid].setThreadTaskStatus(queryId, CommonConstants.Accounting.ANCHOR_TASK_ID, + _threadLocalEntry.get().setThreadTaskStatus(queryId, CommonConstants.Accounting.ANCHOR_TASK_ID, Thread.currentThread()); } else { // not anchor thread - _taskStatus[tid].setThreadTaskStatus(parentContext.getQueryId(), taskId, parentContext.getAnchorThread()); + _threadLocalEntry.get().setThreadTaskStatus(parentContext.getQueryId(), taskId, + parentContext.getAnchorThread()); } } @Override public ThreadExecutionContext getThreadExecutionContext() { - int tid = _runnerWorkerThreadOffsetProvider.get(); - return _taskStatus[tid].getThreadTaskStatus(); + return _threadLocalEntry.get().getCurrentThreadTaskStatus(); } /** @@ -231,20 +249,12 @@ public ThreadExecutionContext getThreadExecutionContext() { @SuppressWarnings("ConstantConditions") @Override public void clear() { - int tid = _runnerWorkerThreadOffsetProvider.get(); - // clear task info - _taskStatus[tid].setToIdle(); - // clear CPU time - if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS._currentStatsSample[tid] = 0; - } - // clear memory usage - if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes._currentStatsSample[tid] = 0; - } + CPUMemThreadLevelAccountingObjects.ThreadEntry threadEntry = _threadLocalEntry.get(); + // clear task info + stats + threadEntry.setToIdle(); // clear threadResourceUsageProvider _threadResourceUsageProvider.set(null); - // clear _rootThread + // clear _anchorThread super.clear(); } @@ -259,18 +269,22 @@ public void startWatcherTask() { public void cleanInactive() { for (String inactiveQueryId : _inactiveQuery) { if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS._finishedTaskStatAggregator.remove(inactiveQueryId); + _finishedTaskCPUStatsAggregator.remove(inactiveQueryId); + _concurrentTaskCPUStatsAggregator.remove(inactiveQueryId); } if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes._finishedTaskStatAggregator.remove(inactiveQueryId); + _finishedTaskMemStatsAggregator.remove(inactiveQueryId); + _concurrentTaskMemStatsAggregator.remove(inactiveQueryId); } } _inactiveQuery.clear(); if (_isThreadCPUSamplingEnabled) { - _inactiveQuery.addAll(_cpuTimeSamplesNS._finishedTaskStatAggregator.keySet()); + _inactiveQuery.addAll(_finishedTaskCPUStatsAggregator.keySet()); + _inactiveQuery.addAll(_concurrentTaskCPUStatsAggregator.keySet()); } if (_isThreadMemorySamplingEnabled) { - _inactiveQuery.addAll(_memorySamplesBytes._finishedTaskStatAggregator.keySet()); + _inactiveQuery.addAll(_finishedTaskMemStatsAggregator.keySet()); + _inactiveQuery.addAll(_concurrentTaskMemStatsAggregator.keySet()); } } @@ -286,43 +300,45 @@ public Map aggregate(boolean isTriggered) { } // for each {pqr, pqw} - for (int threadId = 0; threadId < _numThreads; threadId++) { + for (Map.Entry entry : _threadEntriesMap.entrySet()) { // sample current usage + CPUMemThreadLevelAccountingObjects.ThreadEntry threadEntry = entry.getValue(); long currentCPUSample = _isThreadCPUSamplingEnabled - ? _cpuTimeSamplesNS._currentStatsSample[threadId] : 0; + ? threadEntry._currentThreadCPUTimeSampleMS : 0; long currentMemSample = _isThreadMemorySamplingEnabled - ? _memorySamplesBytes._currentStatsSample[threadId] : 0; + ? threadEntry._currentThreadMemoryAllocationSampleBytes : 0; // sample current running task status - CPUMemThreadLevelAccountingObjects.TaskEntry currentTaskStatus = _taskStatus[threadId].getThreadTaskStatus(); - LOGGER.trace("tid: {}, task: {}", threadId, currentTaskStatus); + CPUMemThreadLevelAccountingObjects.TaskEntry currentTaskStatus = threadEntry.getCurrentThreadTaskStatus(); + Thread thread = entry.getKey(); + LOGGER.trace("tid: {}, task: {}", thread.getId(), currentTaskStatus); // get last task on the thread - CPUMemThreadLevelAccountingObjects.TaskEntry lastQueryTask = _lastQueryTask[threadId]; + CPUMemThreadLevelAccountingObjects.TaskEntry lastQueryTask = threadEntry._previousThreadTaskStatus; // accumulate recorded previous stat to it's _finishedTaskStatAggregator // if the last task on the same thread has finished - if (!CPUMemThreadLevelAccountingObjects.TaskEntry.isSameTask(currentTaskStatus, lastQueryTask)) { + if (!(currentTaskStatus == lastQueryTask)) { // set previous value to current task stats - _lastQueryTask[threadId] = currentTaskStatus; + threadEntry._previousThreadTaskStatus = currentTaskStatus; if (lastQueryTask != null) { String lastQueryId = lastQueryTask.getQueryId(); if (_isThreadCPUSamplingEnabled) { - long lastSample = _cpuTimeSamplesNS._lastStatSample[threadId]; - _cpuTimeSamplesNS._finishedTaskStatAggregator.merge(lastQueryId, lastSample, Long::sum); + long lastSample = threadEntry._previousThreadCPUTimeSampleMS; + _finishedTaskCPUStatsAggregator.merge(lastQueryId, lastSample, Long::sum); } if (_isThreadMemorySamplingEnabled) { - long lastSample = _memorySamplesBytes._lastStatSample[threadId]; - _memorySamplesBytes._finishedTaskStatAggregator.merge(lastQueryId, lastSample, Long::sum); + long lastSample = threadEntry._previousThreadMemoryAllocationSampleBytes; + _finishedTaskMemStatsAggregator.merge(lastQueryId, lastSample, Long::sum); } } } // record current usage values for future accumulation if this task is done if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS._lastStatSample[threadId] = currentCPUSample; + threadEntry._previousThreadCPUTimeSampleMS = currentCPUSample; } if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes._lastStatSample[threadId] = currentMemSample; + threadEntry._previousThreadMemoryAllocationSampleBytes = currentMemSample; } // if current thread is not idle @@ -333,15 +349,19 @@ public Map aggregate(boolean isTriggered) { _inactiveQuery.remove(queryId); // if triggered, accumulate active query task stats if (isTriggered) { - Thread thread = currentTaskStatus.getAnchorThread(); - int finalThreadId = threadId; + Thread anchorThread = currentTaskStatus.getAnchorThread(); boolean isAnchorThread = currentTaskStatus.isAnchorThread(); ret.compute(queryId, (k, v) -> v == null - ? new AggregatedStats(currentCPUSample, currentMemSample, thread, isAnchorThread, - finalThreadId, queryId) - : v.merge(currentCPUSample, currentMemSample, isAnchorThread, finalThreadId)); + ? new AggregatedStats(currentCPUSample, currentMemSample, anchorThread, + isAnchorThread, threadEntry._errorStatus, queryId) + : v.merge(currentCPUSample, currentMemSample, isAnchorThread, threadEntry._errorStatus)); } } + + if (!thread.isAlive()) { + _threadEntriesMap.remove(thread); + LOGGER.info("Removing thread from _threadLocalEntry: {}", thread.getName()); + } } // if triggered, accumulate stats of finished tasks of each active query @@ -349,19 +369,26 @@ public Map aggregate(boolean isTriggered) { for (Map.Entry queryIdResult : ret.entrySet()) { String activeQueryId = queryIdResult.getKey(); long accumulatedCPUValue = _isThreadCPUSamplingEnabled - ? _cpuTimeSamplesNS._finishedTaskStatAggregator.getOrDefault(activeQueryId, 0L) : 0; + ? _finishedTaskCPUStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; + long concurrentCPUValue = _isThreadCPUSamplingEnabled + ? _concurrentTaskCPUStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; long accumulatedMemValue = _isThreadMemorySamplingEnabled - ? _memorySamplesBytes._finishedTaskStatAggregator.getOrDefault(activeQueryId, 0L) : 0; - queryIdResult.getValue().merge(accumulatedCPUValue, accumulatedMemValue, - false, CommonConstants.Accounting.IGNORED_TASK_ID); + ? _finishedTaskMemStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; + long concurrentMemValue = _isThreadMemorySamplingEnabled + ? _concurrentTaskMemStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; + queryIdResult.getValue().merge(accumulatedCPUValue + concurrentCPUValue, + accumulatedMemValue + concurrentMemValue, false, null); } } return ret; } + public void postAggregation(Map aggregatedUsagePerActiveQuery) { + } + @Override public Exception getErrorStatus() { - return _errorStatus.get(_runnerWorkerThreadOffsetProvider.get()).getAndSet(null); + return _threadLocalEntry.get()._errorStatus.getAndSet(null); } /** @@ -369,39 +396,39 @@ public Exception getErrorStatus() { * the ordinal Normal(0) does not trigger any action. */ enum TriggeringLevel { - Normal, HeapMemoryAlarmingVerbose, HeapMemoryCritical, HeapMemoryPanic + Normal, HeapMemoryAlarmingVerbose, CPUTimeBasedKilling, HeapMemoryCritical, HeapMemoryPanic } /** * aggregated usage of a query, _thread is the runner */ - static class AggregatedStats { + protected static class AggregatedStats { final String _queryId; - final Thread _thread; - int _threadId; + final Thread _anchorThread; boolean _isAnchorThread; + AtomicReference _exceptionAtomicReference; long _allocatedBytes; long _cpuNS; - - public AggregatedStats(long cpuNS, long allocatedBytes, Thread thread, Boolean isAnchorThread, int threadId, - String queryId) { + public AggregatedStats(long cpuNS, long allocatedBytes, Thread anchorThread, boolean isAnchorThread, + AtomicReference exceptionAtomicReference, String queryId) { _cpuNS = cpuNS; _allocatedBytes = allocatedBytes; - _thread = thread; - _threadId = threadId; - _queryId = queryId; + _anchorThread = anchorThread; _isAnchorThread = isAnchorThread; + _exceptionAtomicReference = exceptionAtomicReference; + _queryId = queryId; } @Override public String toString() { return "AggregatedStats{" + "_queryId='" + _queryId + '\'' + + ", _anchorThread=" + _anchorThread + + ", _isAnchorThread=" + _isAnchorThread + + ", _exceptionAtomicReference=" + _exceptionAtomicReference + ", _allocatedBytes=" + _allocatedBytes + ", _cpuNS=" + _cpuNS - + ", _thread=" + _thread - + ", _threadId=" + _threadId + '}'; } @@ -413,19 +440,21 @@ public long getAllocatedBytes() { return _allocatedBytes; } - public Thread getThread() { - return _thread; + public Thread getAnchorThread() { + return _anchorThread; } - public AggregatedStats merge(long cpuNS, long memoryBytes, boolean isAnchorThread, int threadId) { + public AggregatedStats merge(long cpuNS, long memoryBytes, boolean isAnchorThread, + AtomicReference exceptionAtomicReference) { _cpuNS += cpuNS; _allocatedBytes += memoryBytes; // the merging results is from an anchor thread if (isAnchorThread) { _isAnchorThread = true; - _threadId = threadId; + _exceptionAtomicReference = exceptionAtomicReference; } + // everything else is already set during creation return this; } @@ -454,8 +483,17 @@ class WatcherTask implements Runnable { * _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO, CommonConstants.Accounting.DEFAULT_CRITICAL_LEVEL_HEAP_USAGE_RATIO)); + // if after gc the heap usage is still above this, kill the most expensive query + // use this to prevent heap size oscillation and repeatedly triggering gc + private final long _criticalLevelAfterGC = _criticalLevel - (long) (_maxHeapSize + * _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO_DELTA_AFTER_GC, + CommonConstants.Accounting.DEFAULT_CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO_DELTA_AFTER_GC)); + // trigger gc if consecutively kill more than some number of queries - private final int _gcTriggerCount = + // set this to 0 to always trigger gc before killing a query to give gc a second chance + // as would minimize the chance of false positive killing in some usecases + // should consider use -XX:+ExplicitGCInvokesConcurrent to avoid STW for some gc algorithms + private final int _gcBackoffCount = _config.getProperty(CommonConstants.Accounting.CONFIG_OF_GC_BACKOFF_COUNT, CommonConstants.Accounting.DEFAULT_GC_BACKOFF_COUNT); @@ -467,8 +505,16 @@ class WatcherTask implements Runnable { // normal sleep time private final int _normalSleepTime = - _config.getProperty(CommonConstants.Accounting.CONFIG_OF_SLEEP_TIME, - CommonConstants.Accounting.DEFAULT_SLEEP_TIME); + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_SLEEP_TIME_MS, + CommonConstants.Accounting.DEFAULT_SLEEP_TIME_MS); + + // wait for gc to complete, according to system.gc() javadoc, when control returns from the method call, + // the Java Virtual Machine has made a best effort to reclaim space from all discarded objects. + // Therefore, we default this to 0. + // Tested with Shenandoah GC and G1GC, with -XX:+ExplicitGCInvokesConcurrent + private final int _gcWaitTime = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_GC_WAIT_TIME_MS, + CommonConstants.Accounting.DEFAULT_CONFIG_OF_GC_WAIT_TIME_MS); // alarming sleep time denominator, should be > 1 to sample more frequent at alarming level private final int _alarmingSleepTimeDenominator = @@ -488,18 +534,84 @@ class WatcherTask implements Runnable { _config.getProperty(CommonConstants.Accounting.CONFIG_OF_PUBLISHING_JVM_USAGE, CommonConstants.Accounting.DEFAULT_PUBLISHING_JVM_USAGE); + // if we want kill query based on CPU time + private final boolean _isCPUTimeBasedKillingEnabled = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CPU_TIME_BASED_KILLING_ENABLED, + CommonConstants.Accounting.DEFAULT_CPU_TIME_BASED_KILLING_ENABLED) && _isThreadCPUSamplingEnabled; + + // CPU time based killing threshold + private final long _cpuTimeBasedKillingThresholdNS = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CPU_TIME_BASED_KILLING_THRESHOLD_MS, + CommonConstants.Accounting.DEFAULT_CPU_TIME_BASED_KILLING_THRESHOLD_MS) * 1000_000L; + + // + private final boolean _isQueryKilledMetricEnabled = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_QUERY_KILLED_METRIC_ENABLED, + CommonConstants.Accounting.DEFAULT_QUERY_KILLED_METRIC_ENABLED); + + private final InstanceType _instanceType = + InstanceType.valueOf(_config.getProperty(CommonConstants.Accounting.CONFIG_OF_INSTANCE_TYPE, + CommonConstants.Accounting.DEFAULT_CONFIG_OF_INSTANCE_TYPE.toString())); + private long _usedBytes; private int _sleepTime; private int _numQueriesKilledConsecutively = 0; - private Map _aggregatedUsagePerActiveQuery; + protected Map _aggregatedUsagePerActiveQuery; private TriggeringLevel _triggeringLevel; + // metrics class + private final AbstractMetrics _metrics; + private final AbstractMetrics.Meter _queryKilledMeter; + private final AbstractMetrics.Meter _heapMemoryCriticalExceededMeter; + private final AbstractMetrics.Meter _heapMemoryPanicExceededMeter; + private final AbstractMetrics.Gauge _memoryUsageGauge; + + WatcherTask() { + switch (_instanceType) { + case SERVER: + _metrics = ServerMetrics.get(); + _queryKilledMeter = ServerMeter.QUERIES_KILLED; + _memoryUsageGauge = ServerGauge.JVM_HEAP_USED_BYTES; + _heapMemoryCriticalExceededMeter = ServerMeter.HEAP_CRITICAL_LEVEL_EXCEEDED; + _heapMemoryPanicExceededMeter = ServerMeter.HEAP_PANIC_LEVEL_EXCEEDED; + break; + case BROKER: + _metrics = BrokerMetrics.get(); + _queryKilledMeter = BrokerMeter.QUERIES_KILLED; + _memoryUsageGauge = BrokerGauge.JVM_HEAP_USED_BYTES; + _heapMemoryCriticalExceededMeter = BrokerMeter.HEAP_CRITICAL_LEVEL_EXCEEDED; + _heapMemoryPanicExceededMeter = BrokerMeter.HEAP_PANIC_LEVEL_EXCEEDED; + break; + default: + LOGGER.error("instanceType: {} not supported, using server metrics", _instanceType); + _metrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); + _queryKilledMeter = ServerMeter.QUERIES_KILLED; + _memoryUsageGauge = ServerGauge.JVM_HEAP_USED_BYTES; + _heapMemoryCriticalExceededMeter = ServerMeter.HEAP_CRITICAL_LEVEL_EXCEEDED; + _heapMemoryPanicExceededMeter = ServerMeter.HEAP_PANIC_LEVEL_EXCEEDED; + break; + } + } + @Override public void run() { + // Log info for the accountant configs LOGGER.info("Starting accountant task for PerQueryCPUMemAccountant."); LOGGER.info("Xmx is {}", _maxHeapSize); + LOGGER.info("_instanceType is {}", _instanceType); LOGGER.info("_alarmingLevel of on heap memory is {}", _alarmingLevel); LOGGER.info("_criticalLevel of on heap memory is {}", _criticalLevel); + LOGGER.info("_criticalLevelAfterGC of on heap memory is {}", _criticalLevelAfterGC); + LOGGER.info("_panicLevel of on heap memory is {}", _panicLevel); + LOGGER.info("_gcBackoffCount is {}", _gcBackoffCount); + LOGGER.info("_gcWaitTime is {}", _gcWaitTime); + LOGGER.info("_normalSleepTime is {}", _normalSleepTime); + LOGGER.info("_alarmingSleepTime is {}", _alarmingSleepTime); + LOGGER.info("_oomKillQueryEnabled: {}", _oomKillQueryEnabled); + LOGGER.info("_minMemoryFootprintForKill: {}", _minMemoryFootprintForKill); + LOGGER.info("_isCPUTimeBasedKillingEnabled: {}, _cpuTimeBasedKillingThresholdNS: {}", + _isCPUTimeBasedKillingEnabled, _cpuTimeBasedKillingThresholdNS); + while (true) { LOGGER.debug("Running timed task for PerQueryCPUMemAccountant."); _triggeringLevel = TriggeringLevel.Normal; @@ -516,17 +628,20 @@ public void run() { evalTriggers(); // Refresh thread usage and aggregate to per query usage if triggered _aggregatedUsagePerActiveQuery = aggregate(_triggeringLevel.ordinal() > TriggeringLevel.Normal.ordinal()); + // post aggregation function + postAggregation(_aggregatedUsagePerActiveQuery); // Act on one triggered actions triggeredActions(); } catch (Exception e) { LOGGER.error("Caught exception while executing stats aggregation and query kill", e); } finally { - if (_aggregatedUsagePerActiveQuery != null) { - LOGGER.debug(_aggregatedUsagePerActiveQuery.toString()); - } + LOGGER.debug(_aggregatedUsagePerActiveQuery == null ? "_aggregatedUsagePerActiveQuery : null" + : _aggregatedUsagePerActiveQuery.toString()); + LOGGER.debug("_threadEntriesMap size: {}", _threadEntriesMap.size()); + // Publish server heap usage metrics if (_publishHeapUsageMetric) { - ServerMetrics.get().setValueOfGlobalGauge(ServerGauge.JVM_HEAP_USED_BYTES, _usedBytes); + _metrics.setValueOfGlobalGauge(_memoryUsageGauge, _usedBytes); } // Clean inactive query stats cleanInactive(); @@ -551,6 +666,7 @@ private boolean outOfMemoryPanicTrigger() { if (_usedBytes >= _panicLevel) { killAllQueries(); _triggeringLevel = TriggeringLevel.HeapMemoryPanic; + _metrics.addMeteredGlobalValue(_heapMemoryPanicExceededMeter, 1); LOGGER.error("Heap used bytes {}, greater than _panicLevel {}, Killed all queries and triggered gc!", _usedBytes, _panicLevel); // call aggregate here as will throw exception and @@ -565,11 +681,18 @@ private boolean outOfMemoryPanicTrigger() { * Triggers should be mutually exclusive and evaluated following level high -> low */ private void evalTriggers() { + if (_isCPUTimeBasedKillingEnabled) { + _triggeringLevel = TriggeringLevel.CPUTimeBasedKilling; + } + if (_usedBytes > _criticalLevel) { _triggeringLevel = TriggeringLevel.HeapMemoryCritical; + _metrics.addMeteredGlobalValue(_heapMemoryCriticalExceededMeter, 1); } else if (_usedBytes > _alarmingLevel) { - _triggeringLevel = LOGGER.isDebugEnabled() ? TriggeringLevel.HeapMemoryAlarmingVerbose : _triggeringLevel; _sleepTime = _alarmingSleepTime; + // For debugging + _triggeringLevel = (IS_DEBUG_MODE_ENABLED && _triggeringLevel == TriggeringLevel.Normal) + ? TriggeringLevel.HeapMemoryAlarmingVerbose : _triggeringLevel; } } @@ -579,9 +702,12 @@ private void evalTriggers() { private void triggeredActions() { switch (_triggeringLevel) { case HeapMemoryCritical: - LOGGER.debug("Heap used bytes {} exceeds critical level", _usedBytes); + LOGGER.warn("Heap used bytes {} exceeds critical level {}", _usedBytes, _criticalLevel); killMostExpensiveQuery(); break; + case CPUTimeBasedKilling: + killCPUTimeExceedQueries(); + break; case HeapMemoryAlarmingVerbose: LOGGER.warn("Heap used bytes {} exceeds alarming level", _usedBytes); LOGGER.warn("Query usage aggregation results {}", _aggregatedUsagePerActiveQuery.toString()); @@ -603,16 +729,19 @@ void reschedule() { void killAllQueries() { if (_oomKillQueryEnabled) { int killedCount = 0; - for (int i = 0; i < _numThreads; i++) { - CPUMemThreadLevelAccountingObjects.TaskEntry - taskEntry = _taskStatus[i].getThreadTaskStatus(); + for (Map.Entry entry : _threadEntriesMap.entrySet()) { + CPUMemThreadLevelAccountingObjects.ThreadEntry threadEntry = entry.getValue(); + CPUMemThreadLevelAccountingObjects.TaskEntry taskEntry = threadEntry.getCurrentThreadTaskStatus(); if (taskEntry != null && taskEntry.isAnchorThread()) { - _errorStatus.get(i).set(new RuntimeException("Query killed due to server out of memory!")); + threadEntry._errorStatus + .set(new RuntimeException(String.format("Query killed due to %s out of memory!", _instanceType))); taskEntry.getAnchorThread().interrupt(); killedCount += 1; } } - ServerMetrics.get().addMeteredGlobalValue(ServerMeter.QUERIES_PREEMPTED, killedCount); + if (_isQueryKilledMetricEnabled) { + _metrics.addMeteredGlobalValue(_queryKilledMeter, killedCount); + } try { Thread.sleep(_normalSleepTime); } catch (InterruptedException ignored) { @@ -629,26 +758,27 @@ void killAllQueries() { * use XX:+ExplicitGCInvokesConcurrent to avoid a full gc when system.gc is triggered */ private void killMostExpensiveQuery() { - if (!_aggregatedUsagePerActiveQuery.isEmpty() && _numQueriesKilledConsecutively >= _gcTriggerCount) { - System.gc(); + if (!_aggregatedUsagePerActiveQuery.isEmpty() && _numQueriesKilledConsecutively >= _gcBackoffCount) { _numQueriesKilledConsecutively = 0; + System.gc(); try { - Thread.sleep(_normalSleepTime); + Thread.sleep(_gcWaitTime); } catch (InterruptedException ignored) { } _usedBytes = MEMORY_MX_BEAN.getHeapMemoryUsage().getUsed(); - if (_usedBytes < _criticalLevel) { + if (_usedBytes < _criticalLevelAfterGC) { return; } + LOGGER.error("After GC, heap used bytes {} still exceeds _criticalLevelAfterGC level {}", + _usedBytes, _criticalLevelAfterGC); } if (!(_isThreadMemorySamplingEnabled || _isThreadCPUSamplingEnabled)) { - LOGGER.warn("Heap used bytes {} exceeds critical level", _usedBytes); LOGGER.warn("But unable to kill query because neither memory nor cpu tracking is enabled"); return; } // Critical heap memory usage while no queries running if (_aggregatedUsagePerActiveQuery.isEmpty()) { - LOGGER.debug("Heap used bytes {} exceeds critical level, but no active queries", _usedBytes); + LOGGER.debug("No active queries to kill"); return; } AggregatedStats maxUsageTuple; @@ -657,33 +787,63 @@ private void killMostExpensiveQuery() { Comparator.comparing(AggregatedStats::getAllocatedBytes)); boolean shouldKill = _oomKillQueryEnabled && maxUsageTuple._allocatedBytes > _minMemoryFootprintForKill; if (shouldKill) { - _errorStatus.get(maxUsageTuple._threadId) - .set(new RuntimeException(String.format(" Query %s got killed because using %d bytes of memory, " - + "exceeding the quota", maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes()))); - interruptRunnerThread(maxUsageTuple.getThread()); + maxUsageTuple._exceptionAtomicReference + .set(new RuntimeException(String.format( + " Query %s got killed because using %d bytes of memory on %s: %s, exceeding the quota", + maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes(), _instanceType, _instanceId))); + interruptRunnerThread(maxUsageTuple.getAnchorThread()); + LOGGER.error("Query {} got picked because using {} bytes of memory, actual kill committed true}", + maxUsageTuple._queryId, maxUsageTuple._allocatedBytes); + LOGGER.error("Current task status recorded is {}", _threadEntriesMap); + } else if (!_oomKillQueryEnabled) { + LOGGER.warn("Query {} got picked because using {} bytes of memory, actual kill committed false " + + "because oomKillQueryEnabled is false", + maxUsageTuple._queryId, maxUsageTuple._allocatedBytes); + } else { + LOGGER.warn("But all queries are below quota, no query killed"); } - LOGGER.error("Heap used bytes {} exceeds critical level {}", _usedBytes, _criticalLevel); - LOGGER.error("Query {} got picked because using {} bytes of memory, actual kill committed {}", - maxUsageTuple._queryId, maxUsageTuple._allocatedBytes, shouldKill); } else { maxUsageTuple = Collections.max(_aggregatedUsagePerActiveQuery.values(), Comparator.comparing(AggregatedStats::getCpuNS)); if (_oomKillQueryEnabled) { - _errorStatus.get(maxUsageTuple._threadId) - .set(new RuntimeException(String.format(" Query %s got killed because server memory pressure, using " - + "%d ns of CPU time", maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes()))); - interruptRunnerThread(maxUsageTuple.getThread()); + maxUsageTuple._exceptionAtomicReference + .set(new RuntimeException(String.format( + " Query %s got killed because memory pressure, using %d ns of CPU time on %s: %s", + maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes(), _instanceType, _instanceId))); + interruptRunnerThread(maxUsageTuple.getAnchorThread()); + LOGGER.error("Query {} got picked because using {} ns of cpu time, actual kill committed true", + maxUsageTuple._allocatedBytes, maxUsageTuple._queryId); + LOGGER.error("Current task status recorded is {}", _threadEntriesMap); + } else { + LOGGER.warn("Query {} got picked because using {} bytes of memory, actual kill committed false " + + "because oomKillQueryEnabled is false", + maxUsageTuple._queryId, maxUsageTuple._allocatedBytes); + } + } + LOGGER.warn("Query aggregation results {} for the previous kill.", _aggregatedUsagePerActiveQuery.toString()); + } + + private void killCPUTimeExceedQueries() { + for (Map.Entry entry : _aggregatedUsagePerActiveQuery.entrySet()) { + AggregatedStats value = entry.getValue(); + if (value._cpuNS > _cpuTimeBasedKillingThresholdNS) { + LOGGER.error("Query {} got picked because using {} ns of cpu time, greater than threshold {}", + value._queryId, value.getCpuNS(), _cpuTimeBasedKillingThresholdNS); + value._exceptionAtomicReference.set(new RuntimeException( + String.format("Query %s got killed on %s: %s because using %d " + + "CPU time exceeding limit of %d ns CPU time", + value._queryId, _instanceType, _instanceId, value.getCpuNS(), _cpuTimeBasedKillingThresholdNS))); + interruptRunnerThread(value.getAnchorThread()); } - LOGGER.error("Heap used bytes {} exceeds critical level {}", _usedBytes, _criticalLevel); - LOGGER.error("Query {} got picked because using {} ns of cpu time, actual kill committed {}", - maxUsageTuple._allocatedBytes, maxUsageTuple._queryId, _oomKillQueryEnabled); } - LOGGER.error("Query aggregation results {} for the previous kill.", _aggregatedUsagePerActiveQuery.toString()); + LOGGER.error("Current task status recorded is {}", _threadEntriesMap); } private void interruptRunnerThread(Thread thread) { thread.interrupt(); - ServerMetrics.get().addMeteredGlobalValue(ServerMeter.QUERIES_PREEMPTED, 1); + if (_isQueryKilledMetricEnabled) { + _metrics.addMeteredGlobalValue(_queryKilledMeter, 1); + } _numQueriesKilledConsecutively += 1; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactoryForTest.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactoryForTest.java new file mode 100644 index 00000000000..3e6c9004a88 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactoryForTest.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.accounting; + +import java.util.Map; +import org.apache.pinot.core.query.utils.QueryIdUtils; +import org.apache.pinot.spi.accounting.ThreadAccountantFactory; +import org.apache.pinot.spi.accounting.ThreadResourceUsageAccountant; +import org.apache.pinot.spi.env.PinotConfiguration; + + +/** + * For broker integration test, remove tracking for severs + */ +public class PerQueryCPUMemAccountantFactoryForTest implements ThreadAccountantFactory { + @Override + public ThreadResourceUsageAccountant init(PinotConfiguration config, String instanceId) { + return new PerQueryCPUMemResourceUsageAccountantBrokerKillingTest(config, instanceId); + } + + public static class PerQueryCPUMemResourceUsageAccountantBrokerKillingTest + extends PerQueryCPUMemAccountantFactory.PerQueryCPUMemResourceUsageAccountant { + public PerQueryCPUMemResourceUsageAccountantBrokerKillingTest(PinotConfiguration config, String instanceId) { + super(config, instanceId); + } + + public void postAggregation(Map aggregatedUsagePerActiveQuery) { + if (aggregatedUsagePerActiveQuery != null) { + aggregatedUsagePerActiveQuery.entrySet().removeIf(item -> item.getKey().endsWith(QueryIdUtils.OFFLINE_SUFFIX)); + } + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/utils/RunnerWorkerThreadOffsetProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/utils/RunnerWorkerThreadOffsetProvider.java deleted file mode 100644 index 3fe00a58dd8..00000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/utils/RunnerWorkerThreadOffsetProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.accounting.utils; - -import com.google.common.annotations.VisibleForTesting; -import java.util.concurrent.atomic.AtomicInteger; - - -/** - * map each thread to a unique id, starting from zero, used as its offset to access - * cputime/memory/job status, etc - */ -public class RunnerWorkerThreadOffsetProvider { - - // Thread local variable containing each thread's ID - private final AtomicInteger _atomicInteger = new AtomicInteger(0); - private final ThreadLocal _threadId = ThreadLocal.withInitial(_atomicInteger::getAndIncrement); - - public RunnerWorkerThreadOffsetProvider() { - } - - @VisibleForTesting - public void reset() { - _atomicInteger.set(0); - } - - // TODO: make this not dependent on numRunnerThreads - /** - * Returns the current thread's unique ID, assigning it if necessary - */ - public int get() { - return _threadId.get(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java b/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java index 03397a59481..2ee6585ecf4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java @@ -19,41 +19,8 @@ package org.apache.pinot.core.common; /** - * - * A block represents a set of rows.A segment will contain one or more blocks - * Currently, it assumes only one column per block. We might change this in - * future + * A {@code Block} represents the data block returned by the {@link Operator}. Each operator can return multiple blocks. + * It can contain document ids, column values or result rows depending on the operator. */ public interface Block { - - /** - * Returns valset that allows one to iterate over the docId. If no predicate - * is provided, this will consists of all docIds within the block - * - * @return {@link BlockDocIdSet} - */ - - BlockDocIdSet getBlockDocIdSet(); - - /** - * Returns valset that allows one to iterate over the values - * - * @return {@link BlockValSet} - */ - BlockValSet getBlockValueSet(); - - /** - * Allows one to iterate over the DocId And Value in parallel - * - * @return - */ - BlockDocIdValueSet getBlockDocIdValueSet(); - - /** - * For future optimizations. The metadata can consists of bloom filter, - * min/max, sum, count etc that can be used in filtering, aggregation - * - * @return - */ - BlockMetadata getMetadata(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java index bedff54d833..dec522db9a6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java @@ -18,9 +18,21 @@ */ package org.apache.pinot.core.common; +import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.operator.dociditerators.AndDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.OrDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; +import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; +import org.apache.pinot.core.operator.docidsets.RangelessBitmapDocIdSet; +import org.apache.pinot.segment.spi.Constants; +import org.roaringbitmap.RoaringBitmapWriter; +import org.roaringbitmap.buffer.MutableRoaringBitmap; + + /** - * The interface {@code BlockDocIdSet} represents all the matching document ids for a predicate. - * TODO: Redesign the filtering to skip the BlockDocIdSet and directly use BlockDocIdIterator + * The {@code BlockDocIdSet} contains the matching document ids returned by the {@link FilterBlock}. */ public interface BlockDocIdSet { @@ -29,4 +41,41 @@ public interface BlockDocIdSet { * ascending order. */ BlockDocIdIterator iterator(); + + /** + * Returns the number of entries (SV value contains one entry, MV value contains multiple entries) scanned in the + * filtering phase. This method should be called after the filtering is done. + */ + long getNumEntriesScannedInFilter(); + + /** + * For scan-based FilterBlockDocIdSet, pre-scans the documents and returns a non-scan-based FilterBlockDocIdSet. + */ + default BlockDocIdSet toNonScanDocIdSet() { + BlockDocIdIterator docIdIterator = iterator(); + + // NOTE: AND and OR DocIdIterator might contain scan-based DocIdIterator + // TODO: This scan is not counted in the execution stats + if (docIdIterator instanceof ScanBasedDocIdIterator || docIdIterator instanceof AndDocIdIterator + || docIdIterator instanceof OrDocIdIterator) { + RoaringBitmapWriter bitmapWriter = + RoaringBitmapWriter.bufferWriter().runCompress(false).get(); + int docId; + while ((docId = docIdIterator.next()) != Constants.EOF) { + bitmapWriter.add(docId); + } + return new RangelessBitmapDocIdSet(bitmapWriter.get()); + } + + // NOTE: AND and OR DocIdSet might return BitmapBasedDocIdIterator after processing the iterators. Create a new + // DocIdSet to prevent processing the iterators again + if (docIdIterator instanceof RangelessBitmapDocIdIterator) { + return new RangelessBitmapDocIdSet((RangelessBitmapDocIdIterator) docIdIterator); + } + if (docIdIterator instanceof BitmapDocIdIterator) { + return new BitmapDocIdSet((BitmapDocIdIterator) docIdIterator); + } + + return this; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/IntermediateStageBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/common/IntermediateStageBlockValSet.java new file mode 100644 index 00000000000..7ddaaf04c93 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/IntermediateStageBlockValSet.java @@ -0,0 +1,155 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.common; + +import java.math.BigDecimal; +import javax.annotation.Nullable; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datablock.DataBlockUtils; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + +/** + * In the multistage engine, the leaf stage servers process the data in columnar fashion. By the time the + * intermediate stage receives the projected column, they are converted to a row based format. This class provides + * the capability to convert the row based representation into columnar blocks so that they can be used to process + * aggregations using v1 aggregation functions. + * TODO: Support MV + */ +public class IntermediateStageBlockValSet implements BlockValSet { + private final FieldSpec.DataType _dataType; + private final DataBlock _dataBlock; + private final int _index; + private final RoaringBitmap _nullBitMap; + + public IntermediateStageBlockValSet(DataSchema.ColumnDataType columnDataType, DataBlock dataBlock, int colIndex) { + _dataType = columnDataType.toDataType(); + _dataBlock = dataBlock; + _index = colIndex; + _nullBitMap = dataBlock.getNullRowIds(colIndex); + } + + /** + * Returns a bitmap of indices where null values are found. + */ + @Nullable + @Override + public RoaringBitmap getNullBitmap() { + return _nullBitMap; + } + + @Override + public FieldSpec.DataType getValueType() { + return _dataType; + } + + @Override + public boolean isSingleValue() { + // TODO: Needs to be changed when we start supporting MV in multistage + return true; + } + + @Nullable + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public int[] getDictionaryIdsSV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getIntValuesSV() { + return DataBlockUtils.extractIntValuesForColumn(_dataBlock, _index); + } + + @Override + public long[] getLongValuesSV() { + return DataBlockUtils.extractLongValuesForColumn(_dataBlock, _index); + } + + @Override + public float[] getFloatValuesSV() { + return DataBlockUtils.extractFloatValuesForColumn(_dataBlock, _index); + } + + @Override + public double[] getDoubleValuesSV() { + return DataBlockUtils.extractDoubleValuesForColumn(_dataBlock, _index); + } + + @Override + public BigDecimal[] getBigDecimalValuesSV() { + return DataBlockUtils.extractBigDecimalValuesForColumn(_dataBlock, _index); + } + + @Override + public String[] getStringValuesSV() { + return DataBlockUtils.extractStringValuesForColumn(_dataBlock, _index); + } + + @Override + public byte[][] getBytesValuesSV() { + return DataBlockUtils.extractBytesValuesForColumn(_dataBlock, _index); + } + + @Override + public int[][] getDictionaryIdsMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] getIntValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public long[][] getLongValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public float[][] getFloatValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public double[][] getDoubleValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public String[][] getStringValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[][][] getBytesValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getNumMVEntries() { + throw new UnsupportedOperationException(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java b/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java index 86bfa4829db..25c5427cf63 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java @@ -91,6 +91,9 @@ public static abstract class MergeTask { // Merge config public static final String MERGE_TYPE_KEY = "mergeType"; public static final String AGGREGATION_TYPE_KEY_SUFFIX = ".aggregationType"; + public static final String MODE = "mode"; + public static final String PROCESS_FROM_WATERMARK_MODE = "processFromWatermark"; + public static final String PROCESS_ALL_MODE = "processAll"; // Segment config public static final String MAX_NUM_RECORDS_PER_TASK_KEY = "maxNumRecordsPerTask"; @@ -115,6 +118,9 @@ public static class MergeRollupTask extends MergeTask { public static final String SEGMENT_ZK_METADATA_TIME_KEY = TASK_TYPE + TASK_TIME_SUFFIX; public static final String MERGED_SEGMENT_NAME_PREFIX = "merged_"; + + // Custom segment group manager class name + public static final String SEGMENT_GROUP_MANAGER_CLASS_NAME_KEY = "segment.group.manager.class.name"; } /** @@ -133,4 +139,23 @@ public static class SegmentGenerationAndPushTask { public static final String CONFIG_NUMBER_CONCURRENT_TASKS_PER_INSTANCE = "SegmentGenerationAndPushTask.numConcurrentTasksPerInstance"; } + + public static class UpsertCompactionTask { + public static final String TASK_TYPE = "UpsertCompactionTask"; + /** + * The time period to wait before picking segments for this task + * e.g. if set to "2d", no task will be scheduled for a time window younger than 2 days + */ + public static final String BUFFER_TIME_PERIOD_KEY = "bufferTimePeriod"; + /** + * The maximum percent of old records allowed for a completed segment. + * e.g. if the percent surpasses 30, then the segment may be compacted + */ + public static final String INVALID_RECORDS_THRESHOLD_PERCENT = "invalidRecordsThresholdPercent"; + /** + * The maximum count of old records for a completed segment + * e.g. if the count surpasses 100k, then the segment may be compacted + */ + public static final String INVALID_RECORDS_THRESHOLD_COUNT = "invalidRecordsThresholdCount"; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java index a01f02a5c90..d93ea17ee94 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java @@ -57,9 +57,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.datasketches.kll.KllDoublesSketch; import org.apache.datasketches.memory.Memory; import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.datasketches.tuple.aninteger.IntegerSummaryDeserializer; import org.apache.pinot.common.CustomObject; +import org.apache.pinot.core.query.aggregation.utils.argminmax.ArgMinMaxObject; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.utils.idset.IdSet; import org.apache.pinot.core.query.utils.idset.IdSets; @@ -127,7 +131,10 @@ public enum ObjectType { StringLongPair(31), CovarianceTuple(32), VarianceTuple(33), - PinotFourthMoment(34); + PinotFourthMoment(34), + ArgMinMaxObject(35), + KllDataSketch(36), + IntegerTupleSketch(37); private final int _value; @@ -176,6 +183,8 @@ public static ObjectType getObjectType(Object value) { return ObjectType.DistinctTable; } else if (value instanceof Sketch) { return ObjectType.DataSketch; + } else if (value instanceof KllDoublesSketch) { + return ObjectType.KllDataSketch; } else if (value instanceof Geometry) { return ObjectType.Geometry; } else if (value instanceof RoaringBitmap) { @@ -213,6 +222,10 @@ public static ObjectType getObjectType(Object value) { return ObjectType.VarianceTuple; } else if (value instanceof PinotFourthMoment) { return ObjectType.PinotFourthMoment; + } else if (value instanceof org.apache.datasketches.tuple.Sketch) { + return ObjectType.IntegerTupleSketch; + } else if (value instanceof ArgMinMaxObject) { + return ObjectType.ArgMinMaxObject; } else { throw new IllegalArgumentException("Unsupported type of value: " + value.getClass().getSimpleName()); } @@ -918,6 +931,48 @@ public Sketch deserialize(ByteBuffer byteBuffer) { } }; + public static final ObjectSerDe> DATA_SKETCH_INT_TUPLE_SER_DE = + new ObjectSerDe>() { + @Override + public byte[] serialize(org.apache.datasketches.tuple.Sketch value) { + return value.compact().toByteArray(); + } + + @Override + public org.apache.datasketches.tuple.Sketch deserialize(byte[] bytes) { + return org.apache.datasketches.tuple.Sketches.heapifySketch(Memory.wrap(bytes), + new IntegerSummaryDeserializer()); + } + + @Override + public org.apache.datasketches.tuple.Sketch deserialize(ByteBuffer byteBuffer) { + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return org.apache.datasketches.tuple.Sketches.heapifySketch(Memory.wrap(bytes), + new IntegerSummaryDeserializer()); + } + }; + + public static final ObjectSerDe KLL_SKETCH_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(KllDoublesSketch value) { + return value.toByteArray(); + } + + @Override + public KllDoublesSketch deserialize(byte[] bytes) { + return KllDoublesSketch.wrap(Memory.wrap(bytes)); + } + + @Override + public KllDoublesSketch deserialize(ByteBuffer byteBuffer) { + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return KllDoublesSketch.wrap(Memory.wrap(bytes)); + } + }; + public static final ObjectSerDe GEOMETRY_SER_DE = new ObjectSerDe() { @Override @@ -1199,6 +1254,37 @@ public Double2LongOpenHashMap deserialize(ByteBuffer byteBuffer) { } }; + public static final ObjectSerDe ARG_MIN_MAX_OBJECT_SER_DE = + new ObjectSerDe() { + + @Override + public byte[] serialize(ArgMinMaxObject value) { + try { + return value.toBytes(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public ArgMinMaxObject deserialize(byte[] bytes) { + try { + return ArgMinMaxObject.fromBytes(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public ArgMinMaxObject deserialize(ByteBuffer byteBuffer) { + try { + return ArgMinMaxObject.fromByteBuffer(byteBuffer); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + // NOTE: DO NOT change the order, it has to be the same order as the ObjectType //@formatter:off private static final ObjectSerDe[] SER_DES = { @@ -1236,7 +1322,10 @@ public Double2LongOpenHashMap deserialize(ByteBuffer byteBuffer) { STRING_LONG_PAIR_SER_DE, COVARIANCE_TUPLE_OBJECT_SER_DE, VARIANCE_TUPLE_OBJECT_SER_DE, - PINOT_FOURTH_MOMENT_OBJECT_SER_DE + PINOT_FOURTH_MOMENT_OBJECT_SER_DE, + ARG_MIN_MAX_OBJECT_SER_DE, + KLL_SKETCH_SER_DE, + DATA_SKETCH_INT_TUPLE_SER_DE, }; //@formatter:on diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java b/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java index f86caf9798e..e8b812306d8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java @@ -48,6 +48,26 @@ public interface Operator { @Nullable String toExplainString(); + default void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + } + + default void explainPlan(ExplainPlanRows explainPlanRows, int[] globalId, int parentId) { + prepareForExplainPlan(explainPlanRows); + String explainPlanString = toExplainString(); + if (explainPlanString != null) { + ExplainPlanRowData explainPlanRowData = new ExplainPlanRowData(explainPlanString, globalId[0], parentId); + parentId = globalId[0]++; + explainPlanRows.appendExplainPlanRowData(explainPlanRowData); + } + + List children = getChildOperators(); + for (Operator child : children) { + if (child != null) { + child.explainPlan(explainPlanRows, globalId, parentId); + } + } + } + /** * Returns the index segment associated with the operator. */ diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java b/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java index 64a3f6a788d..21c6d34f049 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java @@ -67,6 +67,8 @@ private ValueFetcher createFetcher(BlockValSet blockValSet) { return new StringSingleValueFetcher(blockValSet.getStringValuesSV()); case BYTES: return new BytesValueFetcher(blockValSet.getBytesValuesSV()); + case UNKNOWN: + return new UnknownValueFetcher(); default: throw new IllegalStateException("Unsupported value type: " + storedType + " for single-value column"); } @@ -235,4 +237,13 @@ public String[] getValue(int docId) { return _values[docId]; } } + + private static class UnknownValueFetcher implements ValueFetcher { + UnknownValueFetcher() { + } + + public Object getValue(int docId) { + return null; + } + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java b/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java index 573b3beadf7..8b486b40121 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java @@ -20,7 +20,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.math.BigDecimal; @@ -28,6 +27,7 @@ import java.sql.Timestamp; import java.util.List; import javax.annotation.Nullable; +import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.pinot.common.CustomObject; import org.apache.pinot.common.datablock.ColumnarDataBlock; import org.apache.pinot.common.datablock.DataBlock; @@ -56,29 +56,40 @@ public class DataBlockBuilder { private int _numColumns; private final Object2IntOpenHashMap _dictionary = new Object2IntOpenHashMap<>(); - private final ByteArrayOutputStream _fixedSizeDataByteArrayOutputStream = new ByteArrayOutputStream(); - private final DataOutputStream _fixedSizeDataOutputStream = new DataOutputStream(_fixedSizeDataByteArrayOutputStream); - private final ByteArrayOutputStream _variableSizeDataByteArrayOutputStream = new ByteArrayOutputStream(); + private final UnsynchronizedByteArrayOutputStream _fixedSizeDataByteArrayOutputStream; + private final DataOutputStream _fixedSizeDataOutputStream; + private final UnsynchronizedByteArrayOutputStream _variableSizeDataByteArrayOutputStream + = new UnsynchronizedByteArrayOutputStream(8192); private final DataOutputStream _variableSizeDataOutputStream = new DataOutputStream(_variableSizeDataByteArrayOutputStream); - private DataBlockBuilder(DataSchema dataSchema, DataBlock.Type blockType) { + private DataBlockBuilder(DataSchema dataSchema, DataBlock.Type blockType, int numRows) { _dataSchema = dataSchema; _columnDataTypes = dataSchema.getColumnDataTypes(); _blockType = blockType; _numColumns = dataSchema.size(); - if (_blockType == DataBlock.Type.COLUMNAR) { - _cumulativeColumnOffsetSizeInBytes = new int[_numColumns]; - _columnSizeInBytes = new int[_numColumns]; - DataBlockUtils.computeColumnSizeInBytes(_dataSchema, _columnSizeInBytes); - int cumulativeColumnOffset = 0; - for (int i = 0; i < _numColumns; i++) { - _cumulativeColumnOffsetSizeInBytes[i] = cumulativeColumnOffset; - cumulativeColumnOffset += _columnSizeInBytes[i] * _numRows; - } - } else if (_blockType == DataBlock.Type.ROW) { + if (_blockType == DataBlock.Type.ROW) { _columnOffsets = new int[_numColumns]; _rowSizeInBytes = DataBlockUtils.computeColumnOffsets(dataSchema, _columnOffsets); + + int nullBytes = _numColumns * 8; // we need 2 ints per column to store the roaring bitmaps offsets + int expectedFixedSizeStreamSize = (_rowSizeInBytes + nullBytes) * numRows; + _fixedSizeDataByteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(expectedFixedSizeStreamSize); + _fixedSizeDataOutputStream = new DataOutputStream(_fixedSizeDataByteArrayOutputStream); + } else { + _fixedSizeDataByteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(8192); + _fixedSizeDataOutputStream = new DataOutputStream(_fixedSizeDataByteArrayOutputStream); + + if (_blockType == DataBlock.Type.COLUMNAR) { + _cumulativeColumnOffsetSizeInBytes = new int[_numColumns]; + _columnSizeInBytes = new int[_numColumns]; + DataBlockUtils.computeColumnSizeInBytes(_dataSchema, _columnSizeInBytes); + int cumulativeColumnOffset = 0; + for (int i = 0; i < _numColumns; i++) { + _cumulativeColumnOffsetSizeInBytes[i] = cumulativeColumnOffset; + cumulativeColumnOffset += _columnSizeInBytes[i] * _numRows; + } + } } } @@ -96,7 +107,7 @@ public void setNullRowIds(@Nullable RoaringBitmap nullRowIds) public static RowDataBlock buildFromRows(List rows, DataSchema dataSchema) throws IOException { - DataBlockBuilder rowBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.ROW); + DataBlockBuilder rowBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.ROW, rows.size()); // TODO: consolidate these null utils into data table utils. // Selection / Agg / Distinct all have similar code. int numColumns = rowBuilder._numColumns; @@ -109,9 +120,10 @@ public static RowDataBlock buildFromRows(List rows, DataSchema dataSch nullPlaceholders[colId] = columnDataTypes[colId].convert(storedColumnDataTypes[colId].getNullPlaceholder()); } rowBuilder._numRows = rows.size(); + ByteBuffer byteBuffer = ByteBuffer.allocate(rowBuilder._rowSizeInBytes); for (int rowId = 0; rowId < rows.size(); rowId++) { + byteBuffer.clear(); Object[] row = rows.get(rowId); - ByteBuffer byteBuffer = ByteBuffer.allocate(rowBuilder._rowSizeInBytes); for (int colId = 0; colId < rowBuilder._numColumns; colId++) { Object value = row[colId]; if (value == null) { @@ -217,6 +229,9 @@ public static RowDataBlock buildFromRows(List rows, DataSchema dataSch ArrayCopyUtils.copy(timestamps, longs, length); setColumn(rowBuilder, byteBuffer, longs); break; + case UNKNOWN: + setColumn(rowBuilder, byteBuffer, (Object) null); + break; default: throw new IllegalStateException( String.format("Unsupported data type: %s for column: %s", rowBuilder._columnDataTypes[colId], @@ -234,7 +249,8 @@ public static RowDataBlock buildFromRows(List rows, DataSchema dataSch public static ColumnarDataBlock buildFromColumns(List columns, DataSchema dataSchema) throws IOException { - DataBlockBuilder columnarBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.COLUMNAR); + int numRows = columns.isEmpty() ? 0 : columns.get(0).length; + DataBlockBuilder columnarBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.COLUMNAR, numRows); // TODO: consolidate these null utils into data table utils. // Selection / Agg / Distinct all have similar code. @@ -462,6 +478,11 @@ public static ColumnarDataBlock buildFromColumns(List columns, DataSch setColumn(columnarBuilder, byteBuffer, (String[]) value); } break; + case UNKNOWN: + for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + setColumn(columnarBuilder, byteBuffer, (Object) null); + } + break; default: throw new IllegalStateException( String.format("Unsupported data type: %s for column: %s", columnarBuilder._columnDataTypes[colId], diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java index 2bf0426a787..967233b2ee5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java @@ -34,7 +34,7 @@ private DataTableBuilderFactory() { private static final Logger LOGGER = LoggerFactory.getLogger(DataTableBuilderFactory.class); - public static final int DEFAULT_VERSION = DataTableFactory.VERSION_3; + public static final int DEFAULT_VERSION = DataTableFactory.VERSION_4; private static int _version = DEFAULT_VERSION; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java index ff5a7693f64..04059f94c1b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java @@ -32,14 +32,17 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationConverter; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; @@ -97,6 +100,7 @@ public abstract class BaseTableDataManager implements TableDataManager { protected File _resourceTmpDir; protected Logger _logger; protected HelixManager _helixManager; + protected ExecutorService _segmentPreloadExecutor; protected AuthProvider _authProvider; protected long _streamSegmentDownloadUntarRateLimitBytesPerSec; protected boolean _isStreamSegmentDownloadUntar; @@ -107,9 +111,12 @@ public abstract class BaseTableDataManager implements TableDataManager { // Cache used for identifying segments which could not be acquired since they were recently deleted. protected Cache _recentlyDeletedSegments; + protected volatile boolean _shutDown; + @Override public void init(TableDataManagerConfig tableDataManagerConfig, String instanceId, ZkHelixPropertyStore propertyStore, ServerMetrics serverMetrics, HelixManager helixManager, + @Nullable ExecutorService segmentPreloadExecutor, @Nullable LoadingCache, SegmentErrorInfo> errorCache, TableDataManagerParams tableDataManagerParams) { LOGGER.info("Initializing table data manager for table: {}", tableDataManagerConfig.getTableName()); @@ -119,6 +126,7 @@ public void init(TableDataManagerConfig tableDataManagerConfig, String instanceI _propertyStore = propertyStore; _serverMetrics = serverMetrics; _helixManager = helixManager; + _segmentPreloadExecutor = segmentPreloadExecutor; _authProvider = AuthProviderUtils.extractAuthProvider(toPinotConfiguration(_tableDataManagerConfig.getAuthConfig()), null); @@ -182,12 +190,18 @@ public void start() { @Override public void shutDown() { _logger.info("Shutting down table data manager for table: {}", _tableNameWithType); + _shutDown = true; doShutdown(); _logger.info("Shut down table data manager for table: {}", _tableNameWithType); } protected abstract void doShutdown(); + @Override + public boolean isShutDown() { + return _shutDown; + } + /** * {@inheritDoc} *

    If one segment already exists with the same name, replaces it with the new one. @@ -355,14 +369,13 @@ public void reloadSegment(String segmentName, IndexLoadingConfig indexLoadingCon indexLoadingConfig.setInstanceTierConfigs(_tableDataManagerConfig.getInstanceTierConfigs()); File indexDir = getSegmentDataDir(segmentName, segmentTier, indexLoadingConfig.getTableConfig()); try { - // Create backup directory to handle failure of segment reloading. - createBackup(indexDir); - // Download segment from deep store if CRC changes or forced to download; // otherwise, copy backup directory back to the original index directory. // And then continue to load the segment from the index directory. boolean shouldDownload = forceDownload || !hasSameCRC(zkMetadata, localMetadata); if (shouldDownload && allowDownload(segmentName, zkMetadata)) { + // Create backup directory to handle failure of segment reloading. + createBackup(indexDir); if (forceDownload) { LOGGER.info("Segment: {} of table: {} is forced to download", segmentName, _tableNameWithType); } else { @@ -373,12 +386,27 @@ public void reloadSegment(String segmentName, IndexLoadingConfig indexLoadingCon } else { LOGGER.info("Reload existing segment: {} of table: {} on tier: {}", segmentName, _tableNameWithType, TierConfigUtils.normalizeTierName(segmentTier)); + SegmentDirectory segmentDirectory = + initSegmentDirectory(segmentName, String.valueOf(zkMetadata.getCrc()), indexLoadingConfig); + // We should first try to reuse existing segment directory + if (canReuseExistingDirectoryForReload(zkMetadata, segmentTier, segmentDirectory, indexLoadingConfig, + schema)) { + LOGGER.info("Reloading segment: {} of table: {} using existing segment directory as no reprocessing needed", + segmentName, _tableNameWithType); + // No reprocessing needed, reuse the same segment + ImmutableSegment segment = ImmutableSegmentLoader.load(segmentDirectory, indexLoadingConfig, schema); + addSegment(segment); + return; + } + // Create backup directory to handle failure of segment reloading. + createBackup(indexDir); // The indexDir is empty after calling createBackup, as it's renamed to a backup directory. // The SegmentDirectory should initialize accordingly. Like for SegmentLocalFSDirectory, it // doesn't load anything from an empty indexDir, but gets the info to complete the copyTo. - try (SegmentDirectory segmentDirectory = initSegmentDirectory(segmentName, String.valueOf(zkMetadata.getCrc()), - indexLoadingConfig)) { + try { segmentDirectory.copyTo(indexDir); + } finally { + segmentDirectory.close(); } } @@ -403,6 +431,16 @@ public void reloadSegment(String segmentName, IndexLoadingConfig indexLoadingCon } } + private boolean canReuseExistingDirectoryForReload(SegmentZKMetadata segmentZKMetadata, + String currentSegmentTier, SegmentDirectory segmentDirectory, IndexLoadingConfig indexLoadingConfig, + Schema schema) + throws Exception { + SegmentDirectoryLoader segmentDirectoryLoader = + SegmentDirectoryLoaderRegistry.getSegmentDirectoryLoader(indexLoadingConfig.getSegmentDirectoryLoader()); + return !segmentDirectoryLoader.needsTierMigration(segmentZKMetadata.getTier(), currentSegmentTier) + && !ImmutableSegmentLoader.needPreprocess(segmentDirectory, indexLoadingConfig, schema); + } + @Override public void addOrReplaceSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, SegmentZKMetadata zkMetadata, @Nullable SegmentMetadata localMetadata) @@ -595,14 +633,21 @@ private File downloadAndStreamUntarWithRateLimit(String segmentName, SegmentZKMe LOGGER.info("Trying to download segment {} using streamed download-untar with maxStreamRateInByte {}", segmentName, maxStreamRateInByte); String uri = zkMetadata.getDownloadUrl(); + AtomicInteger attempts = new AtomicInteger(0); try { - File ret = SegmentFetcherFactory.fetchAndStreamUntarToLocal(uri, tempRootDir, maxStreamRateInByte); - LOGGER.info("Download and untarred segment: {} for table: {} from: {}", segmentName, _tableNameWithType, uri); - return ret; - } catch (AttemptsExceededException e) { - LOGGER.error("Attempts exceeded when stream download-untarring segment: {} for table: {} from: {} to: {}", - segmentName, _tableNameWithType, uri, tempRootDir); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES, 1L); + File ret = SegmentFetcherFactory.fetchAndStreamUntarToLocal(uri, tempRootDir, maxStreamRateInByte, attempts); + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES, + attempts.get()); + LOGGER.info("Downloaded and untarred segment: {} for table: {} from: {} attempts: {}", segmentName, + _tableNameWithType, uri, attempts.get()); + return ret; + } catch (Exception e) { + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES, + attempts.get()); + if (e instanceof AttemptsExceededException) { + LOGGER.error("Attempts exceeded when stream download-untarring segment: {} for table: {} from: {} to: {}", + segmentName, _tableNameWithType, uri, tempRootDir); + } throw e; } finally { if (_segmentDownloadSemaphore != null) { @@ -640,21 +685,18 @@ File getSegmentDataDir(String segmentName) { return new File(_indexDir, segmentName); } - @VisibleForTesting - File getSegmentDataDir(String segmentName, @Nullable String segmentTier, TableConfig tableConfig) { + @Override + public File getSegmentDataDir(String segmentName, @Nullable String segmentTier, TableConfig tableConfig) { if (segmentTier == null) { return getSegmentDataDir(segmentName); } - try { - String tierDataDir = - TierConfigUtils.getDataDirForTier(tableConfig, segmentTier, _tableDataManagerConfig.getInstanceTierConfigs()); - File tierTableDataDir = new File(tierDataDir, _tableNameWithType); - return new File(tierTableDataDir, segmentName); - } catch (Exception e) { - LOGGER.warn("Failed to get dataDir for segment: {} of table: {} on tier: {} due to error: {}", segmentName, - _tableNameWithType, segmentTier, e.getMessage()); + String tierDataDir = + TierConfigUtils.getDataDirForTier(tableConfig, segmentTier, _tableDataManagerConfig.getInstanceTierConfigs()); + if (StringUtils.isEmpty(tierDataDir)) { return getSegmentDataDir(segmentName); } + File tierTableDataDir = new File(tierDataDir, _tableNameWithType); + return new File(tierTableDataDir, segmentName); } @Nullable @@ -725,7 +767,8 @@ private void removeBackup(File indexDir) * object may be created when trying to load the segment, but it's closed if the method * returns false; otherwise it's opened and to be referred by ImmutableSegment object. */ - protected boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, + @Override + public boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, SegmentZKMetadata zkMetadata) { // Try to recover the segment from potential segment reloading failure. String segmentTier = zkMetadata.getTier(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java index 0e041021d27..bbc392d4d0c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java @@ -46,6 +46,7 @@ public interface InstanceDataManager { /** * Initializes the data manager. *

    Should be called only once and before calling any other method. + *

    NOTE: The config is the subset of server config with prefix 'pinot.server.instance' */ void init(PinotConfiguration config, HelixManager helixManager, ServerMetrics serverMetrics) throws ConfigurationException; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java index 71c79b941b6..1646ba77b9b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java @@ -44,7 +44,7 @@ public ImmutableSegment getSegment() { } @Override - public void destroy() { + protected void doDestroy() { _immutableSegment.destroy(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java index 8f74015f018..ecaa4342b83 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java @@ -37,7 +37,7 @@ class MemoryOptimizedDimensionTable implements DimensionTable { private final Map _lookupTable; private final Schema _tableSchema; private final List _primaryKeyColumns; - private final GenericRow _reuse = new GenericRow(); + private final ThreadLocal _reuse = ThreadLocal.withInitial(GenericRow::new); private final List _segmentDataManagers; private final TableDataManager _tableDataManager; @@ -62,7 +62,9 @@ public GenericRow get(PrimaryKey pk) { if (lookupRecordLocation == null) { return null; } - return lookupRecordLocation.getRecord(_reuse); + GenericRow reuse = _reuse.get(); + reuse.clear(); + return lookupRecordLocation.getRecord(reuse); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java index 2a2671047a5..946ecf1565e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java @@ -19,8 +19,12 @@ package org.apache.pinot.core.data.manager.offline; import com.google.common.cache.LoadingCache; +import java.util.Map; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; @@ -32,6 +36,9 @@ import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; +import org.apache.pinot.spi.stream.StreamConfigProperties; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.IngestionConfigUtils; /** @@ -55,13 +62,14 @@ public static void init(InstanceDataManagerConfig instanceDataManagerConfig) { public static TableDataManager getTableDataManager(TableDataManagerConfig tableDataManagerConfig, String instanceId, ZkHelixPropertyStore propertyStore, ServerMetrics serverMetrics, HelixManager helixManager, LoadingCache, SegmentErrorInfo> errorCache) { - return getTableDataManager(tableDataManagerConfig, instanceId, propertyStore, serverMetrics, helixManager, + return getTableDataManager(tableDataManagerConfig, instanceId, propertyStore, serverMetrics, helixManager, null, errorCache, () -> true); } public static TableDataManager getTableDataManager(TableDataManagerConfig tableDataManagerConfig, String instanceId, ZkHelixPropertyStore propertyStore, ServerMetrics serverMetrics, HelixManager helixManager, - LoadingCache, SegmentErrorInfo> errorCache, Supplier isServerReadyToServeQueries) { + @Nullable ExecutorService segmentPreloadExecutor, LoadingCache, SegmentErrorInfo> errorCache, + Supplier isServerReadyToServeQueries) { TableDataManager tableDataManager; switch (tableDataManagerConfig.getTableType()) { case OFFLINE: @@ -72,13 +80,21 @@ public static TableDataManager getTableDataManager(TableDataManagerConfig tableD } break; case REALTIME: + Map streamConfigMap = IngestionConfigUtils.getStreamConfigMap( + tableDataManagerConfig.getTableConfig()); + if (Boolean.parseBoolean(streamConfigMap.get(StreamConfigProperties.SERVER_UPLOAD_TO_DEEPSTORE)) + && StringUtils.isEmpty(tableDataManagerConfig.getInstanceDataManagerConfig().getSegmentStoreUri())) { + throw new IllegalStateException(String.format("Table has enabled %s config. But the server has not " + + "configured the segmentstore uri. Configure the server config %s", + StreamConfigProperties.SERVER_UPLOAD_TO_DEEPSTORE, CommonConstants.Server.CONFIG_OF_SEGMENT_STORE_URI)); + } tableDataManager = new RealtimeTableDataManager(_segmentBuildSemaphore, isServerReadyToServeQueries); break; default: throw new IllegalStateException(); } - tableDataManager.init(tableDataManagerConfig, instanceId, propertyStore, serverMetrics, helixManager, errorCache, - _tableDataManagerParams); + tableDataManager.init(tableDataManagerConfig, instanceId, propertyStore, serverMetrics, helixManager, + segmentPreloadExecutor, errorCache, _tableDataManagerParams); return tableDataManager; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java index aa16a321470..0e9538f2517 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java @@ -143,13 +143,13 @@ public HLRealtimeSegmentDataManager(final SegmentZKMetadata segmentZKMetadata, f } // Inverted index columns - Set invertedIndexColumns = indexLoadingConfig.getInvertedIndexColumns(); // We need to add sorted column into inverted index columns because when we convert realtime in memory segment into // offline segment, we use sorted column's inverted index to maintain the order of the records so that the records // are sorted on the sorted column. if (_sortedColumn != null) { - invertedIndexColumns.add(_sortedColumn); + indexLoadingConfig.addInvertedIndexColumns(_sortedColumn); } + Set invertedIndexColumns = indexLoadingConfig.getInvertedIndexColumns(); _invertedIndexColumns = new ArrayList<>(invertedIndexColumns); _streamConfig = new StreamConfig(_tableNameWithType, IngestionConfigUtils.getStreamConfigMap(tableConfig)); @@ -159,7 +159,7 @@ public HLRealtimeSegmentDataManager(final SegmentZKMetadata segmentZKMetadata, f invertedIndexColumns); _segmentEndTimeThreshold = _start + _streamConfig.getFlushThresholdTimeMillis(); - _resourceTmpDir = new File(resourceDataDir, "_tmp"); + _resourceTmpDir = new File(resourceDataDir, RESOURCE_TEMP_DIR_NAME); if (!_resourceTmpDir.exists()) { _resourceTmpDir.mkdirs(); } @@ -180,19 +180,19 @@ public HLRealtimeSegmentDataManager(final SegmentZKMetadata segmentZKMetadata, f // lets create a new realtime segment _segmentLogger.info("Started {} stream provider", _streamConfig.getType()); final int capacity = _streamConfig.getFlushThresholdRows(); + boolean nullHandlingEnabled = indexingConfig != null && indexingConfig.isNullHandlingEnabled(); RealtimeSegmentConfig realtimeSegmentConfig = - new RealtimeSegmentConfig.Builder().setTableNameWithType(_tableNameWithType).setSegmentName(_segmentName) + new RealtimeSegmentConfig.Builder(indexLoadingConfig).setTableNameWithType(_tableNameWithType) + .setSegmentName(_segmentName) .setStreamName(_streamConfig.getTopicName()).setSchema(schema).setTimeColumnName(_timeColumnName) .setCapacity(capacity).setAvgNumMultiValues(indexLoadingConfig.getRealtimeAvgMultiValueCount()) - .setNoDictionaryColumns(indexLoadingConfig.getNoDictionaryColumns()) - .setVarLengthDictionaryColumns(indexLoadingConfig.getVarLengthDictionaryColumns()) - .setInvertedIndexColumns(invertedIndexColumns).setSegmentZKMetadata(segmentZKMetadata) + .setSegmentZKMetadata(segmentZKMetadata) .setOffHeap(indexLoadingConfig.isRealtimeOffHeapAllocation()).setMemoryManager( getMemoryManager(realtimeTableDataManager.getConsumerDir(), _segmentName, indexLoadingConfig.isRealtimeOffHeapAllocation(), indexLoadingConfig.isDirectRealtimeOffHeapAllocation(), serverMetrics)) .setStatsHistory(realtimeTableDataManager.getStatsHistory()) - .setNullHandlingEnabled(indexingConfig.isNullHandlingEnabled()).build(); + .setNullHandlingEnabled(nullHandlingEnabled).build(); _realtimeSegment = new MutableSegmentImpl(realtimeSegmentConfig, serverMetrics); _notifier = realtimeTableDataManager; @@ -469,7 +469,7 @@ private void updateCurrentDocumentCountMetrics() { } @Override - public void destroy() { + protected void doDestroy() { LOGGER.info("Trying to shutdown RealtimeSegmentDataManager : {}!", _segmentName); _isShuttingDown = true; try { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java index 64528661954..57ca21def13 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java @@ -76,6 +76,15 @@ public class IngestionDelayTracker { + // Class to wrap supported timestamps collected for an ingested event + private static class IngestionTimestamps { + IngestionTimestamps(long ingestionTimesMs, long firstStreamIngestionTimeMs) { + _ingestionTimeMs = ingestionTimesMs; + _firstStreamIngestionTimeMs = firstStreamIngestionTimeMs; + } + private final long _ingestionTimeMs; + private final long _firstStreamIngestionTimeMs; + } // Sleep interval for timer thread that triggers read of ideal state private static final int TIMER_THREAD_TICK_INTERVAL_MS = 300000; // 5 minutes +/- precision in timeouts // Once a partition is marked for verification, we wait 10 minutes to pull its ideal state. @@ -85,7 +94,7 @@ public class IngestionDelayTracker { private static final Logger _logger = LoggerFactory.getLogger(IngestionDelayTracker.class.getSimpleName()); // HashMap used to store ingestion time measures for all partitions active for the current table. - private final Map _partitionToIngestionTimeMsMap = new ConcurrentHashMap<>(); + private final Map _partitionToIngestionTimestampsMap = new ConcurrentHashMap<>(); // We mark partitions that go from CONSUMING to ONLINE in _partitionsMarkedForVerification: if they do not // go back to CONSUMING in some period of time, we verify whether they are still hosted in this server by reading // ideal state. This is done with the goal of minimizing reading ideal state for efficiency reasons. @@ -141,9 +150,9 @@ public IngestionDelayTracker(ServerMetrics serverMetrics, String tableNameWithTy * * @param ingestionTimeMs original ingestion time in milliseconds. */ - private long getIngestionDelayMs(Long ingestionTimeMs) { - if (ingestionTimeMs == null) { - return 0; // return 0 when not initialized + private long getIngestionDelayMs(long ingestionTimeMs) { + if (ingestionTimeMs < 0) { + return 0; } // Compute aged delay for current partition long agedIngestionDelayMs = _clock.millis() - ingestionTimeMs; @@ -159,7 +168,7 @@ private long getIngestionDelayMs(Long ingestionTimeMs) { * @param partitionGroupId partition ID which we should stop tracking. */ private void removePartitionId(int partitionGroupId) { - _partitionToIngestionTimeMsMap.remove(partitionGroupId); + _partitionToIngestionTimestampsMap.remove(partitionGroupId); // If we are removing a partition we should stop reading its ideal state. _partitionsMarkedForVerification.remove(partitionGroupId); _serverMetrics.removePartitionGauge(_metricName, partitionGroupId, ServerGauge.REALTIME_INGESTION_DELAY_MS); @@ -196,21 +205,36 @@ void setClock(Clock clock) { * Called by LLRealTimeSegmentDataManagers to post ingestion time updates to this tracker class. * * @param ingestionTimeMs ingestion time being recorded. + * @param firstStreamIngestionTimeMs time the event was ingested in the first stage of the ingestion pipeline. * @param partitionGroupId partition ID for which this ingestion time is being recorded. */ - public void updateIngestionDelay(long ingestionTimeMs, int partitionGroupId) { + public void updateIngestionDelay(long ingestionTimeMs, long firstStreamIngestionTimeMs, int partitionGroupId) { // Store new measure and wipe old one for this partition - // TODO: see if we can install gauges after the server is ready. if (!_isServerReadyToServeQueries.get()) { // Do not update the ingestion delay metrics during server startup period return; } - Long previousMeasure = _partitionToIngestionTimeMsMap.put(partitionGroupId, - ingestionTimeMs); + if ((ingestionTimeMs < 0) && (firstStreamIngestionTimeMs < 0)) { + // If stream does not return a valid ingestion timestamps don't publish a metric + return; + } + IngestionTimestamps previousMeasure = _partitionToIngestionTimestampsMap.put(partitionGroupId, + new IngestionTimestamps(ingestionTimeMs, firstStreamIngestionTimeMs)); if (previousMeasure == null) { // First time we start tracking a partition we should start tracking it via metric - _serverMetrics.setOrUpdatePartitionGauge(_metricName, partitionGroupId, - ServerGauge.REALTIME_INGESTION_DELAY_MS, () -> getPartitionIngestionDelayMs(partitionGroupId)); + // Only publish the metric if supported by the underlying stream. If not supported the stream + // returns Long.MIN_VALUE + if (ingestionTimeMs >= 0) { + _serverMetrics.setOrUpdatePartitionGauge(_metricName, partitionGroupId, ServerGauge.REALTIME_INGESTION_DELAY_MS, + () -> getPartitionIngestionDelayMs(partitionGroupId)); + } + if (firstStreamIngestionTimeMs >= 0) { + // Only publish this metric when creation time is supported by the underlying stream + // When this timestamp is not supported it always returns the value Long.MIN_VALUE + _serverMetrics.setOrUpdatePartitionGauge(_metricName, partitionGroupId, + ServerGauge.END_TO_END_REALTIME_INGESTION_DELAY_MS, + () -> getPartitionEndToEndIngestionDelayMs(partitionGroupId)); + } } // If we are consuming we do not need to track this partition for removal. _partitionsMarkedForVerification.remove(partitionGroupId); @@ -283,8 +307,27 @@ public void markPartitionForVerification(int partitionGroupId) { */ public long getPartitionIngestionDelayMs(int partitionGroupId) { // Not protected as this will only be invoked when metric is installed which happens after server ready - Long currentMeasure = _partitionToIngestionTimeMsMap.get(partitionGroupId); - return getIngestionDelayMs(currentMeasure); + IngestionTimestamps currentMeasure = _partitionToIngestionTimestampsMap.get(partitionGroupId); + if (currentMeasure == null) { // Guard just in case we read the metric without initializing it + return 0; + } + return getIngestionDelayMs(currentMeasure._ingestionTimeMs); + } + + /* + * Method to get end to end ingestion delay for a given partition. + * + * @param partitionGroupId partition for which we are retrieving the delay + * + * @return End to end ingestion delay in milliseconds for the given partition ID. + */ + public long getPartitionEndToEndIngestionDelayMs(int partitionGroupId) { + // Not protected as this will only be invoked when metric is installed which happens after server ready + IngestionTimestamps currentMeasure = _partitionToIngestionTimestampsMap.get(partitionGroupId); + if (currentMeasure == null) { // Guard just in case we read the metric without initializing it + return 0; + } + return getIngestionDelayMs(currentMeasure._firstStreamIngestionTimeMs); } /* @@ -299,7 +342,7 @@ public void shutdown() { return; } // Remove partitions so their related metrics get uninstalled. - for (Map.Entry entry : _partitionToIngestionTimeMsMap.entrySet()) { + for (Map.Entry entry : _partitionToIngestionTimestampsMap.entrySet()) { removePartitionId(entry.getKey()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java index 45570b59a42..6b9905ab168 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java @@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BooleanSupplier; import javax.annotation.Nullable; import org.apache.commons.io.FileUtils; import org.apache.pinot.common.Utils; @@ -230,8 +231,9 @@ public void deleteSegmentFile() { // modify the permit. This boolean make sure the semaphore gets released only once when the partition group stops // consuming. private final AtomicBoolean _acquiredConsumerSemaphore; - private final String _metricKeyName; private final ServerMetrics _serverMetrics; + private final PartitionUpsertMetadataManager _partitionUpsertMetadataManager; + private final BooleanSupplier _isReadyToConsumeData; private final MutableSegmentImpl _realtimeSegment; private volatile StreamPartitionMsgOffset _currentOffset; private volatile State _state; @@ -263,7 +265,6 @@ public void deleteSegmentFile() { private final int _partitionGroupId; private final PartitionGroupConsumptionStatus _partitionGroupConsumptionStatus; final String _clientId; - private final LLCSegmentName _llcSegmentName; private final TransformPipeline _transformPipeline; private PartitionGroupConsumer _partitionGroupConsumer = null; private StreamMetadataProvider _partitionMetadataProvider = null; @@ -293,6 +294,7 @@ public void deleteSegmentFile() { private final ConsumptionRateLimiter _rateLimiter; private final StreamPartitionMsgOffset _latestStreamOffsetAtStartupTime; + private final CompletionMode _segmentCompletionMode; // TODO each time this method is called, we print reason for stop. Good to print only once. private boolean endCriteriaReached() { @@ -395,6 +397,10 @@ private void handleTransientStreamErrors(Exception e) protected boolean consumeLoop() throws Exception { + // At this point, we know that we can potentially move the offset, so the old saved segment file is not valid + // anymore. Remove the file if it exists. + removeSegmentFile(); + _numRowsErrored = 0; final long idlePipeSleepTimeMillis = 100; final long idleTimeoutMillis = _partitionLevelStreamConfig.getIdleTimeoutMillis(); @@ -403,9 +409,6 @@ protected boolean consumeLoop() StreamPartitionMsgOffset lastUpdatedOffset = _streamPartitionMsgOffsetFactory .create(_currentOffset); // so that we always update the metric when we enter this method. - // At this point, we know that we can potentially move the offset, so the old saved segment file is not valid - // anymore. Remove the file if it exists. - removeSegmentFile(); _segmentLogger.info("Starting consumption loop start offset {}, finalOffset {}", _currentOffset, _finalOffset); while (!_shouldStop && !endCriteriaReached()) { @@ -445,10 +448,10 @@ protected boolean consumeLoop() // TODO Issue 5359 Need to find a way to bump metrics without getting actual offset value. if (_currentOffset instanceof LongMsgOffset) { // TODO: only LongMsgOffset supplies long offset value. - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.HIGHEST_STREAM_OFFSET_CONSUMED, + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.HIGHEST_STREAM_OFFSET_CONSUMED, ((LongMsgOffset) _currentOffset).getOffset()); } - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 1); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 1); lastUpdatedOffset = _streamPartitionMsgOffsetFactory.create(_currentOffset); } else if (endCriteriaReached) { // At this point current offset has not moved because processStreamEvents() has exited before processing a @@ -479,7 +482,7 @@ protected boolean consumeLoop() if (totalIdleTimeMillis > idleTimeoutMillis) { // Update the partition-consuming metric only if we have been idling beyond idle timeout. // Create a new stream consumer wrapper, in case we are stuck on something. - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 1); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 1); recreateStreamConsumer( String.format("Total idle time: %d ms exceeded idle timeout: %d ms", totalIdleTimeMillis, idleTimeoutMillis)); @@ -495,7 +498,7 @@ protected boolean consumeLoop() } if (_numRowsErrored > 0) { - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); _serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); } return true; @@ -518,9 +521,12 @@ private boolean processStreamEvents(MessageBatch messagesAndOffsets, long idlePi int indexedMessageCount = 0; int streamMessageCount = 0; boolean canTakeMore = true; + boolean hasTransformedRows = false; TransformPipeline.Result reusedResult = new TransformPipeline.Result(); boolean prematureExit = false; + RowMetadata msgMetadata = null; + for (int index = 0; index < messageCount; index++) { prematureExit = _shouldStop || endCriteriaReached(); if (prematureExit) { @@ -553,13 +559,14 @@ private boolean processStreamEvents(MessageBatch messagesAndOffsets, long idlePi // Decode message StreamDataDecoderResult decodedRow = _streamDataDecoder.decode(messagesAndOffsets.getStreamMessage(index)); - RowMetadata msgMetadata = messagesAndOffsets.getStreamMessage(index).getMetadata(); + msgMetadata = messagesAndOffsets.getStreamMessage(index).getMetadata(); if (decodedRow.getException() != null) { // TODO: based on a config, decide whether the record should be silently dropped or stop further consumption on // decode error realtimeRowsDroppedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.INVALID_REALTIME_ROWS_DROPPED, 1, + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.INVALID_REALTIME_ROWS_DROPPED, 1, realtimeRowsDroppedMeter); + _numRowsErrored++; } else { try { _transformPipeline.processRow(decodedRow.getResult(), reusedResult); @@ -569,27 +576,30 @@ private boolean processStreamEvents(MessageBatch messagesAndOffsets, long idlePi reusedResult.getTransformedRows().clear(); String errorMessage = String.format("Caught exception while transforming the record: %s", decodedRow); _segmentLogger.error(errorMessage, e); - _realtimeTableDataManager.addSegmentError(_segmentNameStr, - new SegmentErrorInfo(now(), errorMessage, e)); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); } if (reusedResult.getSkippedRowCount() > 0) { realtimeRowsDroppedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.INVALID_REALTIME_ROWS_DROPPED, + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.REALTIME_ROWS_FILTERED, reusedResult.getSkippedRowCount(), realtimeRowsDroppedMeter); } if (reusedResult.getIncompleteRowCount() > 0) { realtimeIncompleteRowsConsumedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.INCOMPLETE_REALTIME_ROWS_CONSUMED, + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.INCOMPLETE_REALTIME_ROWS_CONSUMED, reusedResult.getIncompleteRowCount(), realtimeIncompleteRowsConsumedMeter); } - for (GenericRow transformedRow : reusedResult.getTransformedRows()) { + List transformedRows = reusedResult.getTransformedRows(); + if (transformedRows.size() > 0) { + hasTransformedRows = true; + } + for (GenericRow transformedRow : transformedRows) { try { canTakeMore = _realtimeSegment.index(transformedRow, msgMetadata); indexedMessageCount++; _lastRowMetadata = msgMetadata; _lastConsumedTimestampMs = System.currentTimeMillis(); realtimeRowsConsumedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.REALTIME_ROWS_CONSUMED, 1, + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.REALTIME_ROWS_CONSUMED, 1, realtimeRowsConsumedMeter); } catch (Exception e) { _numRowsErrored++; @@ -605,17 +615,31 @@ private boolean processStreamEvents(MessageBatch messagesAndOffsets, long idlePi _numRowsConsumed++; streamMessageCount++; } - updateIngestionDelay(indexedMessageCount); + + if (indexedMessageCount > 0) { + // Record Ingestion delay for this partition with metadata for last message we processed + updateIngestionDelay(_lastRowMetadata); + } else if (!hasTransformedRows && (msgMetadata != null)) { + // If all messages were filtered by transformation, we still attempt to update ingestion delay using + // the metadata for the last message we processed if any. + updateIngestionDelay(msgMetadata); + } + updateCurrentDocumentCountMetrics(); if (messagesAndOffsets.getUnfilteredMessageCount() > 0) { _hasMessagesFetched = true; + if (messageCount == 0) { + // If we received events from the stream but all were filtered, we attempt to estimate the ingestion + // delay from the metadata of the last filtered message received. + updateIngestionDelay(messagesAndOffsets.getLastMessageMetadata()); + } if (streamMessageCount > 0 && _segmentLogger.isDebugEnabled()) { _segmentLogger.debug("Indexed {} messages ({} messages read from stream) current offset {}", indexedMessageCount, streamMessageCount, _currentOffset); } } else if (!prematureExit) { // Record Pinot ingestion delay as zero since we are up-to-date and no new events - _realtimeTableDataManager.updateIngestionDelay(System.currentTimeMillis(), _partitionGroupId); + setIngestionDelayToZero(); if (_segmentLogger.isDebugEnabled()) { _segmentLogger.debug("empty batch received - sleeping for {}ms", idlePipeSleepTimeMillis); } @@ -632,24 +656,47 @@ public void run() { long catchUpTimeMillis = 0L; _startTimeMs = now(); try { + if (!_isReadyToConsumeData.getAsBoolean()) { + do { + //noinspection BusyWait + Thread.sleep(RealtimeTableDataManager.READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS); + } while (!_shouldStop && !_isReadyToConsumeData.getAsBoolean()); + } + + // TODO: + // When reaching here, the current consuming segment has already acquired the consumer semaphore, but there is + // no guarantee that the previous consuming segment is already persisted (replaced with immutable segment). It + // can potentially cause the following problems: + // 1. The snapshot for the previous consuming segment might not be taken since it is not persisted yet + // 2. If the previous consuming segment is dropped but immutable segment is not downloaded and replaced yet, + // it might cause inconsistency (especially for partial upsert because events are not consumed in sequence) + // To address this problem, we should consider releasing the consumer semaphore after the consuming segment is + // persisted. + // Take upsert snapshot before starting consuming events + if (_partitionUpsertMetadataManager != null) { + _partitionUpsertMetadataManager.takeSnapshot(); + // If upsertTTL is enabled, we will remove expired primary keys from upsertMetadata after taking snapshot. + _partitionUpsertMetadataManager.removeExpiredPrimaryKeys(); + } + while (!_state.isFinal()) { if (_state.shouldConsume()) { consumeLoop(); // Consume until we reached the end criteria, or we are stopped. } - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); if (_shouldStop) { break; } if (_state == State.INITIAL_CONSUMING) { initialConsumptionEnd = now(); - _serverMetrics.setValueOfTableGauge(_metricKeyName, + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_INITIAL_CONSUMPTION_DURATION_SECONDS, TimeUnit.MILLISECONDS.toSeconds(initialConsumptionEnd - _startTimeMs)); } else if (_state == State.CATCHING_UP) { catchUpTimeMillis += now() - lastCatchUpStart; _serverMetrics - .setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_CATCHUP_DURATION_SECONDS, + .setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_CATCHUP_DURATION_SECONDS, TimeUnit.MILLISECONDS.toSeconds(catchUpTimeMillis)); } @@ -657,7 +704,6 @@ public void run() { _state = State.HOLDING; SegmentCompletionProtocol.Response response = postSegmentConsumedMsg(); SegmentCompletionProtocol.ControllerResponseStatus status = response.getStatus(); - StreamPartitionMsgOffset rspOffset = extractOffset(response); boolean success; switch (status) { case NOT_LEADER: @@ -666,6 +712,7 @@ public void run() { hold(); break; case CATCH_UP: + StreamPartitionMsgOffset rspOffset = extractOffset(response); if (rspOffset.compareTo(_currentOffset) <= 0) { // Something wrong with the controller. Back off and try again. _segmentLogger.error("Invalid catchup offset {} in controller response, current offset {}", rspOffset, @@ -687,8 +734,7 @@ public void run() { break; case KEEP: _state = State.RETAINING; - CompletionMode segmentCompletionMode = getSegmentCompletionMode(); - switch (segmentCompletionMode) { + switch (_segmentCompletionMode) { case DOWNLOAD: _state = State.DISCARDED; break; @@ -742,7 +788,7 @@ public void run() { _state = State.ERROR; _realtimeTableDataManager .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); return; } @@ -750,7 +796,7 @@ public void run() { if (initialConsumptionEnd != 0L) { _serverMetrics - .setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_COMPLETION_DURATION_SECONDS, + .setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_COMPLETION_DURATION_SECONDS, TimeUnit.MILLISECONDS.toSeconds(now() - initialConsumptionEnd)); } // There is a race condition that the destroy() method can be called which ends up calling stop on the consumer. @@ -760,24 +806,11 @@ public void run() { // so it is ok not to mark it non-consuming, as the main thread will clean up this metric in destroy() method // as the final step. if (!_shouldStop) { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); } } } - /** - * Fetches the completion mode for the segment completion for the given realtime table - */ - private CompletionMode getSegmentCompletionMode() { - CompletionConfig completionConfig = _tableConfig.getValidationConfig().getCompletionConfig(); - if (completionConfig != null) { - if (CompletionMode.DOWNLOAD.toString().equalsIgnoreCase(completionConfig.getCompletionMode())) { - return CompletionMode.DOWNLOAD; - } - } - return CompletionMode.DEFAULT; - } - @VisibleForTesting protected StreamPartitionMsgOffset extractOffset(SegmentCompletionProtocol.Response response) { if (response.getStreamPartitionMsgOffset() != null) { @@ -847,7 +880,6 @@ public Map getPartitionToLagState( if (_partitionMetadataProvider == null) { createPartitionMetadataProvider("Get Partition Lag State"); } - ; return _partitionMetadataProvider.getCurrentPartitionLagState(consumerPartitionStateMap); } @@ -876,6 +908,11 @@ protected AtomicBoolean getAcquiredConsumerSemaphore() { protected SegmentBuildDescriptor buildSegmentInternal(boolean forCommit) { closeStreamConsumers(); + // Do not allow building segment when table data manager is already shut down + if (_realtimeTableDataManager.isShutDown()) { + _segmentLogger.warn("Table data manager is already shut down"); + return null; + } try { final long startTimeMillis = now(); if (_segBuildSemaphore != null) { @@ -934,9 +971,9 @@ protected SegmentBuildDescriptor buildSegmentInternal(boolean forCommit) { } long segmentSizeBytes = FileUtils.sizeOfDirectory(indexDir); - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_DURATION_SECONDS, + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_DURATION_SECONDS, TimeUnit.MILLISECONDS.toSeconds(buildTimeMillis)); - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_WAIT_TIME_SECONDS, + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_WAIT_TIME_SECONDS, TimeUnit.MILLISECONDS.toSeconds(waitTimeMillis)); if (forCommit) { @@ -1003,6 +1040,8 @@ protected boolean commitSegment(String controllerVipUrl, boolean isSplitCommit) SegmentCompletionProtocol.Response commitResponse = commit(controllerVipUrl, isSplitCommit); if (!commitResponse.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS)) { + _segmentLogger.warn("Controller response was {} and not {}", commitResponse.getStatus(), + SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS); return false; } @@ -1078,7 +1117,7 @@ private void closePartitionMetadataProvider() { * which no longer resides in this host any more, thus causes false positive information to the metric system. */ private void cleanupMetrics() { - _serverMetrics.removeTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING); + _serverMetrics.removeTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING); } protected void hold() { @@ -1127,7 +1166,7 @@ private void removeSegmentFile() { public void goOnlineFromConsuming(SegmentZKMetadata segmentZKMetadata) throws InterruptedException { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); try { // Remove the segment file before we do anything else. removeSegmentFile(); @@ -1154,11 +1193,10 @@ public void goOnlineFromConsuming(SegmentZKMetadata segmentZKMetadata) case CATCHING_UP: case HOLDING: case INITIAL_CONSUMING: - CompletionMode segmentCompletionMode = getSegmentCompletionMode(); - switch (segmentCompletionMode) { + switch (_segmentCompletionMode) { case DOWNLOAD: _segmentLogger.info("State {}. CompletionMode {}. Downloading to replace", _state.toString(), - segmentCompletionMode); + _segmentCompletionMode); downloadSegmentAndReplace(segmentZKMetadata); break; case DEFAULT: @@ -1199,7 +1237,7 @@ public void goOnlineFromConsuming(SegmentZKMetadata segmentZKMetadata) } catch (Exception e) { Utils.rethrowException(e); } finally { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); } } @@ -1225,7 +1263,7 @@ private boolean catchupToFinalOffset(StreamPartitionMsgOffset endOffset, long ti _segmentLogger.warn("Exception when catching up to final offset", e); return false; } finally { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); } if (_currentOffset.compareTo(endOffset) != 0) { // Timeout? @@ -1236,7 +1274,7 @@ private boolean catchupToFinalOffset(StreamPartitionMsgOffset endOffset, long ti return true; } - public void destroy() { + protected void doDestroy() { try { stop(); } catch (InterruptedException e) { @@ -1277,7 +1315,7 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo RealtimeTableDataManager realtimeTableDataManager, String resourceDataDir, IndexLoadingConfig indexLoadingConfig, Schema schema, LLCSegmentName llcSegmentName, Semaphore partitionGroupConsumerSemaphore, ServerMetrics serverMetrics, @Nullable PartitionUpsertMetadataManager partitionUpsertMetadataManager, - @Nullable PartitionDedupMetadataManager partitionDedupMetadataManager) { + @Nullable PartitionDedupMetadataManager partitionDedupMetadataManager, BooleanSupplier isReadyToConsumeData) { _segBuildSemaphore = realtimeTableDataManager.getSegmentBuildSemaphore(); _segmentZKMetadata = segmentZKMetadata; _tableConfig = tableConfig; @@ -1287,10 +1325,16 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo _indexLoadingConfig = indexLoadingConfig; _schema = schema; _serverMetrics = serverMetrics; + _partitionUpsertMetadataManager = partitionUpsertMetadataManager; + _isReadyToConsumeData = isReadyToConsumeData; _segmentVersion = indexLoadingConfig.getSegmentVersion(); _instanceId = _realtimeTableDataManager.getServerInstance(); _leaseExtender = SegmentBuildTimeLeaseExtender.getLeaseExtender(_tableNameWithType); _protocolHandler = new ServerSegmentCompletionProtocolHandler(_serverMetrics, _tableNameWithType); + CompletionConfig completionConfig = _tableConfig.getValidationConfig().getCompletionConfig(); + _segmentCompletionMode = completionConfig != null + && CompletionMode.DOWNLOAD.toString().equalsIgnoreCase(completionConfig.getCompletionMode()) + ? CompletionMode.DOWNLOAD : CompletionMode.DEFAULT; String timeColumnName = tableConfig.getValidationConfig().getTimeColumnName(); // TODO Validate configs @@ -1301,17 +1345,16 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo _streamPartitionMsgOffsetFactory = _streamConsumerFactory.createStreamMsgOffsetFactory(); String streamTopic = _partitionLevelStreamConfig.getTopicName(); _segmentNameStr = _segmentZKMetadata.getSegmentName(); - _llcSegmentName = llcSegmentName; - _partitionGroupId = _llcSegmentName.getPartitionGroupId(); + _partitionGroupId = llcSegmentName.getPartitionGroupId(); _partitionGroupConsumptionStatus = - new PartitionGroupConsumptionStatus(_partitionGroupId, _llcSegmentName.getSequenceNumber(), + new PartitionGroupConsumptionStatus(_partitionGroupId, llcSegmentName.getSequenceNumber(), _streamPartitionMsgOffsetFactory.create(_segmentZKMetadata.getStartOffset()), _segmentZKMetadata.getEndOffset() == null ? null : _streamPartitionMsgOffsetFactory.create(_segmentZKMetadata.getEndOffset()), _segmentZKMetadata.getStatus().toString()); _partitionGroupConsumerSemaphore = partitionGroupConsumerSemaphore; _acquiredConsumerSemaphore = new AtomicBoolean(false); - _metricKeyName = _tableNameWithType + "-" + streamTopic + "-" + _partitionGroupId; + _clientId = _tableNameWithType + "-" + streamTopic + "-" + _partitionGroupId; _segmentLogger = LoggerFactory.getLogger(LLRealtimeSegmentDataManager.class.getName() + "_" + _segmentNameStr); _tableStreamName = _tableNameWithType + "_" + streamTopic; _memoryManager = getMemoryManager(realtimeTableDataManager.getConsumerDir(), _segmentNameStr, @@ -1319,34 +1362,33 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo serverMetrics); _rateLimiter = RealtimeConsumptionRateManager.getInstance() - .createRateLimiter(_partitionLevelStreamConfig, _tableNameWithType, _serverMetrics, _metricKeyName); + .createRateLimiter(_partitionLevelStreamConfig, _tableNameWithType, _serverMetrics, _clientId); List sortedColumns = indexLoadingConfig.getSortedColumns(); String sortedColumn; if (sortedColumns.isEmpty()) { _segmentLogger.info("RealtimeDataResourceZKMetadata contains no information about sorted column for segment {}", - _llcSegmentName); + llcSegmentName); sortedColumn = null; } else { String firstSortedColumn = sortedColumns.get(0); if (_schema.hasColumn(firstSortedColumn)) { _segmentLogger.info("Setting sorted column name: {} from RealtimeDataResourceZKMetadata for segment {}", - firstSortedColumn, _llcSegmentName); + firstSortedColumn, llcSegmentName); sortedColumn = firstSortedColumn; } else { _segmentLogger .warn("Sorted column name: {} from RealtimeDataResourceZKMetadata is not existed in schema for segment {}.", - firstSortedColumn, _llcSegmentName); + firstSortedColumn, llcSegmentName); sortedColumn = null; } } // Inverted index columns - Set invertedIndexColumns = indexLoadingConfig.getInvertedIndexColumns(); // We need to add sorted column into inverted index columns because when we convert realtime in memory segment into // offline segment, we use sorted column's inverted index to maintain the order of the records so that the records // are sorted on the sorted column. if (sortedColumn != null) { - invertedIndexColumns.add(sortedColumn); + indexLoadingConfig.addInvertedIndexColumns(sortedColumn); } // Read the max number of rows @@ -1362,7 +1404,7 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo _nullHandlingEnabled = indexingConfig.isNullHandlingEnabled(); _columnIndicesForRealtimeTable = new ColumnIndicesForRealtimeTable(sortedColumn, - new ArrayList<>(invertedIndexColumns), + new ArrayList<>(indexLoadingConfig.getInvertedIndexColumns()), new ArrayList<>(indexLoadingConfig.getTextIndexColumns()), new ArrayList<>(indexLoadingConfig.getFSTIndexColumns()), new ArrayList<>(indexLoadingConfig.getNoDictionaryColumns()), @@ -1371,15 +1413,11 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo // Start new realtime segment String consumerDir = realtimeTableDataManager.getConsumerDir(); RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder = - new RealtimeSegmentConfig.Builder().setTableNameWithType(_tableNameWithType).setSegmentName(_segmentNameStr) + new RealtimeSegmentConfig.Builder(indexLoadingConfig).setTableNameWithType(_tableNameWithType) + .setSegmentName(_segmentNameStr) .setStreamName(streamTopic).setSchema(_schema).setTimeColumnName(timeColumnName) .setCapacity(_segmentMaxRowCount).setAvgNumMultiValues(indexLoadingConfig.getRealtimeAvgMultiValueCount()) - .setNoDictionaryColumns(indexLoadingConfig.getNoDictionaryColumns()) - .setVarLengthDictionaryColumns(indexLoadingConfig.getVarLengthDictionaryColumns()) - .setInvertedIndexColumns(invertedIndexColumns).setTextIndexColumns(indexLoadingConfig.getTextIndexColumns()) - .setFSTIndexColumns(indexLoadingConfig.getFSTIndexColumns()) - .setJsonIndexConfigs(indexLoadingConfig.getJsonIndexConfigs()) - .setH3IndexConfigs(indexLoadingConfig.getH3IndexConfigs()).setSegmentZKMetadata(segmentZKMetadata) + .setSegmentZKMetadata(segmentZKMetadata) .setOffHeap(_isOffHeap).setMemoryManager(_memoryManager) .setStatsHistory(realtimeTableDataManager.getStatsHistory()) .setAggregateMetrics(indexingConfig.isAggregateMetrics()) @@ -1388,15 +1426,21 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo .setConsumerDir(consumerDir).setUpsertMode(tableConfig.getUpsertMode()) .setPartitionUpsertMetadataManager(partitionUpsertMetadataManager) .setPartitionDedupMetadataManager(partitionDedupMetadataManager) - .setUpsertComparisonColumn(tableConfig.getUpsertComparisonColumn()) + .setUpsertComparisonColumns(tableConfig.getUpsertComparisonColumns()) + .setUpsertDeleteRecordColumn(tableConfig.getUpsertDeleteRecordColumn()) .setFieldConfigList(tableConfig.getFieldConfigList()); // Create message decoder Set fieldsToRead = IngestionUtils.getFieldsForRecordExtractor(_tableConfig.getIngestionConfig(), _schema); - StreamMessageDecoder streamMessageDecoder = StreamDecoderProvider.create(_partitionLevelStreamConfig, fieldsToRead); - _streamDataDecoder = new StreamDataDecoderImpl(streamMessageDecoder); - _clientId = streamTopic + "-" + _partitionGroupId; - + try { + StreamMessageDecoder streamMessageDecoder = + StreamDecoderProvider.create(_partitionLevelStreamConfig, fieldsToRead); + _streamDataDecoder = new StreamDataDecoderImpl(streamMessageDecoder); + } catch (Exception e) { + _realtimeTableDataManager.addSegmentError(_segmentNameStr, + new SegmentErrorInfo(now(), "Failed to initialize the StreamMessageDecoder", e)); + throw e; + } _transformPipeline = new TransformPipeline(tableConfig, schema); // Acquire semaphore to create stream consumers try { @@ -1415,7 +1459,7 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo createPartitionMetadataProvider("Starting"); setPartitionParameters(realtimeSegmentConfigBuilder, indexingConfig.getSegmentPartitionConfig()); _realtimeSegment = new MutableSegmentImpl(realtimeSegmentConfigBuilder.build(), serverMetrics); - _resourceTmpDir = new File(resourceDataDir, "_tmp"); + _resourceTmpDir = new File(resourceDataDir, RESOURCE_TEMP_DIR_NAME); if (!_resourceTmpDir.exists()) { _resourceTmpDir.mkdirs(); } @@ -1426,7 +1470,7 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo _segmentCommitterFactory = new SegmentCommitterFactory(_segmentLogger, _protocolHandler, tableConfig, indexLoadingConfig, serverMetrics); _segmentLogger - .info("Starting consumption on realtime consuming segment {} maxRowCount {} maxEndTime {}", _llcSegmentName, + .info("Starting consumption on realtime consuming segment {} maxRowCount {} maxEndTime {}", llcSegmentName, _segmentMaxRowCount, new DateTime(_consumeEndTime, DateTimeZone.UTC)); startConsumerThread(); } catch (Exception e) { @@ -1434,6 +1478,12 @@ public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableCo // ERROR -> OFFLINE -> CONSUMING via Helix Admin fails because the semaphore is acquired, but not released. // Hence releasing the semaphore here to unblock reset operation via Helix Admin. _partitionGroupConsumerSemaphore.release(); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), + "Failed to initialize segment data manager", e)); + _segmentLogger.warn( + "Calling controller to mark the segment as OFFLINE in Ideal State because of initialization error: '{}'", + e.getMessage()); + postStopConsumedMsg("Consuming segment initialization error"); throw e; } } @@ -1562,17 +1612,19 @@ private void createPartitionMetadataProvider(String reason) { _partitionMetadataProvider = _streamConsumerFactory.createPartitionMetadataProvider(_clientId, _partitionGroupId); } + private void updateIngestionDelay(RowMetadata metadata) { + if (metadata != null) { + _realtimeTableDataManager.updateIngestionDelay(metadata.getRecordIngestionTimeMs(), + metadata.getFirstStreamRecordIngestionTimeMs(), _partitionGroupId); + } + } + /* - * Updates the ingestion delay if messages were processed using the time stamp for the last consumed event. - * - * @param indexedMessagesCount + * Sets ingestion delay to zero in situations where we are caught up processing events. */ - private void updateIngestionDelay(int indexedMessageCount) { - if ((indexedMessageCount > 0) && (_lastRowMetadata != null)) { - // Record Ingestion delay for this partition - _realtimeTableDataManager.updateIngestionDelay(_lastRowMetadata.getRecordIngestionTimeMs(), - _partitionGroupId); - } + private void setIngestionDelayToZero() { + long currentTimeMs = System.currentTimeMillis(); + _realtimeTableDataManager.updateIngestionDelay(currentTimeMs, currentTimeMs, _partitionGroupId); } // This should be done during commit? We may not always commit when we build a segment.... diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java index 909d863b987..4f173521c9e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java @@ -26,10 +26,15 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.pinot.common.metrics.ServerMeter; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.metrics.ServerTimer; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.spi.filesystem.PinotFS; import org.apache.pinot.spi.filesystem.PinotFSFactory; import org.apache.pinot.spi.utils.StringUtil; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,22 +48,34 @@ public class PinotFSSegmentUploader implements SegmentUploader { private static final Logger LOGGER = LoggerFactory.getLogger(PinotFSSegmentUploader.class); public static final int DEFAULT_SEGMENT_UPLOAD_TIMEOUT_MILLIS = 10 * 1000; - private String _segmentStoreUriStr; - private ExecutorService _executorService = Executors.newCachedThreadPool(); - private int _timeoutInMs; + private final String _segmentStoreUriStr; + private final ExecutorService _executorService = Executors.newCachedThreadPool(); + private final int _timeoutInMs; + private final ServerMetrics _serverMetrics; - public PinotFSSegmentUploader(String segmentStoreDirUri, int timeoutMillis) { + public PinotFSSegmentUploader(String segmentStoreDirUri, int timeoutMillis, ServerMetrics serverMetrics) { _segmentStoreUriStr = segmentStoreDirUri; _timeoutInMs = timeoutMillis; + _serverMetrics = serverMetrics; } + @Override public URI uploadSegment(File segmentFile, LLCSegmentName segmentName) { + return uploadSegment(segmentFile, segmentName, _timeoutInMs); + } + + @Override + public URI uploadSegment(File segmentFile, LLCSegmentName segmentName, int timeoutInMillis) { if (_segmentStoreUriStr == null || _segmentStoreUriStr.isEmpty()) { + LOGGER.error("Missing segment store uri. Failed to upload segment file {} for {}.", segmentFile.getName(), + segmentName.getSegmentName()); return null; } + final String rawTableName = TableNameBuilder.extractRawTableName(segmentName.getTableName()); Callable uploadTask = () -> { URI destUri = new URI(StringUtil.join(File.separator, _segmentStoreUriStr, segmentName.getTableName(), segmentName.getSegmentName() + UUID.randomUUID().toString())); + long startTime = System.currentTimeMillis(); try { PinotFS pinotFS = PinotFSFactory.create(new URI(_segmentStoreUriStr).getScheme()); // Check and delete any existing segment file. @@ -69,21 +86,32 @@ public URI uploadSegment(File segmentFile, LLCSegmentName segmentName) { return destUri; } catch (Exception e) { LOGGER.warn("Failed copy segment tar file {} to segment store {}: {}", segmentFile.getName(), destUri, e); + } finally { + long duration = System.currentTimeMillis() - startTime; + _serverMetrics.addTimedTableValue(rawTableName, ServerTimer.SEGMENT_UPLOAD_TIME_MS, duration, + TimeUnit.MILLISECONDS); } return null; }; Future future = _executorService.submit(uploadTask); try { - URI segmentLocation = future.get(_timeoutInMs, TimeUnit.MILLISECONDS); + URI segmentLocation = future.get(timeoutInMillis, TimeUnit.MILLISECONDS); LOGGER.info("Successfully upload segment {} to {}.", segmentName, segmentLocation); + _serverMetrics.addMeteredTableValue(rawTableName, + segmentLocation == null ? ServerMeter.SEGMENT_UPLOAD_FAILURE : ServerMeter.SEGMENT_UPLOAD_SUCCESS, 1); return segmentLocation; } catch (InterruptedException e) { LOGGER.info("Interrupted while waiting for segment upload of {} to {}.", segmentName, _segmentStoreUriStr); Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + // Emit a separate metric for timeout since this is relatively more common than other errors. + _serverMetrics.addMeteredTableValue(rawTableName, ServerMeter.SEGMENT_UPLOAD_TIMEOUT, 1); + LOGGER.warn("Timed out waiting to upload segment: {} for table: {}", segmentName.getSegmentName(), rawTableName); } catch (Exception e) { LOGGER .warn("Failed to upload file {} of segment {} for table {} ", segmentFile.getAbsolutePath(), segmentName, e); } + _serverMetrics.addMeteredTableValue(rawTableName, ServerMeter.SEGMENT_UPLOAD_FAILURE, 1); return null; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java index e1e06c90810..3111d284182 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java @@ -31,6 +31,7 @@ public abstract class RealtimeSegmentDataManager extends SegmentDataManager { + public static final String RESOURCE_TEMP_DIR_NAME = "_tmp"; @Override public abstract MutableSegment getSegment(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java index 4ae54b6bbc6..a7ee82b9ebd 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java @@ -29,11 +29,9 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BooleanSupplier; import java.util.function.Supplier; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.collections.CollectionUtils; @@ -45,7 +43,6 @@ import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ServerGauge; import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.NamedThreadFactory; import org.apache.pinot.common.utils.SegmentName; import org.apache.pinot.common.utils.SegmentUtils; import org.apache.pinot.common.utils.TarGzCompressionUtils; @@ -56,6 +53,7 @@ import org.apache.pinot.segment.local.data.manager.SegmentDataManager; import org.apache.pinot.segment.local.dedup.PartitionDedupMetadataManager; import org.apache.pinot.segment.local.dedup.TableDedupMetadataManager; +import org.apache.pinot.segment.local.dedup.TableDedupMetadataManagerFactory; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentImpl; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentStatsHistory; @@ -87,8 +85,6 @@ @ThreadSafe public class RealtimeTableDataManager extends BaseTableDataManager { - private final ExecutorService _segmentAsyncExecutorService = - Executors.newSingleThreadExecutor(new NamedThreadFactory("SegmentAsyncExecutorService")); private SegmentBuildTimeLeaseExtender _leaseExtender; private RealtimeSegmentStatsHistory _statsHistory; private final Semaphore _segmentBuildSemaphore; @@ -119,13 +115,17 @@ public class RealtimeTableDataManager extends BaseTableDataManager { // likely that we get fresh data each time instead of multiple copies of roughly same data. private static final int MIN_INTERVAL_BETWEEN_STATS_UPDATES_MINUTES = 30; - private final AtomicBoolean _allSegmentsLoaded = new AtomicBoolean(); + public static final long READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5); + + // TODO: Change it to BooleanSupplier + private final Supplier _isServerReadyToServeQueries; - private TableDedupMetadataManager _tableDedupMetadataManager; - private TableUpsertMetadataManager _tableUpsertMetadataManager; // Object to track ingestion delay for all partitions private IngestionDelayTracker _ingestionDelayTracker; - private final Supplier _isServerReadyToServeQueries; + + private TableDedupMetadataManager _tableDedupMetadataManager; + private TableUpsertMetadataManager _tableUpsertMetadataManager; + private BooleanSupplier _isTableReadyToConsumeData; public RealtimeTableDataManager(Semaphore segmentBuildSemaphore) { this(segmentBuildSemaphore, () -> true); @@ -140,8 +140,8 @@ public RealtimeTableDataManager(Semaphore segmentBuildSemaphore, Supplier primaryKeyColumns = schema.getPrimaryKeyColumns(); Preconditions.checkState(!CollectionUtils.isEmpty(primaryKeyColumns), "Primary key columns must be configured for dedup"); - _tableDedupMetadataManager = new TableDedupMetadataManager(_tableNameWithType, primaryKeyColumns, _serverMetrics, - dedupConfig.getHashFunction()); + _tableDedupMetadataManager = TableDedupMetadataManagerFactory.create(tableConfig, schema, this, _serverMetrics); } UpsertConfig upsertConfig = tableConfig.getUpsertConfig(); @@ -206,7 +205,41 @@ protected void doInit() { _tableUpsertMetadataManager); Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); Preconditions.checkState(schema != null, "Failed to find schema for table: %s", _tableNameWithType); - _tableUpsertMetadataManager = TableUpsertMetadataManagerFactory.create(tableConfig, schema, this, _serverMetrics); + // NOTE: Set _tableUpsertMetadataManager before initializing it because when preloading is enabled, we need to + // load segments into it + _tableUpsertMetadataManager = TableUpsertMetadataManagerFactory.create(tableConfig); + _tableUpsertMetadataManager.init(tableConfig, schema, this, _serverMetrics, _helixManager, + _segmentPreloadExecutor); + } + + // For dedup and partial-upsert, need to wait for all segments loaded before starting consuming data + if (isDedupEnabled() || isPartialUpsertEnabled()) { + _isTableReadyToConsumeData = new BooleanSupplier() { + volatile boolean _allSegmentsLoaded; + long _lastCheckTimeMs; + + @Override + public boolean getAsBoolean() { + if (_allSegmentsLoaded) { + return true; + } else { + synchronized (this) { + if (_allSegmentsLoaded) { + return true; + } + long currentTimeMs = System.currentTimeMillis(); + if (currentTimeMs - _lastCheckTimeMs <= READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS) { + return false; + } + _lastCheckTimeMs = currentTimeMs; + _allSegmentsLoaded = TableStateUtils.isAllSegmentsLoaded(_helixManager, _tableNameWithType); + return _allSegmentsLoaded; + } + } + } + }; + } else { + _isTableReadyToConsumeData = () -> true; } } @@ -216,16 +249,21 @@ protected void doStart() { @Override protected void doShutdown() { - _segmentAsyncExecutorService.shutdown(); if (_tableUpsertMetadataManager != null) { + // Stop the upsert metadata manager first to prevent removing metadata when destroying segments + _tableUpsertMetadataManager.stop(); + for (SegmentDataManager segmentDataManager : _segmentDataManagerMap.values()) { + segmentDataManager.destroy(); + } try { _tableUpsertMetadataManager.close(); } catch (IOException e) { _logger.warn("Cannot close upsert metadata manager properly for table: {}", _tableNameWithType, e); } - } - for (SegmentDataManager segmentDataManager : _segmentDataManagerMap.values()) { - segmentDataManager.destroy(); + } else { + for (SegmentDataManager segmentDataManager : _segmentDataManagerMap.values()) { + segmentDataManager.destroy(); + } } if (_leaseExtender != null) { _leaseExtender.shutDown(); @@ -240,8 +278,8 @@ protected void doShutdown() { * @param ingestionTimeMs Ingestion delay being reported. * @param partitionGroupId Partition ID for which delay is being updated. */ - public void updateIngestionDelay(long ingestionTimeMs, int partitionGroupId) { - _ingestionDelayTracker.updateIngestionDelay(ingestionTimeMs, partitionGroupId); + public void updateIngestionDelay(long ingestionTimeMs, long firstStreamIngestionTimeMs, int partitionGroupId) { + _ingestionDelayTracker.updateIngestionDelay(ingestionTimeMs, firstStreamIngestionTimeMs, partitionGroupId); } /* @@ -271,7 +309,7 @@ public void onConsumingToOnline(String segmentNameStr) { /** * Returns all partitionGroupIds for the partitions hosted by this server for current table. - * @apiNote this involves Zookeeper read and should not be used frequently due to efficiency concerns. + * @apiNote this involves Zookeeper read and should not be used frequently due to efficiency concerns. */ public Set getHostedPartitionsGroupIds() { Set partitionsHostedByThisServer = new HashSet<>(); @@ -407,22 +445,10 @@ public void addSegment(String segmentName, IndexLoadingConfig indexLoadingConfig PartitionDedupMetadataManager partitionDedupMetadataManager = _tableDedupMetadataManager != null ? _tableDedupMetadataManager.getOrCreatePartitionManager(partitionGroupId) : null; - // For dedup and partial-upsert, wait for all segments loaded before creating the consuming segment - if (isDedupEnabled() || isPartialUpsertEnabled()) { - if (!_allSegmentsLoaded.get()) { - synchronized (_allSegmentsLoaded) { - if (!_allSegmentsLoaded.get()) { - TableStateUtils.waitForAllSegmentsLoaded(_helixManager, _tableNameWithType); - _allSegmentsLoaded.set(true); - } - } - } - } - segmentDataManager = new LLRealtimeSegmentDataManager(segmentZKMetadata, tableConfig, this, _indexDir.getAbsolutePath(), indexLoadingConfig, schema, llcSegmentName, semaphore, _serverMetrics, partitionUpsertMetadataManager, - partitionDedupMetadataManager); + partitionDedupMetadataManager, _isTableReadyToConsumeData); } else { InstanceZKMetadata instanceZKMetadata = ZKMetadataProvider.getInstanceZKMetadata(_propertyStore, _instanceId); segmentDataManager = new HLRealtimeSegmentDataManager(segmentZKMetadata, tableConfig, instanceZKMetadata, this, @@ -511,7 +537,11 @@ private void handleUpsert(ImmutableSegment immutableSegment) { ImmutableSegmentDataManager newSegmentManager = new ImmutableSegmentDataManager(immutableSegment); SegmentDataManager oldSegmentManager = registerSegment(segmentName, newSegmentManager); if (oldSegmentManager == null) { - partitionUpsertMetadataManager.addSegment(immutableSegment); + if (_tableUpsertMetadataManager.isPreloading()) { + partitionUpsertMetadataManager.preloadSegment(immutableSegment); + } else { + partitionUpsertMetadataManager.addSegment(immutableSegment); + } _logger.info("Added new immutable segment: {} to upsert-enabled table: {}", segmentName, _tableNameWithType); } else { IndexSegment oldSegment = oldSegmentManager.getSegment(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java index 5a52103efcf..297f30482be 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java @@ -69,16 +69,18 @@ public SegmentCommitter createSegmentCommitter(boolean isSplitCommit, SegmentCom boolean uploadToFs = _streamConfig.isServerUploadToDeepStore(); String peerSegmentDownloadScheme = _tableConfig.getValidationConfig().getPeerSegmentDownloadScheme(); - // TODO: exists for backwards compatibility. remove peerDownloadScheme non-null check once users have migrated + String segmentStoreUri = _indexLoadingConfig.getSegmentStoreURI(); + if (uploadToFs || peerSegmentDownloadScheme != null) { - segmentUploader = new PinotFSSegmentUploader(_indexLoadingConfig.getSegmentStoreURI(), - PinotFSSegmentUploader.DEFAULT_SEGMENT_UPLOAD_TIMEOUT_MILLIS); + // TODO: peer scheme non-null check exists for backwards compatibility. remove check once users have migrated + segmentUploader = new PinotFSSegmentUploader(segmentStoreUri, + ServerSegmentCompletionProtocolHandler.getSegmentUploadRequestTimeoutMs(), _serverMetrics); } else { segmentUploader = new Server2ControllerSegmentUploader(_logger, _protocolHandler.getFileUploadDownloadClient(), _protocolHandler.getSegmentCommitUploadURL(params, controllerVipUrl), params.getSegmentName(), ServerSegmentCompletionProtocolHandler.getSegmentUploadRequestTimeoutMs(), _serverMetrics, - _protocolHandler.getAuthProvider()); + _protocolHandler.getAuthProvider(), _tableConfig.getTableName()); } return new SplitSegmentCommitter(_logger, _protocolHandler, params, segmentUploader, peerSegmentDownloadScheme); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java index 44d0a360679..63bbe99a5bf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java @@ -24,6 +24,15 @@ public interface SegmentUploader { - // Returns the URI of the uploaded segment. null if the upload fails. + + /** + * Uploads the given segmentFile to the deep-store. Returns the URI where the segment is uploaded. + */ URI uploadSegment(File segmentFile, LLCSegmentName segmentName); + + /** + * Uploads the given segmentFile to the deep-store. Returns the URI where the segment is uploaded. The upload will + * wait for the specified timeout. + */ + URI uploadSegment(File segmentFile, LLCSegmentName segmentName, int timeoutInMillis); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java index 2559616b49f..4b00563125a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java @@ -21,14 +21,18 @@ import java.io.File; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; import org.apache.pinot.common.auth.AuthProviderUtils; +import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.metrics.ServerTimer; import org.apache.pinot.common.protocols.SegmentCompletionProtocol; import org.apache.pinot.common.utils.FileUploadDownloadClient; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.core.util.SegmentCompletionProtocolUtils; import org.apache.pinot.server.realtime.ControllerLeaderLocator; import org.apache.pinot.spi.auth.AuthProvider; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; @@ -42,10 +46,11 @@ public class Server2ControllerSegmentUploader implements SegmentUploader { private final int _segmentUploadRequestTimeoutMs; private final ServerMetrics _serverMetrics; private final AuthProvider _authProvider; + private final String _rawTableName; public Server2ControllerSegmentUploader(Logger segmentLogger, FileUploadDownloadClient fileUploadDownloadClient, String controllerSegmentUploadCommitUrl, String segmentName, int segmentUploadRequestTimeoutMs, - ServerMetrics serverMetrics, AuthProvider authProvider) + ServerMetrics serverMetrics, AuthProvider authProvider, String tableName) throws URISyntaxException { _segmentLogger = segmentLogger; _fileUploadDownloadClient = fileUploadDownloadClient; @@ -54,27 +59,41 @@ public Server2ControllerSegmentUploader(Logger segmentLogger, FileUploadDownload _segmentUploadRequestTimeoutMs = segmentUploadRequestTimeoutMs; _serverMetrics = serverMetrics; _authProvider = authProvider; + _rawTableName = TableNameBuilder.extractRawTableName(tableName); } @Override public URI uploadSegment(File segmentFile, LLCSegmentName segmentName) { - SegmentCompletionProtocol.Response response = uploadSegmentToController(segmentFile); + return uploadSegment(segmentFile, segmentName, _segmentUploadRequestTimeoutMs); + } + + @Override + public URI uploadSegment(File segmentFile, LLCSegmentName segmentName, int timeoutInMillis) { + SegmentCompletionProtocol.Response response = uploadSegmentToController(segmentFile, timeoutInMillis); if (response.getStatus() == SegmentCompletionProtocol.ControllerResponseStatus.UPLOAD_SUCCESS) { try { - return new URI(response.getSegmentLocation()); + URI uri = new URI(response.getSegmentLocation()); + _serverMetrics.addMeteredTableValue(_rawTableName, ServerMeter.SEGMENT_UPLOAD_SUCCESS, 1); + return uri; } catch (URISyntaxException e) { _segmentLogger.error("Error in segment location format: ", e); } } + _serverMetrics.addMeteredTableValue(_rawTableName, ServerMeter.SEGMENT_UPLOAD_FAILURE, 1); return null; } public SegmentCompletionProtocol.Response uploadSegmentToController(File segmentFile) { + return uploadSegmentToController(segmentFile, _segmentUploadRequestTimeoutMs); + } + + private SegmentCompletionProtocol.Response uploadSegmentToController(File segmentFile, int timeoutInMillis) { SegmentCompletionProtocol.Response response; + long startTime = System.currentTimeMillis(); try { String responseStr = _fileUploadDownloadClient .uploadSegment(_controllerSegmentUploadCommitUrl, _segmentName, segmentFile, - AuthProviderUtils.toRequestHeaders(_authProvider), null, _segmentUploadRequestTimeoutMs).getResponse(); + AuthProviderUtils.toRequestHeaders(_authProvider), null, timeoutInMillis).getResponse(); response = SegmentCompletionProtocol.Response.fromJsonString(responseStr); _segmentLogger.info("Controller response {} for {}", response.toJsonString(), _controllerSegmentUploadCommitUrl); if (response.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.NOT_LEADER)) { @@ -88,6 +107,10 @@ public SegmentCompletionProtocol.Response uploadSegmentToController(File segment // hence unable to send {@link SegmentCompletionProtocol.ControllerResponseStatus.NOT_LEADER} // If cache is not invalidated, we will not recover from exceptions until the controller comes back up ControllerLeaderLocator.getInstance().invalidateCachedControllerLeader(); + } finally { + long duration = System.currentTimeMillis() - startTime; + _serverMetrics.addTimedTableValue(_rawTableName, ServerTimer.SEGMENT_UPLOAD_TIME_MS, duration, + TimeUnit.MILLISECONDS); } SegmentCompletionProtocolUtils.raiseSegmentCompletionProtocolResponseMetric(_serverMetrics, response); return response; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java b/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java index cbbe6abdce5..4299e5665ee 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java @@ -86,10 +86,12 @@ public TableResizer(DataSchema dataSchema, QueryContext queryContext) { _numOrderByExpressions = orderByExpressions.size(); _orderByValueExtractors = new OrderByValueExtractor[_numOrderByExpressions]; Comparator[] comparators = new Comparator[_numOrderByExpressions]; + int[] nullComparisonResults = new int[_numOrderByExpressions]; for (int i = 0; i < _numOrderByExpressions; i++) { OrderByExpressionContext orderByExpression = orderByExpressions.get(i); _orderByValueExtractors[i] = getOrderByValueExtractor(orderByExpression.getExpression()); comparators[i] = orderByExpression.isAsc() ? Comparator.naturalOrder() : Comparator.reverseOrder(); + nullComparisonResults[i] = orderByExpression.isNullsLast() ? -1 : 1; } boolean nullHandlingEnabled = queryContext.isNullHandlingEnabled(); if (nullHandlingEnabled) { @@ -101,10 +103,9 @@ public TableResizer(DataSchema dataSchema, QueryContext queryContext) { if (v2 == null) { continue; } - // The default null ordering is NULLS LAST, regardless of the ordering direction. - return 1; + return -nullComparisonResults[i]; } else if (v2 == null) { - return -1; + return nullComparisonResults[i]; } int result = comparators[i].compare(v1, v2); if (result != 0) { @@ -131,7 +132,7 @@ public TableResizer(DataSchema dataSchema, QueryContext queryContext) { */ private OrderByValueExtractor getOrderByValueExtractor(ExpressionContext expression) { if (expression.getType() == ExpressionContext.Type.LITERAL) { - return new LiteralExtractor(expression.getLiteralString()); + return new LiteralExtractor(expression.getLiteral().getStringValue()); } Integer groupByExpressionIndex = _groupByExpressionIndexMap.get(expression); if (groupByExpressionIndex != null) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/function/scalar/SketchFunctions.java b/pinot-core/src/main/java/org/apache/pinot/core/function/scalar/SketchFunctions.java new file mode 100644 index 00000000000..f6245bec6f7 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/function/scalar/SketchFunctions.java @@ -0,0 +1,187 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.function.scalar; + +import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import java.math.BigDecimal; +import javax.annotation.Nullable; +import org.apache.datasketches.theta.Sketches; +import org.apache.datasketches.theta.UpdateSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.spi.annotations.ScalarFunction; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * Inbuilt Sketch Transformation Functions + * The functions can be used as UDFs in Query when added in the FunctionRegistry. + * @ScalarFunction annotation is used with each method for the registration + * + * Note these will just make sketches that contain a single item, these are intended to be used during ingestion to + * create sketches from raw data, which can be rolled up later. + * + * Note this is defined in pinot-core rather than pinot-common because pinot-core has dependencies on + * datasketches/clearspring analytics. + * + * Example usage: + * + * { + * "transformConfigs": [ + * { + * "columnName": "players", + * "transformFunction": "toThetaSketch(playerID)" + * }, + * { + * "columnName": "players", + * "transformFunction": "toThetaSketch(playerID, 1024)" + * }, + * { + * "columnName": "names", + * "transformFunction": "toHLL(playerName)" + * }, + * { + * "columnName": "names", + * "transformFunction": "toHLL(playerName, 8)" + * } + * ] + * } + */ +public class SketchFunctions { + private SketchFunctions() { + } + + /** + * Create a Theta Sketch containing the input + * + * @param input an Object we want to insert into the sketch, may be null to return an empty sketch + * @return serialized theta sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toThetaSketch(@Nullable Object input) { + return toThetaSketch(input, CommonConstants.Helix.DEFAULT_THETA_SKETCH_NOMINAL_ENTRIES); + } + + /** + * Create a Theta Sketch containing the input, with a configured nominal entries + * + * @param input an Object we want to insert into the sketch, may be null to return an empty sketch + * @param nominalEntries number of nominal entries the sketch is configured to keep + * @return serialized theta sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toThetaSketch(@Nullable Object input, int nominalEntries) { + UpdateSketch sketch = Sketches.updateSketchBuilder().setNominalEntries(nominalEntries).build(); + if (input != null) { + if (input instanceof Integer) { + sketch.update((Integer) input); + } else if (input instanceof Long) { + sketch.update((Long) input); + } else if (input instanceof Float) { + sketch.update((Float) input); + } else if (input instanceof Double) { + sketch.update((Double) input); + } else if (input instanceof BigDecimal) { + sketch.update(((BigDecimal) input).toString()); + } else if (input instanceof String) { + sketch.update((String) input); + } else if (input instanceof byte[]) { + sketch.update((byte[]) input); + } else { + throw new IllegalArgumentException("Unrecognised input type for Theta sketch: " + input.getClass().getName()); + } + } + return ObjectSerDeUtils.DATA_SKETCH_SER_DE.serialize(sketch.compact()); + } + + /** + * Create a HyperLogLog containing the input + * + * @param input an Object we want to insert into the HLL, may be null to return an empty HLL + * @return serialized HLL as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toHLL(@Nullable Object input) { + return toHLL(input, CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M); + } + + /** + * Create a HyperLogLog containing the input, with a configurable log2m + * + * @param input an Object we want to insert into the HLL, may be null to return an empty HLL + * @param log2m the log2m value for the created HyperLogLog + * @return serialized HLL as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toHLL(@Nullable Object input, int log2m) { + HyperLogLog hll = new HyperLogLog(log2m); + if (input != null) { + hll.offer(input); + } + return ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.serialize(hll); + } + + /** + * Create a Tuple Sketch containing the key and value supplied + * + * @param key an Object we want to insert as the key of the sketch, may be null to return an empty sketch + * @param value an Integer we want to associate as the value to go along with the key, may be null to return an + * empty sketch + * @return serialized tuple sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toIntegerSumTupleSketch(@Nullable Object key, @Nullable Integer value) { + return toIntegerSumTupleSketch(key, value, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK); + } + + /** + * Create a Tuple Sketch containing the key and value supplied + * + * @param key an Object we want to insert as the key of the sketch, may be null to return an empty sketch + * @param value an Integer we want to associate as the value to go along with the key, may be null to return an + * empty sketch + * @param lgK integer representing the log of the maximum number of retained entries in the sketch, between 4 and 26 + * @return serialized tuple sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toIntegerSumTupleSketch(@Nullable Object key, Integer value, int lgK) { + IntegerSketch is = new IntegerSketch(lgK, IntegerSummary.Mode.Sum); + if (value != null && key != null) { + if (key instanceof Integer) { + is.update((Integer) key, value); + } else if (key instanceof Long) { + is.update((Long) key, value); + } else if (key instanceof Float) { + is.update((float) key, value); + } else if (key instanceof Double) { + is.update((double) key, value); + } else if (key instanceof BigDecimal) { + is.update(((BigDecimal) key).toString(), value); + } else if (key instanceof String) { + is.update((String) key, value); + } else if (key instanceof byte[]) { + is.update((byte[]) key, value); + } else { + throw new IllegalArgumentException("Unrecognised key type for Theta sketch: " + key.getClass().getName()); + } + } + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(is.compact()); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java index c528cedb51d..347abeb0f02 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java @@ -22,13 +22,13 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.utils.BytesUtils; import org.locationtech.jts.geom.Geometry; @@ -46,20 +46,18 @@ public abstract class BaseBinaryGeoTransformFunction extends BaseTransformFuncti private double[] _doubleResults; @Override - public void init(List arguments, Map dataSourceMap) { - Preconditions - .checkArgument(arguments.size() == 2, "2 arguments are required for transform function: %s", getName()); + public void init(List arguments, Map columnContextMap) { + Preconditions.checkArgument(arguments.size() == 2, "2 arguments are required for transform function: %s", + getName()); TransformFunction transformFunction = arguments.get(0); Preconditions.checkArgument(transformFunction.getResultMetadata().isSingleValue(), "First argument must be single-valued for transform function: %s", getName()); Preconditions.checkArgument(transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.BYTES || transformFunction instanceof LiteralTransformFunction, - "The first argument must be of type BYTES , but was %s", - transformFunction.getResultMetadata().getDataType() - ); + "The first argument must be of type BYTES , but was %s", transformFunction.getResultMetadata().getDataType()); if (transformFunction instanceof LiteralTransformFunction) { _firstLiteral = GeometrySerializer.deserialize( - BytesUtils.toBytes(((LiteralTransformFunction) transformFunction).getLiteral())); + BytesUtils.toBytes(((LiteralTransformFunction) transformFunction).getStringLiteral())); } else { _firstArgument = transformFunction; } @@ -68,40 +66,38 @@ public void init(List arguments, Map data "Second argument must be single-valued for transform function: %s", getName()); Preconditions.checkArgument(transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.BYTES || transformFunction instanceof LiteralTransformFunction, - "The second argument must be of type BYTES , but was %s", - transformFunction.getResultMetadata().getDataType() - ); + "The second argument must be of type BYTES , but was %s", transformFunction.getResultMetadata().getDataType()); if (transformFunction instanceof LiteralTransformFunction) { _secondLiteral = GeometrySerializer.deserialize( - BytesUtils.toBytes(((LiteralTransformFunction) transformFunction).getLiteral())); + BytesUtils.toBytes(((LiteralTransformFunction) transformFunction).getStringLiteral())); } else { _secondArgument = transformFunction; } } - protected int[] transformGeometryToIntValuesSV(ProjectionBlock projectionBlock) { + protected int[] transformGeometryToIntValuesSV(ValueBlock valueBlock) { if (_intResults == null) { _intResults = new int[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } byte[][] firstValues; byte[][] secondValues; if (_firstArgument == null && _secondArgument == null) { - _intResults = new int[Math.min(projectionBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; + _intResults = new int[Math.min(valueBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; Arrays.fill(_intResults, transformGeometryToInt(_firstLiteral, _secondLiteral)); } else if (_firstArgument == null) { - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _intResults[i] = transformGeometryToInt(_firstLiteral, GeometrySerializer.deserialize(secondValues[i])); } } else if (_secondArgument == null) { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _intResults[i] = transformGeometryToInt(GeometrySerializer.deserialize(firstValues[i]), _secondLiteral); } } else { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _intResults[i] = transformGeometryToInt(GeometrySerializer.deserialize(firstValues[i]), GeometrySerializer.deserialize(secondValues[i])); } @@ -109,29 +105,29 @@ protected int[] transformGeometryToIntValuesSV(ProjectionBlock projectionBlock) return _intResults; } - protected double[] transformGeometryToDoubleValuesSV(ProjectionBlock projectionBlock) { + protected double[] transformGeometryToDoubleValuesSV(ValueBlock valueBlock) { if (_doubleResults == null) { _doubleResults = new double[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } byte[][] firstValues; byte[][] secondValues; if (_firstArgument == null && _secondArgument == null) { - _doubleResults = new double[Math.min(projectionBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; + _doubleResults = new double[Math.min(valueBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; Arrays.fill(_doubleResults, transformGeometryToDouble(_firstLiteral, _secondLiteral)); } else if (_firstArgument == null) { - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _doubleResults[i] = transformGeometryToDouble(_firstLiteral, GeometrySerializer.deserialize(secondValues[i])); } } else if (_secondArgument == null) { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _doubleResults[i] = transformGeometryToDouble(GeometrySerializer.deserialize(firstValues[i]), _secondLiteral); } } else { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _doubleResults[i] = transformGeometryToDouble(GeometrySerializer.deserialize(firstValues[i]), GeometrySerializer.deserialize(secondValues[i])); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java index d0ff874aee3..189aed33cf3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java @@ -22,13 +22,13 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.Utils; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.ParseException; @@ -44,7 +44,7 @@ abstract class ConstructFromTextFunction extends BaseTransformFunction { protected WKTReader _reader; @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -64,12 +64,12 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - String[] argumentValues = _transformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] argumentValues = _transformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { try { Geometry geometry = _reader.read(argumentValues[i]); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java index 761fe06a927..29ac9f8244e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java @@ -21,13 +21,13 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.utils.BytesUtils; import org.locationtech.jts.geom.Geometry; @@ -44,7 +44,7 @@ abstract class ConstructFromWKBFunction extends BaseTransformFunction { private WKBReader _reader; @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -64,12 +64,12 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - byte[][] argumentValues = _transformFunction.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + byte[][] argumentValues = _transformFunction.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { try { Geometry geometry = _reader.read(argumentValues[i]); _results[i] = GeometrySerializer.serialize(geometry); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java index 75f46c7b2cc..c716b6e677b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java @@ -21,14 +21,14 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -51,7 +51,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions.checkArgument(arguments.size() == 3 || arguments.size() == 2, "Transform function %s requires 2 or 3 arguments", getName()); if (arguments.size() == 3) { @@ -95,23 +95,23 @@ public TransformResultMetadata getResultMetadata() { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new long[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } if (_thirdArgument == null) { - byte[][] geoValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - int[] resValues = _secondArgument.transformToIntValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + byte[][] geoValues = _firstArgument.transformToBytesValuesSV(valueBlock); + int[] resValues = _secondArgument.transformToIntValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { Geometry geometry = GeometrySerializer.deserialize(geoValues[i]); _results[i] = ScalarFunctions.geoToH3(geometry.getCoordinate().x, geometry.getCoordinate().y, resValues[i]); } } else { - double[] lonValues = _firstArgument.transformToDoubleValuesSV(projectionBlock); - double[] latValues = _secondArgument.transformToDoubleValuesSV(projectionBlock); - int[] resValues = _thirdArgument.transformToIntValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + double[] lonValues = _firstArgument.transformToDoubleValuesSV(valueBlock); + double[] latValues = _secondArgument.transformToDoubleValuesSV(valueBlock); + int[] resValues = _thirdArgument.transformToIntValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _results[i] = ScalarFunctions.geoToH3(lonValues[i], latValues[i], resValues[i]); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java index 5ac874335c8..5102582024d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java @@ -21,7 +21,8 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; @@ -29,7 +30,6 @@ import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; @@ -57,7 +57,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions .checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -76,13 +76,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new double[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Geometry geometry = GeometrySerializer.deserialize(values[i]); _results[i] = GeometryUtils.isGeography(geometry) ? calculateGeographyArea(geometry) : geometry.getArea(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java index 66e399925d8..4c9da0c2aa2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java @@ -21,14 +21,14 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -48,7 +48,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -65,13 +65,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); Geometry geometry; - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + for (int i = 0; i < valueBlock.getNumDocs(); i++) { geometry = GeometrySerializer.deserialize(values[i]); _results[i] = GeometryUtils.WKB_WRITER.write(geometry); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java index 4f277fb3232..75e8f9acd4c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java @@ -21,14 +21,14 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -48,7 +48,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -65,13 +65,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new String[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); Geometry geometry; - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + for (int i = 0; i < valueBlock.getNumDocs(); i++) { geometry = GeometrySerializer.deserialize(values[i]); _results[i] = GeometryUtils.WKT_WRITER.write(geometry); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java index 210215c7e6d..1045da58bb9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.geospatial.transform.function; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.local.utils.GeometryUtils; import org.locationtech.jts.geom.Geometry; @@ -43,8 +43,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return transformGeometryToIntValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java index a28b0ad40a9..1802d7698ac 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java @@ -19,7 +19,7 @@ package org.apache.pinot.core.geospatial.transform.function; import com.google.common.base.Preconditions; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.local.utils.GeometryUtils; import org.locationtech.jts.geom.Geometry; @@ -49,8 +49,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToDoubleValuesSV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + return transformGeometryToDoubleValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java index 45b4f269e1a..73d5e06c2a0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.geospatial.transform.function; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.locationtech.jts.geom.Geometry; @@ -40,8 +40,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return transformGeometryToIntValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java index 27b1a203a1a..4fa9bf814f9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java @@ -21,13 +21,13 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -46,7 +46,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions .checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -63,13 +63,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new String[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); Geometry geometry; - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + for (int i = 0; i < valueBlock.getNumDocs(); i++) { geometry = GeometrySerializer.deserialize(values[i]); _results[i] = geometry.getGeometryType(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java index a780c475017..b1bb8424845 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java @@ -21,14 +21,13 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.spi.utils.BooleanUtils; /** @@ -47,7 +46,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { Preconditions.checkArgument(arguments.size() == 2 || arguments.size() == 3, "2 or 3 arguments are required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -62,7 +61,7 @@ public void init(List arguments, Map data transformFunction = arguments.get(2); Preconditions.checkArgument(transformFunction instanceof LiteralTransformFunction, "Third argument must be a literal of integer: %s", getName()); - _isGeography = BooleanUtils.toBoolean(((LiteralTransformFunction) transformFunction).getLiteral()); + _isGeography = ((LiteralTransformFunction) transformFunction).getBooleanLiteral(); } } @@ -72,13 +71,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - double[] firstValues = _firstArgument.transformToDoubleValuesSV(projectionBlock); - double[] secondValues = _secondArgument.transformToDoubleValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + double[] firstValues = _firstArgument.transformToDoubleValuesSV(valueBlock); + double[] secondValues = _secondArgument.transformToDoubleValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _results[i] = ScalarFunctions.stPoint(firstValues[i], secondValues[i], _isGeography); } return _results; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java index 6f477662d2f..7680eafa535 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java @@ -19,7 +19,7 @@ package org.apache.pinot.core.geospatial.transform.function; import com.google.common.base.Preconditions; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; @@ -46,12 +46,12 @@ public String getName() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - String[] argumentValues = _transformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] argumentValues = _transformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { try { Geometry geometry = _reader.read(argumentValues[i]); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java index 474af0d2280..60b76ac65ad 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.geospatial.transform.function; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.local.utils.GeometryUtils; import org.locationtech.jts.geom.Geometry; @@ -42,8 +42,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return transformGeometryToIntValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java b/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java index 2b655085859..4faf6955220 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java @@ -28,9 +28,11 @@ import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.data.readers.RecordReader; import org.apache.pinot.spi.data.readers.RecordReaderConfig; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,19 +47,21 @@ public class SegmentPurger { private final File _indexDir; private final File _workingDir; private final TableConfig _tableConfig; + private final Schema _schema; private final RecordPurger _recordPurger; private final RecordModifier _recordModifier; private int _numRecordsPurged; private int _numRecordsModified; - public SegmentPurger(File indexDir, File workingDir, TableConfig tableConfig, @Nullable RecordPurger recordPurger, - @Nullable RecordModifier recordModifier) { + public SegmentPurger(File indexDir, File workingDir, TableConfig tableConfig, Schema schema, + @Nullable RecordPurger recordPurger, @Nullable RecordModifier recordModifier) { Preconditions.checkArgument(recordPurger != null || recordModifier != null, "At least one of record purger and modifier should be non-null"); _indexDir = indexDir; _workingDir = workingDir; _tableConfig = tableConfig; + _schema = schema; _recordPurger = recordPurger; _recordModifier = recordModifier; } @@ -79,7 +83,7 @@ public File purgeSegment() return null; } - SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, segmentMetadata.getSchema()); + SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, _schema); config.setOutDir(_workingDir.getPath()); config.setSegmentName(segmentName); @@ -227,6 +231,13 @@ public interface RecordPurgerFactory { * Get the {@link RecordPurger} for the given table. */ RecordPurger getRecordPurger(String rawTableName); + + /** + * Get the {@link RecordPurger} associated with the given taskConfig, tableConfig and tableSchema + */ + default RecordPurger getRecordPurger(PinotTaskConfig taskConfig, TableConfig tableConfig, Schema tableSchema) { + return getRecordPurger(TableNameBuilder.extractRawTableName(tableConfig.getTableName())); + } } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java index 4d79eeea4ab..2b722924314 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.plan.PlanNode; @@ -54,12 +55,16 @@ public AcquireReleaseColumnsSegmentOperator(PlanNode planNode, IndexSegment inde _fetchContext = fetchContext; } + public void materializeChildOperator() { + _childOperator = (Operator) _planNode.run(); + } + /** * Runs the planNode to get the childOperator, and then proceeds with execution. */ @Override protected BaseResultsBlock getNextBlock() { - _childOperator = (Operator) _planNode.run(); + materializeChildOperator(); return _childOperator.nextBlock(); } @@ -69,7 +74,7 @@ protected BaseResultsBlock getNextBlock() { public void acquire() { // do not acquire if interrupted (similar to the check inside the nextBlock()) if (Thread.interrupted()) { - throw new EarlyTerminationException(); + throw new EarlyTerminationException("Interrupted while acquiring segment"); } _indexSegment.acquire(_fetchContext); } @@ -81,6 +86,11 @@ public void release() { _indexSegment.release(_fetchContext); } + @Override + public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + acquire(); + materializeChildOperator(); + } @Override public String toExplainString() { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java index b0694bcd75f..c9ab35457d8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java @@ -37,7 +37,7 @@ public final T nextBlock() { itself will cancel all worker's futures. Therefore, the worker will interrupt even if we only kill the runner thread. */ if (Tracing.ThreadAccountantOps.isInterrupted()) { - throw new EarlyTerminationException(); + throw new EarlyTerminationException("Interrupted while processing next block"); } try (InvocationScope ignored = Tracing.getTracer().createScope(getClass())) { return getNextBlock(); diff --git a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/provider/JsonIndexReaderProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseProjectOperator.java similarity index 52% rename from pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/provider/JsonIndexReaderProvider.java rename to pinot-core/src/main/java/org/apache/pinot/core/operator/BaseProjectOperator.java index 8f048613b82..aaaa8cc4155 100644 --- a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/provider/JsonIndexReaderProvider.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseProjectOperator.java @@ -16,23 +16,29 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.segment.spi.index.reader.provider; +package org.apache.pinot.core.operator; -import java.io.IOException; -import org.apache.pinot.segment.spi.ColumnMetadata; -import org.apache.pinot.segment.spi.index.reader.JsonIndexReader; -import org.apache.pinot.segment.spi.memory.PinotDataBuffer; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; -public interface JsonIndexReaderProvider { +public abstract class BaseProjectOperator extends BaseOperator { /** - * Creates a {@see JsonIndexReader} - * @param dataBuffer the buffer, the caller is responsible for closing it - * @param metadata the column metadata, may be used to select a reader if the buffer does not start with a magic byte. - * @return a JSON index reader - * @throws IOException if reading from the buffer fails. + * Returns a map from source column name to context. */ - JsonIndexReader newJsonIndexReader(PinotDataBuffer dataBuffer, ColumnMetadata metadata) - throws IOException; + public abstract Map getSourceColumnContextMap(); + + /** + * Returns the result column context. Without transform, the source and result column context are the same. + */ + public abstract ColumnContext getResultColumnContext(ExpressionContext expression); + + /** + * Returns the number of columns projected. + */ + public int getNumColumnsProjected() { + return getSourceColumnContextMap().size(); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java index b251c552d89..4f203599c1e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java @@ -52,6 +52,11 @@ public BitmapDocIdSetOperator(ImmutableBitmapDataProvider bitmap, int numDocs) { _docIdBuffer = new int[Math.min(numDocs, DocIdSetPlanNode.MAX_DOC_PER_CALL)]; } + public BitmapDocIdSetOperator(IntIterator intIterator, int[] docIdBuffer) { + _intIterator = intIterator; + _docIdBuffer = docIdBuffer; + } + public BitmapDocIdSetOperator(ImmutableBitmapDataProvider bitmap, int[] docIdBuffer) { _intIterator = bitmap.getIntIterator(); _docIdBuffer = docIdBuffer; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/ColumnContext.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/ColumnContext.java new file mode 100644 index 00000000000..878ef0b3f95 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/ColumnContext.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator; + +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.core.operator.transform.function.TransformFunction; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; + + +public class ColumnContext { + private final DataType _dataType; + private final boolean _isSingleValue; + private final Dictionary _dictionary; + private final DataSource _dataSource; + + private ColumnContext(DataType dataType, boolean isSingleValue, @Nullable Dictionary dictionary, + @Nullable DataSource dataSource) { + _dataType = dataType; + _isSingleValue = isSingleValue; + _dictionary = dictionary; + _dataSource = dataSource; + } + + public DataType getDataType() { + return _dataType; + } + + public boolean isSingleValue() { + return _isSingleValue; + } + + @Nullable + public Dictionary getDictionary() { + return _dictionary; + } + + @Nullable + public DataSource getDataSource() { + return _dataSource; + } + + public static ColumnContext fromDataSource(DataSource dataSource) { + DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); + return new ColumnContext(dataSourceMetadata.getDataType(), dataSourceMetadata.isSingleValue(), + dataSource.getDictionary(), dataSource); + } + + public static ColumnContext fromTransformFunction(TransformFunction transformFunction) { + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + return new ColumnContext(resultMetadata.getDataType(), resultMetadata.isSingleValue(), + transformFunction.getDictionary(), null); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java index 6ee1314459f..2d6e9acc67d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java @@ -22,9 +22,9 @@ import java.util.Collections; import java.util.List; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.DocIdSetBlock; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.spi.Constants; @@ -45,7 +45,7 @@ public class DocIdSetOperator extends BaseOperator { private final BaseFilterOperator _filterOperator; private final int _maxSizeOfDocIdSet; - private FilterBlockDocIdSet _filterBlockDocIdSet; + private BlockDocIdSet _blockDocIdSet; private BlockDocIdIterator _blockDocIdIterator; private int _currentDocId = 0; @@ -61,11 +61,10 @@ protected DocIdSetBlock getNextBlock() { return null; } - // Initialize filter block document Id set - if (_filterBlockDocIdSet == null) { - _filterBlockDocIdSet = _filterOperator.nextBlock().getBlockDocIdSet(); - _blockDocIdIterator = _filterBlockDocIdSet.iterator(); + if (_blockDocIdSet == null) { + _blockDocIdSet = _filterOperator.nextBlock().getBlockDocIdSet(); + _blockDocIdIterator = _blockDocIdSet.iterator(); } Tracing.ThreadAccountantOps.sample(); @@ -86,7 +85,6 @@ protected DocIdSetBlock getNextBlock() { } } - @Override public String toExplainString() { return EXPLAIN_NAME; @@ -99,8 +97,7 @@ public List getChildOperators() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = - _filterBlockDocIdSet != null ? _filterBlockDocIdSet.getNumEntriesScannedInFilter() : 0; + long numEntriesScannedInFilter = _blockDocIdSet != null ? _blockDocIdSet.getNumEntriesScannedInFilter() : 0; return new ExecutionStatistics(0, numEntriesScannedInFilter, 0, 0); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java index e0ca51302fc..75f6c46103f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java @@ -20,25 +20,30 @@ import java.util.Collections; import java.util.List; +import org.apache.commons.lang.StringUtils; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; import org.apache.pinot.core.operator.combine.BaseCombineOperator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.FetchContext; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.exception.QueryCancelledException; +import org.apache.pinot.spi.trace.Tracing; public class InstanceResponseOperator extends BaseOperator { private static final String EXPLAIN_NAME = "INSTANCE_RESPONSE"; - private final BaseCombineOperator _combineOperator; - private final List _indexSegments; - private final List _fetchContexts; - private final int _fetchContextSize; - private final QueryContext _queryContext; + protected final BaseCombineOperator _combineOperator; + protected final List _indexSegments; + protected final List _fetchContexts; + protected final int _fetchContextSize; + protected final QueryContext _queryContext; public InstanceResponseOperator(BaseCombineOperator combineOperator, List indexSegments, List fetchContexts, QueryContext queryContext) { @@ -111,18 +116,23 @@ private BaseResultsBlock getCombinedResults() { try { prefetchAll(); return _combineOperator.nextBlock(); + } catch (EarlyTerminationException e) { + Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); + return new ExceptionResultsBlock(new QueryCancelledException( + "Cancelled while combining results" + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg), + e)); } finally { releaseAll(); } } - private void prefetchAll() { + public void prefetchAll() { for (int i = 0; i < _fetchContextSize; i++) { _indexSegments.get(i).prefetch(_fetchContexts.get(i)); } } - private void releaseAll() { + public void releaseAll() { for (int i = 0; i < _fetchContextSize; i++) { _indexSegments.get(i).release(_fetchContexts.get(i)); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java index fb3c26df75d..3a48d6ed929 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java @@ -19,9 +19,12 @@ package org.apache.pinot.core.operator; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.common.DataBlockCache; import org.apache.pinot.core.common.DataFetcher; import org.apache.pinot.core.common.Operator; @@ -31,28 +34,33 @@ import org.apache.pinot.spi.trace.Tracing; -public class ProjectionOperator extends BaseOperator { - +public class ProjectionOperator extends BaseProjectOperator { private static final String EXPLAIN_NAME = "PROJECT"; private final Map _dataSourceMap; private final BaseOperator _docIdSetOperator; private final DataBlockCache _dataBlockCache; + private final Map _columnContextMap; public ProjectionOperator(Map dataSourceMap, @Nullable BaseOperator docIdSetOperator) { _dataSourceMap = dataSourceMap; _docIdSetOperator = docIdSetOperator; _dataBlockCache = new DataBlockCache(new DataFetcher(dataSourceMap)); + _columnContextMap = new HashMap<>(HashUtil.getHashMapCapacity(dataSourceMap.size())); + dataSourceMap.forEach( + (column, dataSource) -> _columnContextMap.put(column, ColumnContext.fromDataSource(dataSource))); + } + + @Override + public Map getSourceColumnContextMap() { + return _columnContextMap; } - /** - * Returns the map from column to data source. - * - * @return Map from column to data source - */ - public Map getDataSourceMap() { - return _dataSourceMap; + @Override + public ColumnContext getResultColumnContext(ExpressionContext expression) { + assert expression.getType() == ExpressionContext.Type.IDENTIFIER; + return _columnContextMap.get(expression.getIdentifier()); } @Override @@ -64,14 +72,13 @@ protected ProjectionBlock getNextBlock() { return null; } else { Tracing.activeRecording().setNumChildren(_dataSourceMap.size()); - _dataBlockCache.initNewBlock(docIdSetBlock.getDocIdSet(), docIdSetBlock.getSearchableLength()); + _dataBlockCache.initNewBlock(docIdSetBlock.getDocIds(), docIdSetBlock.getLength()); return new ProjectionBlock(_dataSourceMap, _dataBlockCache); } } - @Override - public List getChildOperators() { + public List> getChildOperators() { return Collections.singletonList(_docIdSetOperator); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java index bf2528bbb88..d4bb1562769 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java @@ -19,48 +19,27 @@ package org.apache.pinot.core.operator.blocks; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.docidsets.ArrayBasedDocIdSet; +import org.apache.pinot.core.operator.DocIdSetOperator; +/** + * The {@code DocIdSetBlock} contains a block of document ids (sorted), and is returned from {@link DocIdSetOperator}. + * Each {@code DocIdSetOperator} can return multiple {@code DocIdSetBlock}s. + */ public class DocIdSetBlock implements Block { + private final int[] _docIds; + private final int _length; - private final int[] _docIdArray; - private final int _searchableLength; - - public DocIdSetBlock(int[] docIdSet, int searchableLength) { - _docIdArray = docIdSet; - _searchableLength = searchableLength; - } - - public int[] getDocIdSet() { - return _docIdArray; - } - - public int getSearchableLength() { - return _searchableLength; - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); + public DocIdSetBlock(int[] docIds, int length) { + _docIds = docIds; + _length = length; } - @Override - public BlockDocIdSet getBlockDocIdSet() { - return new ArrayBasedDocIdSet(_docIdArray, _searchableLength); + public int[] getDocIds() { + return _docIds; } - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public int getLength() { + return _length; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java index 8afdd8cd3d6..185939283bf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java @@ -19,50 +19,31 @@ package org.apache.pinot.core.operator.blocks; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; +import org.apache.pinot.core.common.BlockDocIdSet; /** * The {@code FilterBlock} class is the block holding the document Ids returned from the filter operator. */ public class FilterBlock implements Block { - private final FilterBlockDocIdSet _filterBlockDocIdSet; - private FilterBlockDocIdSet _nonScanFilterBlockDocIdSet; + private final BlockDocIdSet _blockDocIdSet; + private BlockDocIdSet _nonScanBlockDocIdSet; - public FilterBlock(FilterBlockDocIdSet filterBlockDocIdSet) { - _filterBlockDocIdSet = filterBlockDocIdSet; + public FilterBlock(BlockDocIdSet blockDocIdSet) { + _blockDocIdSet = blockDocIdSet; } /** * Pre-scans the documents if needed, and returns a non-scan-based FilterBlockDocIdSet. */ - public FilterBlockDocIdSet getNonScanFilterBLockDocIdSet() { - if (_nonScanFilterBlockDocIdSet == null) { - _nonScanFilterBlockDocIdSet = _filterBlockDocIdSet.toNonScanDocIdSet(); + public BlockDocIdSet getNonScanFilterBLockDocIdSet() { + if (_nonScanBlockDocIdSet == null) { + _nonScanBlockDocIdSet = _blockDocIdSet.toNonScanDocIdSet(); } - return _nonScanFilterBlockDocIdSet; + return _nonScanBlockDocIdSet; } - @Override - public FilterBlockDocIdSet getBlockDocIdSet() { - return _nonScanFilterBlockDocIdSet != null ? _nonScanFilterBlockDocIdSet : _filterBlockDocIdSet; - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public BlockDocIdSet getBlockDocIdSet() { + return _nonScanBlockDocIdSet != null ? _nonScanBlockDocIdSet : _blockDocIdSet; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java index 4708d5cf832..9c923ecfc7e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java @@ -28,10 +28,6 @@ import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.query.request.context.QueryContext; @@ -145,24 +141,4 @@ private void attachMetadata(DataTable dataTable) { } dataTable.getMetadata().putAll(_metadata); } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java index 066d27b350d..efef8693d03 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java @@ -20,10 +20,7 @@ import java.math.BigDecimal; import java.util.Map; -import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; +import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.DataBlockCache; import org.apache.pinot.core.operator.docvalsets.ProjectionBlockValSet; @@ -35,7 +32,7 @@ * ProjectionBlock holds a column name to Block Map. * It provides DocIdSetBlock for a given column. */ -public class ProjectionBlock implements Block { +public class ProjectionBlock implements ValueBlock { private final Map _dataSourceMap; private final DataBlockCache _dataBlockCache; @@ -44,36 +41,25 @@ public ProjectionBlock(Map dataSourceMap, DataBlockCache dat _dataBlockCache = dataBlockCache; } + @Override public int getNumDocs() { return _dataBlockCache.getNumDocs(); } + @Override public int[] getDocIds() { return _dataBlockCache.getDocIds(); } - public BlockValSet getBlockValueSet(String column) { - return new ProjectionBlockValSet(_dataBlockCache, column, _dataSourceMap.get(column)); - } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); + public BlockValSet getBlockValueSet(ExpressionContext expression) { + assert expression.getType() == ExpressionContext.Type.IDENTIFIER; + return getBlockValueSet(expression.getIdentifier()); } @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public BlockValSet getBlockValueSet(String column) { + return new ProjectionBlockValSet(_dataBlockCache, column, _dataSourceMap.get(column)); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java index 5625a5704d4..65460d857c9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java @@ -19,67 +19,47 @@ package org.apache.pinot.core.operator.blocks; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.operator.docvalsets.TransformBlockValSet; import org.apache.pinot.core.operator.transform.function.TransformFunction; /** - * Transform Block holds blocks of transformed columns. - *

    In absence of transforms, it servers as a pass-through to projection block. + * The {@code TransformBlock} contains values of the transformed columns. */ -public class TransformBlock implements Block { - protected final ProjectionBlock _projectionBlock; +public class TransformBlock implements ValueBlock { + protected final ValueBlock _sourceBlock; protected final Map _transformFunctionMap; - public TransformBlock(ProjectionBlock projectionBlock, - Map transformFunctionMap) { - _projectionBlock = projectionBlock; + public TransformBlock(ValueBlock sourceBlock, Map transformFunctionMap) { + _sourceBlock = sourceBlock; _transformFunctionMap = transformFunctionMap; } + @Override public int getNumDocs() { - return _projectionBlock.getNumDocs(); + return _sourceBlock.getNumDocs(); } + @Nullable + @Override public int[] getDocIds() { - return _projectionBlock.getDocIds(); + return _sourceBlock.getDocIds(); } + @Override public BlockValSet getBlockValueSet(ExpressionContext expression) { if (expression.getType() == ExpressionContext.Type.IDENTIFIER) { - return _projectionBlock.getBlockValueSet(expression.getIdentifier()); + return _sourceBlock.getBlockValueSet(expression); } else { - return new TransformBlockValSet(_projectionBlock, _transformFunctionMap.get(expression), expression); + return new TransformBlockValSet(_sourceBlock, _transformFunctionMap.get(expression)); } } - public BlockValSet getBlockValueSet(String column) { - return _projectionBlock.getBlockValueSet(column); - } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public BlockValSet getBlockValueSet(String column) { + return _sourceBlock.getBlockValueSet(column); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/PassThroughTransformBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ValueBlock.java similarity index 59% rename from pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/PassThroughTransformBlock.java rename to pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ValueBlock.java index c9f7d0d7e19..a135c1edf09 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/PassThroughTransformBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ValueBlock.java @@ -18,24 +18,35 @@ */ package org.apache.pinot.core.operator.blocks; -import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.Block; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.transform.function.TransformFunction; /** - * Transform Block servers as a pass-through to projection block. + * The {@code ValueBlock} contains a block of values for multiple expressions. */ -public class PassThroughTransformBlock extends TransformBlock { +public interface ValueBlock extends Block { - public PassThroughTransformBlock(ProjectionBlock projectionBlock, - Map transformFunctionMap) { - super(projectionBlock, transformFunctionMap); - } + /** + * Returns the number of documents within the block. + */ + int getNumDocs(); - @Override - public BlockValSet getBlockValueSet(ExpressionContext expression) { - return _projectionBlock.getBlockValueSet(expression.getIdentifier()); - } + /** + * Returns the document ids from the segment, or {@code null} if it is not available. + */ + @Nullable + int[] getDocIds(); + + /** + * Returns the values for a given expression. + */ + BlockValSet getBlockValueSet(ExpressionContext expression); + + /** + * Returns the values for a given column (identifier). + */ + BlockValSet getBlockValueSet(String column); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java index 54fa0b9558b..1b300aeb118 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java @@ -19,17 +19,20 @@ package org.apache.pinot.core.operator.blocks.results; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; import java.io.IOException; import java.math.BigDecimal; -import java.util.Collection; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.datatable.DataTableBuilder; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.spi.utils.ByteArray; import org.roaringbitmap.RoaringBitmap; @@ -63,13 +66,17 @@ public int getNumRows() { @Override public DataSchema getDataSchema(QueryContext queryContext) { - boolean returnFinalResult = queryContext.isServerReturnFinalResult(); - int numColumns = _aggregationFunctions.length; + List> filteredAggregationFunctions = + queryContext.getFilteredAggregationFunctions(); + assert filteredAggregationFunctions != null; + int numColumns = filteredAggregationFunctions.size(); String[] columnNames = new String[numColumns]; ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; + boolean returnFinalResult = queryContext.isServerReturnFinalResult(); for (int i = 0; i < numColumns; i++) { - AggregationFunction aggregationFunction = _aggregationFunctions[i]; - columnNames[i] = aggregationFunction.getColumnName(); + Pair pair = filteredAggregationFunctions.get(i); + AggregationFunction aggregationFunction = pair.getLeft(); + columnNames[i] = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); columnDataTypes[i] = returnFinalResult ? aggregationFunction.getFinalResultColumnType() : aggregationFunction.getIntermediateResultColumnType(); } @@ -77,19 +84,19 @@ public DataSchema getDataSchema(QueryContext queryContext) { } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { return Collections.singletonList(_results.toArray()); } @Override public DataTable getDataTable(QueryContext queryContext) throws IOException { - boolean returnFinalResult = queryContext.isServerReturnFinalResult(); DataSchema dataSchema = getDataSchema(queryContext); assert dataSchema != null; ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); int numColumns = columnDataTypes.length; DataTableBuilder dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema); + boolean returnFinalResult = queryContext.isServerReturnFinalResult(); if (queryContext.isNullHandlingEnabled()) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; for (int i = 0; i < numColumns; i++) { @@ -133,6 +140,9 @@ private void setIntermediateResult(DataTableBuilder dataTableBuilder, ColumnData throws IOException { ColumnDataType columnDataType = columnDataTypes[index]; switch (columnDataType) { + case INT: + dataTableBuilder.setColumn(index, (int) result); + break; case LONG: dataTableBuilder.setColumn(index, (long) result); break; @@ -151,7 +161,7 @@ private void setFinalResult(DataTableBuilder dataTableBuilder, ColumnDataType[] Object result) throws IOException { ColumnDataType columnDataType = columnDataTypes[index]; - switch (columnDataType) { + switch (columnDataType.getStoredType()) { case INT: dataTableBuilder.setColumn(index, (int) result); break; @@ -176,6 +186,9 @@ private void setFinalResult(DataTableBuilder dataTableBuilder, ColumnDataType[] case DOUBLE_ARRAY: dataTableBuilder.setColumn(index, ((DoubleArrayList) result).elements()); break; + case LONG_ARRAY: + dataTableBuilder.setColumn(index, ((LongArrayList) result).elements()); + break; default: throw new IllegalStateException("Illegal column data type in final result: " + columnDataType); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java index 7c17a7abb65..b611430d506 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java @@ -21,7 +21,6 @@ import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,10 +30,6 @@ import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.query.request.context.QueryContext; @@ -175,7 +170,7 @@ public void setNumServerThreads(int numServerThreads) { * Returns the rows for the results. Return {@code null} when the block only contains metadata. */ @Nullable - public abstract Collection getRows(QueryContext queryContext); + public abstract List getRows(QueryContext queryContext); /** * Returns a data table without metadata or exception attached. @@ -199,24 +194,4 @@ public Map getResultsMetadata() { metadata.put(MetadataKey.NUM_CONSUMING_SEGMENTS_MATCHED.getName(), Integer.toString(_numConsumingSegmentsMatched)); return metadata; } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java index 99a0f868c6b..b1d4fc3ade9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java @@ -25,7 +25,6 @@ import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.data.table.Record; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; @@ -35,11 +34,9 @@ * Results block for distinct queries. */ public class DistinctResultsBlock extends BaseResultsBlock { - private final DistinctAggregationFunction _distinctFunction; private DistinctTable _distinctTable; - public DistinctResultsBlock(DistinctAggregationFunction distinctFunction, DistinctTable distinctTable) { - _distinctFunction = distinctFunction; + public DistinctResultsBlock(DistinctTable distinctTable) { _distinctTable = distinctTable; } @@ -62,7 +59,7 @@ public DataSchema getDataSchema(QueryContext queryContext) { } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { List rows = new ArrayList<>(_distinctTable.size()); for (Record record : _distinctTable.getRecords()) { rows.add(record.getValues()); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java index 783c7c22a4e..0b7e6e05d06 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.operator.blocks.results; -import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.exception.QueryException; @@ -26,6 +26,7 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.spi.exception.QueryCancelledException; public class ExceptionResultsBlock extends BaseResultsBlock { @@ -38,6 +39,10 @@ public ExceptionResultsBlock(Throwable t) { this(QueryException.QUERY_EXECUTION_ERROR, t); } + public ExceptionResultsBlock(QueryCancelledException t) { + this(QueryException.QUERY_CANCELLATION_ERROR, t); + } + @Override public int getNumRows() { return 0; @@ -51,7 +56,7 @@ public DataSchema getDataSchema(QueryContext queryContext) { @Nullable @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { return null; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java index bb3e71958be..5789c9ba76a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -52,7 +51,7 @@ public int getNumRows() { } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { List rows = new ArrayList<>(_entries.size()); for (ExplainEntry entry : _entries) { rows.add(entry.toRow()); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java index f431f4bd6f7..7e5057b4d42 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java @@ -149,7 +149,7 @@ public DataSchema getDataSchema(QueryContext queryContext) { @Nullable @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { if (_table == null) { return Collections.emptyList(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java index 3ef747d2a14..97b941f3d64 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.operator.blocks.results; -import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; @@ -41,7 +41,7 @@ public DataSchema getDataSchema(QueryContext queryContext) { @Nullable @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { return null; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java index 6fe8346a8b0..ed59ffbcc6d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java @@ -29,7 +29,6 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextUtils; @@ -43,16 +42,16 @@ private ResultsBlockUtils() { public static BaseResultsBlock buildEmptyQueryResults(QueryContext queryContext) { if (QueryContextUtils.isSelectionQuery(queryContext)) { return buildEmptySelectionQueryResults(queryContext); - } else if (QueryContextUtils.isAggregationQuery(queryContext)) { + } + if (QueryContextUtils.isAggregationQuery(queryContext)) { if (queryContext.getGroupByExpressions() == null) { return buildEmptyAggregationQueryResults(queryContext); } else { return buildEmptyGroupByQueryResults(queryContext); } - } else { - assert QueryContextUtils.isDistinctQuery(queryContext); - return buildEmptyDistinctQueryResults(queryContext); } + assert QueryContextUtils.isDistinctQuery(queryContext); + return buildEmptyDistinctQueryResults(queryContext); } private static SelectionResultsBlock buildEmptySelectionQueryResults(QueryContext queryContext) { @@ -71,8 +70,6 @@ private static SelectionResultsBlock buildEmptySelectionQueryResults(QueryContex private static AggregationResultsBlock buildEmptyAggregationQueryResults(QueryContext queryContext) { AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); - List> filteredAggregationFunctions = - queryContext.getFilteredAggregationFunctions(); assert aggregationFunctions != null; int numAggregations = aggregationFunctions.length; List results = new ArrayList<>(numAggregations); @@ -85,9 +82,8 @@ private static AggregationResultsBlock buildEmptyAggregationQueryResults(QueryCo private static GroupByResultsBlock buildEmptyGroupByQueryResults(QueryContext queryContext) { List> filteredAggregationFunctions = queryContext.getFilteredAggregationFunctions(); - List groupByExpressions = queryContext.getGroupByExpressions(); - assert groupByExpressions != null; + assert filteredAggregationFunctions != null && groupByExpressions != null; int numColumns = groupByExpressions.size() + filteredAggregationFunctions.size(); String[] columnNames = new String[numColumns]; ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; @@ -98,12 +94,9 @@ private static GroupByResultsBlock buildEmptyGroupByQueryResults(QueryContext qu columnDataTypes[index] = ColumnDataType.STRING; index++; } - for (Pair aggFilterPair : filteredAggregationFunctions) { - // NOTE: Use AggregationFunction.getResultColumnName() for SQL format response - AggregationFunction aggregationFunction = aggFilterPair.getLeft(); - String columnName = - AggregationFunctionUtils.getResultColumnName(aggregationFunction, aggFilterPair.getRight()); - columnNames[index] = columnName; + for (Pair pair : filteredAggregationFunctions) { + AggregationFunction aggregationFunction = pair.getLeft(); + columnNames[index] = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); columnDataTypes[index] = aggregationFunction.getIntermediateResultColumnType(); index++; } @@ -111,17 +104,17 @@ private static GroupByResultsBlock buildEmptyGroupByQueryResults(QueryContext qu } private static DistinctResultsBlock buildEmptyDistinctQueryResults(QueryContext queryContext) { - AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); - assert aggregationFunctions != null && aggregationFunctions.length == 1 - && aggregationFunctions[0] instanceof DistinctAggregationFunction; - DistinctAggregationFunction distinctAggregationFunction = (DistinctAggregationFunction) aggregationFunctions[0]; - String[] columnNames = distinctAggregationFunction.getColumns(); - ColumnDataType[] columnDataTypes = new ColumnDataType[columnNames.length]; + List expressions = queryContext.getSelectExpressions(); + int numExpressions = expressions.size(); + String[] columns = new String[numExpressions]; + for (int i = 0; i < numExpressions; i++) { + columns[i] = expressions.get(i).toString(); + } + ColumnDataType[] columnDataTypes = new ColumnDataType[numExpressions]; // NOTE: Use STRING column data type as default for distinct query Arrays.fill(columnDataTypes, ColumnDataType.STRING); - DistinctTable distinctTable = - new DistinctTable(new DataSchema(columnNames, columnDataTypes), Collections.emptySet(), - queryContext.isNullHandlingEnabled()); - return new DistinctResultsBlock(distinctAggregationFunction, distinctTable); + DistinctTable distinctTable = new DistinctTable(new DataSchema(columns, columnDataTypes), Collections.emptySet(), + queryContext.isNullHandlingEnabled()); + return new DistinctResultsBlock(distinctTable); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java index 363cd1a4f5a..b19c9898c69 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java @@ -18,12 +18,9 @@ */ package org.apache.pinot.core.operator.blocks.results; -import com.google.common.base.Preconditions; import java.io.IOException; -import java.util.Collection; import java.util.Comparator; import java.util.List; -import java.util.PriorityQueue; import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; @@ -36,32 +33,37 @@ */ public class SelectionResultsBlock extends BaseResultsBlock { private final DataSchema _dataSchema; - private final Collection _rows; private final Comparator _comparator; + private List _rows; - public SelectionResultsBlock(DataSchema dataSchema, List rows) { - this(dataSchema, rows, null); - } - - public SelectionResultsBlock(DataSchema dataSchema, PriorityQueue rows) { - this(dataSchema, rows, rows.comparator()); - } - - public SelectionResultsBlock(DataSchema dataSchema, Collection rows, + public SelectionResultsBlock(DataSchema dataSchema, List rows, @Nullable Comparator comparator) { _dataSchema = dataSchema; _rows = rows; _comparator = comparator; } + public SelectionResultsBlock(DataSchema dataSchema, List rows) { + this(dataSchema, rows, null); + } + public DataSchema getDataSchema() { return _dataSchema; } - public Collection getRows() { + public List getRows() { return _rows; } + public void setRows(List rows) { + _rows = rows; + } + + @Nullable + public Comparator getComparator() { + return _comparator; + } + @Override public int getNumRows() { return _rows.size(); @@ -73,25 +75,10 @@ public DataSchema getDataSchema(QueryContext queryContext) { } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows(QueryContext queryContext) { return _rows; } - public SelectionResultsBlock convertToPriorityQueueBased() { - if (_rows instanceof PriorityQueue) { - return this; - } - Preconditions.checkState(_comparator != null, "No comparator specified in the results block"); - PriorityQueue result = new PriorityQueue<>(_comparator); - result.addAll(_rows); - return new SelectionResultsBlock(_dataSchema, result); - } - - public PriorityQueue getRowsAsPriorityQueue() { - Preconditions.checkState(_rows instanceof PriorityQueue, "The results block is not backed by a priority queue"); - return (PriorityQueue) _rows; - } - @Override public DataTable getDataTable(QueryContext queryContext) throws IOException { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java index 6dbb6fd839b..e7f888ef006 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java @@ -22,37 +22,24 @@ import java.util.concurrent.ExecutorService; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; -import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.operator.combine.merger.AggregationResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; /** * Combine operator for aggregation queries. */ -@SuppressWarnings({"rawtypes", "unchecked"}) -public class AggregationCombineOperator extends BaseCombineOperator { +@SuppressWarnings({"rawtypes"}) +public class AggregationCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_AGGREGATE"; public AggregationCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); + super(new AggregationResultsBlockMerger(queryContext), operators, queryContext, executorService); } @Override public String toExplainString() { return EXPLAIN_NAME; } - - @Override - protected void mergeResultsBlocks(AggregationResultsBlock mergedBlock, AggregationResultsBlock blockToMerge) { - AggregationFunction[] aggregationFunctions = mergedBlock.getAggregationFunctions(); - List mergedResults = mergedBlock.getResults(); - List resultsToMerge = blockToMerge.getResults(); - assert aggregationFunctions != null && mergedResults != null && resultsToMerge != null; - - int numAggregationFunctions = aggregationFunctions.length; - for (int i = 0; i < numAggregationFunctions; i++) { - mergedResults.set(i, aggregationFunctions[i].merge(mergedResults.get(i), resultsToMerge.get(i))); - } - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java index 49e7502aa2f..b4babb28a3d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java @@ -24,24 +24,22 @@ import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Phaser; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import org.apache.commons.lang.StringUtils; +import java.util.concurrent.atomic.AtomicReference; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.ResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.core.util.QueryMultiThreadingUtils; import org.apache.pinot.core.util.trace.TraceRunnable; import org.apache.pinot.spi.accounting.ThreadExecutionContext; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; import org.apache.pinot.spi.exception.EarlyTerminationException; -import org.apache.pinot.spi.exception.QueryCancelledException; import org.apache.pinot.spi.trace.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,23 +51,31 @@ * the results blocks from the processed segments. It can early-terminate the query to save the system resources if it * detects that the merged results can already satisfy the query, or the query is already errored out or timed out. */ -@SuppressWarnings({"rawtypes", "unchecked"}) +@SuppressWarnings({"rawtypes"}) public abstract class BaseCombineOperator extends BaseOperator { private static final Logger LOGGER = LoggerFactory.getLogger(BaseCombineOperator.class); + protected final ResultsBlockMerger _resultsBlockMerger; protected final List _operators; protected final int _numOperators; protected final QueryContext _queryContext; protected final ExecutorService _executorService; protected final int _numTasks; + protected final Phaser _phaser; protected final Future[] _futures; + // Use an AtomicInteger to track the next operator to execute protected final AtomicInteger _nextOperatorId = new AtomicInteger(); // Use a BlockingQueue to store the intermediate results blocks protected final BlockingQueue _blockingQueue = new LinkedBlockingQueue<>(); + // Use an AtomicReference to track the exception/error during segment processing + protected final AtomicReference _processingException = new AtomicReference<>(); + protected final AtomicLong _totalWorkerThreadCpuTimeNs = new AtomicLong(0); - protected BaseCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { + protected BaseCombineOperator(ResultsBlockMerger resultsBlockMerger, List operators, + QueryContext queryContext, ExecutorService executorService) { + _resultsBlockMerger = resultsBlockMerger; _operators = operators; _numOperators = _operators.size(); _queryContext = queryContext; @@ -78,17 +84,20 @@ protected BaseCombineOperator(List operators, QueryContext queryContex // NOTE: We split the query execution into multiple tasks, where each task handles the query execution on multiple // (>=1) segments. These tasks are assigned to multiple execution threads so that they can run in parallel. // The parallelism is bounded by the task count. - _numTasks = CombineOperatorUtils.getNumTasksForQuery(operators.size(), queryContext.getMaxExecutionThreads()); - _futures = new Future[_numTasks]; - } + _numTasks = QueryMultiThreadingUtils.getNumTasksForQuery(operators.size(), queryContext.getMaxExecutionThreads()); - @Override - protected BaseResultsBlock getNextBlock() { // Use a Phaser to ensure all the Futures are done (not scheduled, finished or interrupted) before the main thread // returns. We need to ensure this because the main thread holds the reference to the segments. If a segment is // deleted/refreshed, the segment will be released after the main thread returns, which would lead to undefined // behavior (even JVM crash) when processing queries against it. - Phaser phaser = new Phaser(1); + _phaser = new Phaser(1); + _futures = new Future[_numTasks]; + } + + /** + * Start the combine operator process. This will spin up multiple threads to process data segments in parallel. + */ + protected void startProcess() { Tracing.activeRecording().setNumTasks(_numTasks); ThreadExecutionContext parentContext = Tracing.getThreadAccountant().getThreadExecutionContext(); for (int i = 0; i < _numTasks; i++) { @@ -104,7 +113,7 @@ public void runJob() { // NOTE: If the phaser is terminated (returning negative value) when trying to register the task, that means // the query execution has finished, and the main thread has deregistered itself and returned the // result. Directly return as no execution result will be taken. - if (phaser.register() < 0) { + if (_phaser.register() < 0) { Tracing.ThreadAccountantOps.clear(); return; } @@ -122,10 +131,10 @@ public void runJob() { } else { LOGGER.error("Caught serious error while processing query: " + _queryContext, t); } - onException(t); + onProcessSegmentsException(t); } finally { - onFinish(); - phaser.arriveAndDeregister(); + onProcessSegmentsFinish(); + _phaser.arriveAndDeregister(); Tracing.ThreadAccountantOps.clear(); } @@ -133,155 +142,46 @@ public void runJob() { } }); } - - BaseResultsBlock mergedBlock; - try { - mergedBlock = mergeResults(); - } catch (InterruptedException | EarlyTerminationException e) { - Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); - throw new QueryCancelledException( - "Cancelled while merging results blocks" - + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg), e); - } catch (Exception e) { - LOGGER.error("Caught exception while merging results blocks (query: {})", _queryContext, e); - mergedBlock = new ExceptionResultsBlock(QueryException.getException(QueryException.INTERNAL_ERROR, e)); - } finally { - // Cancel all ongoing jobs - for (Future future : _futures) { - if (!future.isDone()) { - future.cancel(true); - } - } - // Deregister the main thread and wait for all threads done - phaser.awaitAdvance(phaser.arriveAndDeregister()); - } - /* - * _numTasks are number of async tasks submitted to the _executorService, but it does not mean Pinot server - * use those number of threads to concurrently process segments. Instead, if _executorService thread pool has - * less number of threads than _numTasks, the number of threads that used to concurrently process segments equals - * to the pool size. - * TODO: Get the actual number of query worker threads instead of using the default value. - */ - int numServerThreads = Math.min(_numTasks, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); - CombineOperatorUtils.setExecutionStatistics(mergedBlock, _operators, _totalWorkerThreadCpuTimeNs.get(), - numServerThreads); - return mergedBlock; - } - - /** - * Executes query on one or more segments in a worker thread. - */ - protected void processSegments() { - int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { - Operator operator = _operators.get(operatorId); - T resultsBlock; - try { - if (operator instanceof AcquireReleaseColumnsSegmentOperator) { - ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); - } - resultsBlock = (T) operator.nextBlock(); - } finally { - if (operator instanceof AcquireReleaseColumnsSegmentOperator) { - ((AcquireReleaseColumnsSegmentOperator) operator).release(); - } - } - - if (isQuerySatisfied(resultsBlock)) { - // Query is satisfied, skip processing the remaining segments - _blockingQueue.offer(resultsBlock); - return; - } else { - _blockingQueue.offer(resultsBlock); - } - } - } - - /** - * Invoked when {@link #processSegments()} throws exception/error. - */ - protected void onException(Throwable t) { - _blockingQueue.offer(new ExceptionResultsBlock(t)); - } - - /** - * Invoked when {@link #processSegments()} is finished (called in the finally block). - */ - protected void onFinish() { } /** - * Merges the results from the worker threads into a results block. + * Stop the combine operator process. This will stop all sub-tasks that were spun up to process data segments. */ - protected BaseResultsBlock mergeResults() - throws Exception { - T mergedBlock = null; - int numBlocksMerged = 0; - long endTimeMs = _queryContext.getEndTimeMs(); - while (numBlocksMerged < _numOperators) { - // Timeout has reached, shouldn't continue to process. `_blockingQueue.poll` will continue to return blocks even - // if negative timeout is provided; therefore an extra check is needed - long waitTimeMs = endTimeMs - System.currentTimeMillis(); - if (waitTimeMs <= 0) { - return getTimeoutResultsBlock(numBlocksMerged); - } - BaseResultsBlock blockToMerge = _blockingQueue.poll(waitTimeMs, TimeUnit.MILLISECONDS); - if (blockToMerge == null) { - return getTimeoutResultsBlock(numBlocksMerged); - } - if (blockToMerge.getProcessingExceptions() != null) { - // Caught exception while processing segment, skip merging the remaining results blocks and directly return the - // exception - return blockToMerge; - } - if (mergedBlock == null) { - mergedBlock = convertToMergeableBlock((T) blockToMerge); - } else { - mergeResultsBlocks(mergedBlock, (T) blockToMerge); - } - numBlocksMerged++; - if (isQuerySatisfied(mergedBlock)) { - // Query is satisfied, skip merging the remaining results blocks - return mergedBlock; + protected void stopProcess() { + // Cancel all ongoing jobs + for (Future future : _futures) { + if (future != null && !future.isDone()) { + future.cancel(true); } } - return mergedBlock; + // Deregister the main thread and wait for all threads done + _phaser.awaitAdvance(_phaser.arriveAndDeregister()); } - private ExceptionResultsBlock getTimeoutResultsBlock(int numBlocksMerged) { + protected ExceptionResultsBlock getTimeoutResultsBlock(int numBlocksMerged) { LOGGER.error("Timed out while polling results block, numBlocksMerged: {} (query: {})", numBlocksMerged, _queryContext); return new ExceptionResultsBlock(QueryException.EXECUTION_TIMEOUT_ERROR, new TimeoutException("Timed out while polling results block")); } - /** - * Can be overridden for early termination. The input results block might not be mergeable. - */ - protected boolean isQuerySatisfied(T resultsBlock) { - return false; + @Override + public List getChildOperators() { + return _operators; } /** - * Merges a results block into the main mergeable results block. - *

    NOTE: {@code blockToMerge} should contain the result for a segment without any exception. The errored segment - * result is already handled. - * - * @param mergedBlock The block that accumulates previous results. It should be modified to add the information of the - * other block. - * @param blockToMerge The new block that needs to be merged into the mergedBlock. + * Executes query on one or more segments in a worker thread. */ - protected abstract void mergeResultsBlocks(T mergedBlock, T blockToMerge); + protected abstract void processSegments(); /** - * Converts the given results block into a mergeable results block if necessary. + * Invoked when {@link #processSegments()} throws exception/error. */ - protected T convertToMergeableBlock(T resultsBlock) { - return resultsBlock; - } + protected abstract void onProcessSegmentsException(Throwable t); - @Override - public List getChildOperators() { - return _operators; - } + /** + * Invoked when {@link #processSegments()} is finished (called in the finally block). + */ + protected abstract void onProcessSegmentsFinish(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseSingleBlockCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseSingleBlockCombineOperator.java new file mode 100644 index 00000000000..f3dd847fbaa --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseSingleBlockCombineOperator.java @@ -0,0 +1,152 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.ResultsBlockMerger; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Base implementation of the combine operator. + *

    Combine operator uses multiple worker threads to process segments in parallel, and uses the main thread to merge + * the results blocks from the processed segments. It can early-terminate the query to save the system resources if it + * detects that the merged results can already satisfy the query, or the query is already errored out or timed out. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public abstract class BaseSingleBlockCombineOperator + extends BaseCombineOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(BaseSingleBlockCombineOperator.class); + + protected BaseSingleBlockCombineOperator(ResultsBlockMerger resultsBlockMerger, List operators, + QueryContext queryContext, ExecutorService executorService) { + super(resultsBlockMerger, operators, queryContext, executorService); + } + + @Override + protected BaseResultsBlock getNextBlock() { + BaseResultsBlock mergedBlock; + try { + startProcess(); + mergedBlock = mergeResults(); + } catch (InterruptedException e) { + throw new EarlyTerminationException("Interrupted while merging results blocks", e); + } catch (Exception e) { + LOGGER.error("Caught exception while merging results blocks (query: {})", _queryContext, e); + mergedBlock = new ExceptionResultsBlock(QueryException.getException(QueryException.INTERNAL_ERROR, e)); + } finally { + stopProcess(); + } + /* + * _numTasks are number of async tasks submitted to the _executorService, but it does not mean Pinot server + * use those number of threads to concurrently process segments. Instead, if _executorService thread pool has + * less number of threads than _numTasks, the number of threads that used to concurrently process segments equals + * to the pool size. + * TODO: Get the actual number of query worker threads instead of using the default value. + */ + int numServerThreads = Math.min(_numTasks, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); + CombineOperatorUtils.setExecutionStatistics(mergedBlock, _operators, _totalWorkerThreadCpuTimeNs.get(), + numServerThreads); + return mergedBlock; + } + + @Override + protected void processSegments() { + int operatorId; + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + Operator operator = _operators.get(operatorId); + T resultsBlock; + try { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); + } + resultsBlock = (T) operator.nextBlock(); + } finally { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).release(); + } + } + _blockingQueue.offer(resultsBlock); + // When query is satisfied, skip processing the remaining segments + if (_resultsBlockMerger.isQuerySatisfied(resultsBlock)) { + _nextOperatorId.set(_numOperators); + return; + } + } + } + + @Override + protected void onProcessSegmentsException(Throwable t) { + _processingException.compareAndSet(null, t); + _blockingQueue.offer(new ExceptionResultsBlock(t)); + } + + @Override + protected void onProcessSegmentsFinish() { + } + + /** + * Merges the results from the worker threads into a results block. + */ + protected BaseResultsBlock mergeResults() + throws Exception { + T mergedBlock = null; + int numBlocksMerged = 0; + long endTimeMs = _queryContext.getEndTimeMs(); + while (numBlocksMerged < _numOperators) { + // Timeout has reached, shouldn't continue to process. `_blockingQueue.poll` will continue to return blocks even + // if negative timeout is provided; therefore an extra check is needed + long waitTimeMs = endTimeMs - System.currentTimeMillis(); + if (waitTimeMs <= 0) { + return getTimeoutResultsBlock(numBlocksMerged); + } + BaseResultsBlock blockToMerge = _blockingQueue.poll(waitTimeMs, TimeUnit.MILLISECONDS); + if (blockToMerge == null) { + return getTimeoutResultsBlock(numBlocksMerged); + } + if (blockToMerge.getProcessingExceptions() != null) { + // Caught exception while processing segment, skip merging the remaining results blocks and directly return the + // exception + return blockToMerge; + } + if (mergedBlock == null) { + mergedBlock = (T) blockToMerge; + } else { + _resultsBlockMerger.mergeResultsBlocks(mergedBlock, (T) blockToMerge); + } + numBlocksMerged++; + if (_resultsBlockMerger.isQuerySatisfied(mergedBlock)) { + // Query is satisfied, skip merging the remaining results blocks + return mergedBlock; + } + } + return mergedBlock; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java index 842f5053f79..eae5678f61f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java @@ -30,26 +30,6 @@ public class CombineOperatorUtils { private CombineOperatorUtils() { } - /** - * Use at most 10 or half of the processors threads for each query. If there are less than 2 processors, use 1 thread. - *

    NOTE: Runtime.getRuntime().availableProcessors() may return value < 2 in container based environment, e.g. - * Kubernetes. - */ - public static final int MAX_NUM_THREADS_PER_QUERY = - Math.max(1, Math.min(10, Runtime.getRuntime().availableProcessors() / 2)); - - /** - * Returns the number of tasks for the query execution. The tasks can be assigned to multiple execution threads so - * that they can run in parallel. The parallelism is bounded by the task count. - */ - public static int getNumTasksForQuery(int numOperators, int maxExecutionThreads) { - if (maxExecutionThreads > 0) { - return Math.min(numOperators, maxExecutionThreads); - } else { - return Math.min(numOperators, MAX_NUM_THREADS_PER_QUERY); - } - } - /** * Sets the execution statistics into the results block. */ diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java index 2014350359d..6d3bb77a2df 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java @@ -22,7 +22,7 @@ import java.util.concurrent.ExecutorService; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; -import org.apache.pinot.core.query.distinct.DistinctTable; +import org.apache.pinot.core.operator.combine.merger.DistinctResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; @@ -30,45 +30,15 @@ * Combine operator for distinct queries. */ @SuppressWarnings("rawtypes") -public class DistinctCombineOperator extends BaseCombineOperator { +public class DistinctCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_DISTINCT"; - private final boolean _hasOrderBy; - public DistinctCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); - _hasOrderBy = queryContext.getOrderByExpressions() != null; + super(new DistinctResultsBlockMerger(queryContext), operators, queryContext, executorService); } @Override public String toExplainString() { return EXPLAIN_NAME; } - - @Override - protected boolean isQuerySatisfied(DistinctResultsBlock resultsBlock) { - if (_hasOrderBy) { - return false; - } - return resultsBlock.getDistinctTable().size() >= _queryContext.getLimit(); - } - - @Override - protected void mergeResultsBlocks(DistinctResultsBlock mergedBlock, DistinctResultsBlock blockToMerge) { - DistinctTable mergedDistinctTable = mergedBlock.getDistinctTable(); - DistinctTable distinctTableToMerge = blockToMerge.getDistinctTable(); - assert mergedDistinctTable != null && distinctTableToMerge != null; - - // Convert the merged table into a main table if necessary in order to merge other tables - if (!mergedDistinctTable.isMainTable()) { - DistinctTable mainDistinctTable = - new DistinctTable(distinctTableToMerge.getDataSchema(), _queryContext.getOrderByExpressions(), - _queryContext.getLimit(), _queryContext.isNullHandlingEnabled()); - mainDistinctTable.mergeTable(mergedDistinctTable); - mergedBlock.setDistinctTable(mainDistinctTable); - mergedDistinctTable = mainDistinctTable; - } - - mergedDistinctTable.mergeTable(distinctTableToMerge); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java index 643962dc9d7..88fe1a702d8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java @@ -18,18 +18,14 @@ */ package org.apache.pinot.core.operator.combine; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.data.table.ConcurrentIndexedTable; @@ -47,7 +43,7 @@ import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.util.GroupByUtils; -import org.apache.pinot.spi.utils.LoopUtils; +import org.apache.pinot.spi.trace.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,9 +54,8 @@ * all threads */ @SuppressWarnings("rawtypes") -public class GroupByCombineOperator extends BaseCombineOperator { +public class GroupByCombineOperator extends BaseSingleBlockCombineOperator { public static final int MAX_TRIM_THRESHOLD = 1_000_000_000; - public static final int MAX_GROUP_BY_KEYS_MERGED_PER_INTERRUPTION_CHECK = 10_000; private static final Logger LOGGER = LoggerFactory.getLogger(GroupByCombineOperator.class); private static final String EXPLAIN_NAME = "COMBINE_GROUP_BY"; @@ -70,7 +65,6 @@ public class GroupByCombineOperator extends BaseCombineOperator _mergedProcessingExceptions = new ConcurrentLinkedQueue<>(); // We use a CountDownLatch to track if all Futures are finished by the query timeout, and cancel the unfinished // _futures (try to interrupt the execution if it already started). private final CountDownLatch _operatorLatch; @@ -79,7 +73,7 @@ public class GroupByCombineOperator extends BaseCombineOperator operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, overrideMaxExecutionThreads(queryContext, operators.size()), executorService); + super(null, operators, overrideMaxExecutionThreads(queryContext, operators.size()), executorService); int minTrimSize = queryContext.getMinServerGroupTrimSize(); if (minTrimSize > 0) { @@ -130,7 +124,7 @@ public String toExplainString() { @Override protected void processSegments() { int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { Operator operator = _operators.get(operatorId); try { if (operator instanceof AcquireReleaseColumnsSegmentOperator) { @@ -156,12 +150,6 @@ protected void processSegments() { } } - // Merge processing exceptions. - List processingExceptionsToMerge = resultsBlock.getProcessingExceptions(); - if (processingExceptionsToMerge != null) { - _mergedProcessingExceptions.addAll(processingExceptionsToMerge); - } - // Set groups limit reached flag. if (resultsBlock.isNumGroupsLimitReached()) { _numGroupsLimitReached = true; @@ -188,16 +176,16 @@ protected void processSegments() { values[_numGroupByExpressions + i] = aggregationGroupByResult.getResultForGroupId(i, groupId); } _indexedTable.upsert(new Key(keys), new Record(values)); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(mergedKeys); mergedKeys++; - LoopUtils.checkMergePhaseInterruption(mergedKeys); } } } else { for (IntermediateRecord intermediateResult : intermediateRecords) { //TODO: change upsert api so that it accepts intermediateRecord directly _indexedTable.upsert(intermediateResult._key, intermediateResult._record); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(mergedKeys); mergedKeys++; - LoopUtils.checkMergePhaseInterruption(mergedKeys); } } } finally { @@ -209,12 +197,12 @@ protected void processSegments() { } @Override - protected void onException(Throwable t) { - _mergedProcessingExceptions.add(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, t)); + public void onProcessSegmentsException(Throwable t) { + _processingException.compareAndSet(null, t); } @Override - protected void onFinish() { + public void onProcessSegmentsFinish() { _operatorLatch.countDown(); } @@ -232,7 +220,7 @@ protected void onFinish() { * */ @Override - protected BaseResultsBlock mergeResults() + public BaseResultsBlock mergeResults() throws Exception { long timeoutMs = _queryContext.getEndTimeMs() - System.currentTimeMillis(); boolean opCompleted = _operatorLatch.await(timeoutMs, TimeUnit.MILLISECONDS); @@ -245,6 +233,11 @@ protected BaseResultsBlock mergeResults() return new ExceptionResultsBlock(new TimeoutException(errorMessage)); } + Throwable processingException = _processingException.get(); + if (processingException != null) { + return new ExceptionResultsBlock(processingException); + } + IndexedTable indexedTable = _indexedTable; if (!_queryContext.isServerReturnFinalResult()) { indexedTable.finish(false); @@ -255,16 +248,6 @@ protected BaseResultsBlock mergeResults() mergedBlock.setNumGroupsLimitReached(_numGroupsLimitReached); mergedBlock.setNumResizes(indexedTable.getNumResizes()); mergedBlock.setResizeTimeMs(indexedTable.getResizeTimeMs()); - - // Set the processing exceptions. - if (!_mergedProcessingExceptions.isEmpty()) { - mergedBlock.setProcessingExceptions(new ArrayList<>(_mergedProcessingExceptions)); - } - return mergedBlock; } - - @Override - protected void mergeResultsBlocks(GroupByResultsBlock mergedBlock, GroupByResultsBlock blockToMerge) { - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java index 3f0690d8fe3..a5e744fc6d3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java @@ -55,7 +55,8 @@ * */ @SuppressWarnings({"rawtypes", "unchecked"}) -public class MinMaxValueBasedSelectionOrderByCombineOperator extends BaseCombineOperator { +public class MinMaxValueBasedSelectionOrderByCombineOperator + extends BaseSingleBlockCombineOperator { private static final Logger LOGGER = LoggerFactory.getLogger(MinMaxValueBasedSelectionOrderByCombineOperator.class); private static final String EXPLAIN_NAME = "COMBINE_SELECT_ORDERBY_MINMAX"; @@ -74,7 +75,7 @@ public class MinMaxValueBasedSelectionOrderByCombineOperator extends BaseCombine public MinMaxValueBasedSelectionOrderByCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); + super(null, operators, queryContext, executorService); _endOperatorId = new AtomicInteger(_numOperators); _numRowsToKeep = queryContext.getLimit() + queryContext.getOffset(); @@ -146,7 +147,7 @@ protected void processSegments() { Comparable threadBoundaryValue = null; int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { if (operatorId >= _endOperatorId.get()) { _blockingQueue.offer(EMPTY_RESULTS_BLOCK); continue; @@ -284,23 +285,21 @@ protected BaseResultsBlock mergeResults() return blockToMerge; } if (mergedBlock == null) { - mergedBlock = convertToMergeableBlock((SelectionResultsBlock) blockToMerge); + mergedBlock = (SelectionResultsBlock) blockToMerge; } else { mergeResultsBlocks(mergedBlock, (SelectionResultsBlock) blockToMerge); } numBlocksMerged++; // Update the boundary value if enough rows are collected - PriorityQueue selectionResult = mergedBlock.getRowsAsPriorityQueue(); - if (selectionResult != null && selectionResult.size() == _numRowsToKeep) { - assert selectionResult.peek() != null; - _globalBoundaryValue.set((Comparable) selectionResult.peek()[0]); + List rows = mergedBlock.getRows(); + if (rows.size() == _numRowsToKeep) { + _globalBoundaryValue.set((Comparable) rows.get(_numRowsToKeep - 1)[0]); } } return mergedBlock; } - @Override protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { DataSchema mergedDataSchema = mergedBlock.getDataSchema(); DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); @@ -315,18 +314,7 @@ protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionRe QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); return; } - - PriorityQueue mergedRows = mergedBlock.getRowsAsPriorityQueue(); - Collection rowsToMerge = blockToMerge.getRows(); - assert mergedRows != null && rowsToMerge != null; - SelectionOperatorUtils.mergeWithOrdering(mergedRows, rowsToMerge, _numRowsToKeep); - } - - @Override - protected SelectionResultsBlock convertToMergeableBlock(SelectionResultsBlock resultsBlock) { - // This may create a copy or return the same instance. Anyway, this operator is the owner of the - // value now, so it can mutate it. - return resultsBlock.convertToPriorityQueueBased(); + SelectionOperatorUtils.mergeWithOrdering(mergedBlock, blockToMerge, _numRowsToKeep); } private static class MinMaxValueContext { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java index d0e3461b15a..9f8b84c0daf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java @@ -18,18 +18,13 @@ */ package org.apache.pinot.core.operator.combine; -import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutorService; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.SelectionOnlyResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.selection.SelectionOperatorUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -39,16 +34,13 @@ *

    NOTE: Selection order-by query with LIMIT 0 is treated as selection only query. */ @SuppressWarnings("rawtypes") -public class SelectionOnlyCombineOperator extends BaseCombineOperator { - private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOnlyCombineOperator.class); - +public class SelectionOnlyCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_SELECT"; - private final int _numRowsToKeep; public SelectionOnlyCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); + super(new SelectionOnlyResultsBlockMerger(queryContext), operators, queryContext, executorService); _numRowsToKeep = queryContext.getLimit(); } @@ -68,31 +60,4 @@ protected BaseResultsBlock getNextBlock() { return super.getNextBlock(); } - - @Override - protected boolean isQuerySatisfied(SelectionResultsBlock resultsBlock) { - return resultsBlock.getRows().size() == _numRowsToKeep; - } - - @Override - protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { - DataSchema mergedDataSchema = mergedBlock.getDataSchema(); - DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); - assert mergedDataSchema != null && dataSchemaToMerge != null; - if (!mergedDataSchema.equals(dataSchemaToMerge)) { - String errorMessage = - String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", - mergedDataSchema, dataSchemaToMerge); - // NOTE: This is segment level log, so log at debug level to prevent flooding the log. - LOGGER.debug(errorMessage); - mergedBlock.addToProcessingExceptions( - QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); - return; - } - - Collection mergedRows = mergedBlock.getRows(); - Collection rowsToMerge = blockToMerge.getRows(); - assert mergedRows != null && rowsToMerge != null; - SelectionOperatorUtils.mergeWithoutOrdering(mergedRows, rowsToMerge, _numRowsToKeep); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java index 3f799d266f4..583916fa75b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java @@ -18,19 +18,12 @@ */ package org.apache.pinot.core.operator.combine; -import java.util.Collection; import java.util.List; -import java.util.PriorityQueue; import java.util.concurrent.ExecutorService; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.SelectionOrderByResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.selection.SelectionOperatorUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Combine operator for selection order-by queries. @@ -40,50 +33,16 @@ * (process all segments). */ @SuppressWarnings("rawtypes") -public class SelectionOrderByCombineOperator extends BaseCombineOperator { - private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOrderByCombineOperator.class); - +public class SelectionOrderByCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_SELECT_ORDERBY"; - private final int _numRowsToKeep; - public SelectionOrderByCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); - _numRowsToKeep = queryContext.getLimit() + queryContext.getOffset(); + super(new SelectionOrderByResultsBlockMerger(queryContext), operators, queryContext, executorService); } @Override public String toExplainString() { return EXPLAIN_NAME; } - - @Override - protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { - DataSchema mergedDataSchema = mergedBlock.getDataSchema(); - DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); - assert mergedDataSchema != null && dataSchemaToMerge != null; - if (!mergedDataSchema.equals(dataSchemaToMerge)) { - String errorMessage = - String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", - mergedDataSchema, dataSchemaToMerge); - // NOTE: This is segment level log, so log at debug level to prevent flooding the log. - LOGGER.debug(errorMessage); - mergedBlock.addToProcessingExceptions( - QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); - return; - } - - PriorityQueue mergedRows = mergedBlock.getRowsAsPriorityQueue(); - Collection rowsToMerge = blockToMerge.getRows(); - assert mergedRows != null && rowsToMerge != null; - SelectionOperatorUtils.mergeWithOrdering(mergedRows, rowsToMerge, _numRowsToKeep); - } - - @Override - protected SelectionResultsBlock convertToMergeableBlock(SelectionResultsBlock resultsBlock) { - // This may create a copy or return the same instance. Anyway, this operator is the owner of the - // value now, so it can mutate it. - return resultsBlock.convertToPriorityQueueBased(); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/AggregationResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/AggregationResultsBlockMerger.java new file mode 100644 index 00000000000..ccdf86bd3c3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/AggregationResultsBlockMerger.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import java.util.List; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.request.context.QueryContext; + + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class AggregationResultsBlockMerger implements ResultsBlockMerger { + + public AggregationResultsBlockMerger(QueryContext queryContext) { + } + + @Override + public void mergeResultsBlocks(AggregationResultsBlock mergedBlock, AggregationResultsBlock blockToMerge) { + AggregationFunction[] aggregationFunctions = mergedBlock.getAggregationFunctions(); + List mergedResults = mergedBlock.getResults(); + List resultsToMerge = blockToMerge.getResults(); + assert aggregationFunctions != null && mergedResults != null && resultsToMerge != null; + + int numAggregationFunctions = aggregationFunctions.length; + for (int i = 0; i < numAggregationFunctions; i++) { + mergedResults.set(i, aggregationFunctions[i].merge(mergedResults.get(i), resultsToMerge.get(i))); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/DistinctResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/DistinctResultsBlockMerger.java new file mode 100644 index 00000000000..28c41feaf3d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/DistinctResultsBlockMerger.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; +import org.apache.pinot.core.query.distinct.DistinctTable; +import org.apache.pinot.core.query.request.context.QueryContext; + + +public class DistinctResultsBlockMerger implements ResultsBlockMerger { + private final QueryContext _queryContext; + private final boolean _hasOrderBy; + + public DistinctResultsBlockMerger(QueryContext queryContext) { + _queryContext = queryContext; + _hasOrderBy = queryContext.getOrderByExpressions() != null; + } + + @Override + public boolean isQuerySatisfied(DistinctResultsBlock resultsBlock) { + if (_hasOrderBy) { + return false; + } + return resultsBlock.getDistinctTable().size() >= _queryContext.getLimit(); + } + + @Override + public void mergeResultsBlocks(DistinctResultsBlock mergedBlock, DistinctResultsBlock blockToMerge) { + DistinctTable mergedDistinctTable = mergedBlock.getDistinctTable(); + DistinctTable distinctTableToMerge = blockToMerge.getDistinctTable(); + assert mergedDistinctTable != null && distinctTableToMerge != null; + + // Convert the merged table into a main table if necessary in order to merge other tables + if (!mergedDistinctTable.isMainTable()) { + DistinctTable mainDistinctTable = + new DistinctTable(distinctTableToMerge.getDataSchema(), _queryContext.getOrderByExpressions(), + _queryContext.getLimit(), _queryContext.isNullHandlingEnabled()); + mainDistinctTable.mergeTable(mergedDistinctTable); + mergedBlock.setDistinctTable(mainDistinctTable); + mergedDistinctTable = mainDistinctTable; + } + + mergedDistinctTable.mergeTable(distinctTableToMerge); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/ResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/ResultsBlockMerger.java new file mode 100644 index 00000000000..318dfd2bd00 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/ResultsBlockMerger.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; + + +public interface ResultsBlockMerger { + + /** + * Merges a results block into the main mergeable results block. + * + *

    NOTE: {@code blockToMerge} should contain the result for a segment without any exception. The errored segment + * results are handled by {@link org.apache.pinot.core.operator.combine.BaseCombineOperator}. + * + * @param mergedBlock The block that accumulates previous results. It should be modified to add the information of the + * other block. + * @param blockToMerge The new block that needs to be merged into the mergedBlock. + */ + void mergeResultsBlocks(T mergedBlock, T blockToMerge); + + /** + * Determine if a block satisfies the query result requirement. This is mostly used to determine early termination + * conditions. Default to always false and terminate should be done normally by processing all blocks. + * + *

    The input results block might not be mergeable. + */ + default boolean isQuerySatisfied(T resultsBlock) { + return false; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOnlyResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOnlyResultsBlockMerger.java new file mode 100644 index 00000000000..aec95823c83 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOnlyResultsBlockMerger.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SelectionOnlyResultsBlockMerger implements ResultsBlockMerger { + private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOnlyResultsBlockMerger.class); + private final int _numRowsToKeep; + + public SelectionOnlyResultsBlockMerger(QueryContext queryContext) { + _numRowsToKeep = queryContext.getLimit(); + } + + @Override + public boolean isQuerySatisfied(SelectionResultsBlock resultsBlock) { + return resultsBlock.getRows().size() >= _numRowsToKeep; + } + + @Override + public void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { + DataSchema mergedDataSchema = mergedBlock.getDataSchema(); + DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); + assert mergedDataSchema != null && dataSchemaToMerge != null; + if (!mergedDataSchema.equals(dataSchemaToMerge)) { + String errorMessage = + String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", + mergedDataSchema, dataSchemaToMerge); + // NOTE: This is segment level log, so log at debug level to prevent flooding the log. + LOGGER.debug(errorMessage); + mergedBlock.addToProcessingExceptions( + QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); + return; + } + SelectionOperatorUtils.mergeWithoutOrdering(mergedBlock, blockToMerge, _numRowsToKeep); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOrderByResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOrderByResultsBlockMerger.java new file mode 100644 index 00000000000..a5483853cbe --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOrderByResultsBlockMerger.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SelectionOrderByResultsBlockMerger implements ResultsBlockMerger { + private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOrderByResultsBlockMerger.class); + private final int _numRowsToKeep; + + public SelectionOrderByResultsBlockMerger(QueryContext queryContext) { + _numRowsToKeep = queryContext.getLimit() + queryContext.getOffset(); + } + + @Override + public void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { + DataSchema mergedDataSchema = mergedBlock.getDataSchema(); + DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); + assert mergedDataSchema != null && dataSchemaToMerge != null; + if (!mergedDataSchema.equals(dataSchemaToMerge)) { + String errorMessage = + String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", + mergedDataSchema, dataSchemaToMerge); + // NOTE: This is segment level log, so log at debug level to prevent flooding the log. + LOGGER.debug(errorMessage); + mergedBlock.addToProcessingExceptions( + QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); + return; + } + SelectionOperatorUtils.mergeWithOrdering(mergedBlock, blockToMerge, _numRowsToKeep); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java index 64987293bc2..6d9e770a707 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java @@ -18,9 +18,11 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.OptionalInt; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.BitmapDocIdSetOperator; @@ -33,7 +35,9 @@ import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.datasource.DataSource; +import org.roaringbitmap.BatchIterator; import org.roaringbitmap.BitmapDataProvider; +import org.roaringbitmap.IntIterator; import org.roaringbitmap.PeekableIntIterator; import org.roaringbitmap.RoaringBitmap; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; @@ -110,6 +114,19 @@ public int advance(int targetDocId) { return next(); } + @Override + public MutableRoaringBitmap applyAnd(BatchIterator batchIterator, OptionalInt firstDoc, OptionalInt lastDoc) { + IntIterator intIterator = batchIterator.asIntIterator(new int[OPTIMAL_ITERATOR_BATCH_SIZE]); + ProjectionOperator projectionOperator = + new ProjectionOperator(_dataSourceMap, new BitmapDocIdSetOperator(intIterator, _docIdBuffer)); + MutableRoaringBitmap matchingDocIds = new MutableRoaringBitmap(); + ProjectionBlock projectionBlock; + while ((projectionBlock = projectionOperator.nextBlock()) != null) { + processProjectionBlock(projectionBlock, matchingDocIds); + } + return matchingDocIds; + } + @Override public MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { ProjectionOperator projectionOperator = @@ -184,6 +201,14 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP } } break; + case BIG_DECIMAL: + BigDecimal[] bigDecimalValues = _transformFunction.transformToBigDecimalValuesSV(projectionBlock); + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(bigDecimalValues[i])) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + break; default: throw new IllegalStateException(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java index 558c4003d9e..1c794e81c00 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.util.OptionalInt; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.datasource.DataSource; @@ -26,7 +27,6 @@ import org.apache.pinot.spi.utils.CommonConstants.Query.OptimizationConstants; import org.roaringbitmap.BatchIterator; import org.roaringbitmap.RoaringBitmapWriter; -import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MutableRoaringBitmap; @@ -79,13 +79,21 @@ public int advance(int targetDocId) { } @Override - public MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { - if (docIds.isEmpty()) { + public MutableRoaringBitmap applyAnd(BatchIterator docIdIterator, OptionalInt firstDoc, OptionalInt lastDoc) { + if (!docIdIterator.hasNext()) { return new MutableRoaringBitmap(); } - RoaringBitmapWriter result = RoaringBitmapWriter.bufferWriter() - .expectedRange(docIds.first(), docIds.last()).runCompress(false).get(); - BatchIterator docIdIterator = docIds.getBatchIterator(); + RoaringBitmapWriter result; + if (firstDoc.isPresent() && lastDoc.isPresent()) { + result = RoaringBitmapWriter.bufferWriter() + .expectedRange(firstDoc.getAsInt(), lastDoc.getAsInt()) + .runCompress(false) + .get(); + } else { + result = RoaringBitmapWriter.bufferWriter() + .runCompress(false) + .get(); + } int[] buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; while (docIdIterator.hasNext()) { int limit = docIdIterator.nextBatch(buffer); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java index 1a4f9c2c543..baf07c16aa9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java @@ -18,7 +18,9 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.util.OptionalInt; import javax.annotation.Nullable; +import org.apache.pinot.core.common.BlockDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.datasource.DataSource; @@ -44,7 +46,7 @@ public final class SVScanDocIdIterator implements ScanBasedDocIdIterator { private final ForwardIndexReaderContext _readerContext; private final int _numDocs; private final ValueMatcher _valueMatcher; - private final int[] _batch = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _batch; private int _firstMismatch; private int _cursor; private final int _cardinality; @@ -53,7 +55,8 @@ public final class SVScanDocIdIterator implements ScanBasedDocIdIterator { private long _numEntriesScanned = 0L; public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, - @Nullable NullValueVectorReader nullValueReader) { + @Nullable NullValueVectorReader nullValueReader, int batchSize) { + _batch = new int[batchSize]; _predicateEvaluator = predicateEvaluator; _reader = dataSource.getForwardIndex(); _readerContext = _reader.createContext(); @@ -69,6 +72,7 @@ public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, DataSource dat // for testing public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, ForwardIndexReader reader, int numDocs, @Nullable NullValueVectorReader nullValueReader) { + _batch = new int[BlockDocIdIterator.OPTIMAL_ITERATOR_BATCH_SIZE]; _predicateEvaluator = predicateEvaluator; _reader = reader; _readerContext = reader.createContext(); @@ -87,7 +91,7 @@ public int next() { int limit; int batchSize = 0; do { - limit = Math.min(_numDocs - _nextDocId, OPTIMAL_ITERATOR_BATCH_SIZE); + limit = Math.min(_numDocs - _nextDocId, _batch.length); if (limit > 0) { for (int i = 0; i < limit; i++) { _batch[i] = _nextDocId + i; @@ -121,14 +125,22 @@ public int advance(int targetDocId) { } @Override - public MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { - if (docIds.isEmpty()) { + public MutableRoaringBitmap applyAnd(BatchIterator docIdIterator, OptionalInt firstDoc, OptionalInt lastDoc) { + if (!docIdIterator.hasNext()) { return new MutableRoaringBitmap(); } - RoaringBitmapWriter result = RoaringBitmapWriter.bufferWriter() - .expectedRange(docIds.first(), docIds.last()).runCompress(false).get(); - BatchIterator docIdIterator = docIds.getBatchIterator(); - int[] buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + RoaringBitmapWriter result; + if (firstDoc.isPresent() && lastDoc.isPresent()) { + result = RoaringBitmapWriter.bufferWriter() + .expectedRange(firstDoc.getAsInt(), lastDoc.getAsInt()) + .runCompress(false) + .get(); + } else { + result = RoaringBitmapWriter.bufferWriter() + .runCompress(false) + .get(); + } + int[] buffer = new int[_batch.length]; while (docIdIterator.hasNext()) { int limit = docIdIterator.nextBatch(buffer); if (limit > 0) { @@ -264,7 +276,7 @@ public static int removeNullDocs(int[] docIds, double[] values, int limit, Immut private class DictIdMatcher implements ValueMatcher { - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _buffer = new int[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -280,7 +292,7 @@ public int matchValues(int limit, int[] docIds) { private class DictIdMatcherAndNullHandler implements ValueMatcher { - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _buffer = new int[_batch.length]; private final ImmutableRoaringBitmap _nullBitmap; public DictIdMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { @@ -308,7 +320,7 @@ public int matchValues(int limit, int[] docIds) { private class IntMatcher implements ValueMatcher { - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _buffer = new int[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -325,7 +337,7 @@ public int matchValues(int limit, int[] docIds) { private class IntMatcherAndNullHandler implements ValueMatcher { private final ImmutableRoaringBitmap _nullBitmap; - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _buffer = new int[_batch.length]; public IntMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { _nullBitmap = nullBitmap; @@ -349,7 +361,7 @@ public int matchValues(int limit, int[] docIds) { private class LongMatcher implements ValueMatcher { - private final long[] _buffer = new long[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final long[] _buffer = new long[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -366,7 +378,7 @@ public int matchValues(int limit, int[] docIds) { private class LongMatcherAndNullHandler implements ValueMatcher { private final ImmutableRoaringBitmap _nullBitmap; - private final long[] _buffer = new long[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final long[] _buffer = new long[_batch.length]; public LongMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { _nullBitmap = nullBitmap; @@ -390,7 +402,7 @@ public int matchValues(int limit, int[] docIds) { private class FloatMatcher implements ValueMatcher { - private final float[] _buffer = new float[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final float[] _buffer = new float[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -407,7 +419,7 @@ public int matchValues(int limit, int[] docIds) { private class FloatMatcherAndNullHandler implements ValueMatcher { private final ImmutableRoaringBitmap _nullBitmap; - private final float[] _buffer = new float[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final float[] _buffer = new float[_batch.length]; public FloatMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { _nullBitmap = nullBitmap; @@ -431,7 +443,7 @@ public int matchValues(int limit, int[] docIds) { private class DoubleMatcher implements ValueMatcher { - private final double[] _buffer = new double[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final double[] _buffer = new double[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -448,7 +460,7 @@ public int matchValues(int limit, int[] docIds) { private class DoubleMatcherAndNullHandler implements ValueMatcher { private final ImmutableRoaringBitmap _nullBitmap; - private final double[] _buffer = new double[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final double[] _buffer = new double[_batch.length]; public DoubleMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { _nullBitmap = nullBitmap; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java index 1ed890cc834..1caa5264443 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java @@ -18,7 +18,9 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.util.OptionalInt; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.roaringbitmap.BatchIterator; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MutableRoaringBitmap; @@ -34,10 +36,17 @@ */ public interface ScanBasedDocIdIterator extends BlockDocIdIterator { + MutableRoaringBitmap applyAnd(BatchIterator batchIterator, OptionalInt firstDoc, OptionalInt lastDoc); + /** * Applies AND operation to the given bitmap of document ids, returns a bitmap of the matching document ids. */ - MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds); + default MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { + if (docIds.isEmpty()) { + return new MutableRoaringBitmap(); + } + return applyAnd(docIds.getBatchIterator(), OptionalInt.of(docIds.first()), OptionalInt.of(docIds.last())); + } /** * Returns the number of entries (SV value contains one entry, MV value contains multiple entries) scanned during the diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java index 8b43b7d3406..29f267c4a13 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java @@ -25,6 +25,7 @@ import org.apache.commons.collections.MapUtils; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.AndDocIdIterator; import org.apache.pinot.core.operator.dociditerators.BitmapBasedDocIdIterator; import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; @@ -37,9 +38,9 @@ /** - * The FilterBlockDocIdSet to perform AND on all child FilterBlockDocIdSets. + * The BlockDocIdSet to perform AND on all child BlockDocIdSets. *

    The AndBlockDocIdSet will construct the BlockDocIdIterator based on the BlockDocIdIterators from the child - * FilterBlockDocIdSets: + * BlockDocIdSets: *

      *
    • * When there are at least one index-base BlockDocIdIterator (SortedDocIdIterator or BitmapBasedDocIdIterator) and @@ -53,11 +54,11 @@ *
    • *
    */ -public final class AndDocIdSet implements FilterBlockDocIdSet { - private final List _docIdSets; +public final class AndDocIdSet implements BlockDocIdSet { + private final List _docIdSets; private final boolean _cardinalityBasedRankingForScan; - public AndDocIdSet(List docIdSets, Map queryOptions) { + public AndDocIdSet(List docIdSets, Map queryOptions) { _docIdSets = docIdSets; _cardinalityBasedRankingForScan = !MapUtils.isEmpty(queryOptions) && QueryOptionsUtils.isAndScanReorderingEnabled(queryOptions); @@ -66,7 +67,7 @@ public AndDocIdSet(List docIdSets, Map quer @Override public BlockDocIdIterator iterator() { int numDocIdSets = _docIdSets.size(); - // NOTE: Keep the order of FilterBlockDocIdSets to preserve the order decided within FilterOperatorUtils. + // NOTE: Keep the order of BlockDocIdSets to preserve the order decided within FilterOperatorUtils. // TODO: Consider deciding the order based on the stats of BlockDocIdIterators BlockDocIdIterator[] allDocIdIterators = new BlockDocIdIterator[numDocIdSets]; List sortedDocIdIterators = new ArrayList<>(); @@ -170,7 +171,7 @@ public BlockDocIdIterator iterator() { @Override public long getNumEntriesScannedInFilter() { long numEntriesScannedInFilter = 0L; - for (FilterBlockDocIdSet child : _docIdSets) { + for (BlockDocIdSet child : _docIdSets) { numEntriesScannedInFilter += child.getNumEntriesScannedInFilter(); } return numEntriesScannedInFilter; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java index a69ac0066a8..6abf65545b0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java @@ -18,11 +18,12 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; -public class BitmapDocIdSet implements FilterBlockDocIdSet { +public class BitmapDocIdSet implements BlockDocIdSet { private final BitmapDocIdIterator _iterator; public BitmapDocIdSet(ImmutableRoaringBitmap docIds, int numDocs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java index fc78df51a38..1db64ff5577 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java @@ -18,13 +18,14 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.EmptyDocIdIterator; /** - * Singleton class which extends {@link FilterBlockDocIdSet} that is empty, i.e. does not contain any document. + * Singleton class which extends {@link BlockDocIdSet} that is empty, i.e. does not contain any document. */ -public final class EmptyDocIdSet implements FilterBlockDocIdSet { +public final class EmptyDocIdSet implements BlockDocIdSet { private EmptyDocIdSet() { } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionFilterDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionDocIdSet.java similarity index 88% rename from pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionFilterDocIdSet.java rename to pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionDocIdSet.java index 3fbdf475232..70322ac5258 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionFilterDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionDocIdSet.java @@ -19,16 +19,17 @@ package org.apache.pinot.core.operator.docidsets; import java.util.Map; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.ExpressionScanDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.segment.spi.datasource.DataSource; -public final class ExpressionFilterDocIdSet implements FilterBlockDocIdSet { +public final class ExpressionDocIdSet implements BlockDocIdSet { private final ExpressionScanDocIdIterator _docIdIterator; - public ExpressionFilterDocIdSet(TransformFunction transformFunction, PredicateEvaluator predicateEvaluator, + public ExpressionDocIdSet(TransformFunction transformFunction, PredicateEvaluator predicateEvaluator, Map dataSourceMap, int numDocs) { _docIdIterator = new ExpressionScanDocIdIterator(transformFunction, predicateEvaluator, dataSourceMap, numDocs); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/FilterBlockDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/FilterBlockDocIdSet.java deleted file mode 100644 index 18dab2b21c0..00000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/FilterBlockDocIdSet.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.docidsets; - -import org.apache.pinot.core.common.BlockDocIdIterator; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.operator.dociditerators.AndDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.OrDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; -import org.apache.pinot.segment.spi.Constants; -import org.roaringbitmap.RoaringBitmapWriter; -import org.roaringbitmap.buffer.MutableRoaringBitmap; - - -/** - * The FilterBlockDocIdSet interface represents the BlockDocIdSet returned by the - * BaseFilterBlock. - */ -public interface FilterBlockDocIdSet extends BlockDocIdSet { - - /** - * Returns the number of entries (SV value contains one entry, MV value contains multiple entries) scanned in the - * filtering phase. This method should be called after the filtering is done. - */ - long getNumEntriesScannedInFilter(); - - /** - * For scan-based FilterBlockDocIdSet, pre-scans the documents and returns a non-scan-based FilterBlockDocIdSet. - */ - default FilterBlockDocIdSet toNonScanDocIdSet() { - BlockDocIdIterator docIdIterator = iterator(); - - // NOTE: AND and OR DocIdIterator might contain scan-based DocIdIterator - // TODO: This scan is not counted in the execution stats - if (docIdIterator instanceof ScanBasedDocIdIterator || docIdIterator instanceof AndDocIdIterator - || docIdIterator instanceof OrDocIdIterator) { - RoaringBitmapWriter bitmapWriter = - RoaringBitmapWriter.bufferWriter().runCompress(false).get(); - int docId; - while ((docId = docIdIterator.next()) != Constants.EOF) { - bitmapWriter.add(docId); - } - return new RangelessBitmapDocIdSet(bitmapWriter.get()); - } - - // NOTE: AND and OR DocIdSet might return BitmapBasedDocIdIterator after processing the iterators. Create a new - // DocIdSet to prevent processing the iterators again - if (docIdIterator instanceof RangelessBitmapDocIdIterator) { - return new RangelessBitmapDocIdSet((RangelessBitmapDocIdIterator) docIdIterator); - } - if (docIdIterator instanceof BitmapDocIdIterator) { - return new BitmapDocIdSet((BitmapDocIdIterator) docIdIterator); - } - - return this; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java index 6f85c2b946e..dc9361e0d81 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java @@ -18,12 +18,13 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.MVScanDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.datasource.DataSource; -public final class MVScanDocIdSet implements FilterBlockDocIdSet { +public final class MVScanDocIdSet implements BlockDocIdSet { private final MVScanDocIdIterator _docIdIterator; public MVScanDocIdSet(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java index 8e6b1bdccb4..497be02bd4f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java @@ -18,10 +18,11 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.MatchAllDocIdIterator; -public final class MatchAllDocIdSet implements FilterBlockDocIdSet { +public final class MatchAllDocIdSet implements BlockDocIdSet { private final int _numDocs; public MatchAllDocIdSet(int numDocs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java index 1fe6462b684..c874722e43c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java @@ -19,14 +19,15 @@ package org.apache.pinot.core.operator.docidsets; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.NotDocIdIterator; -public class NotDocIdSet implements FilterBlockDocIdSet { - private final FilterBlockDocIdSet _childDocIdSet; +public class NotDocIdSet implements BlockDocIdSet { + private final BlockDocIdSet _childDocIdSet; private final int _numDocs; - public NotDocIdSet(FilterBlockDocIdSet childDocIdSet, int numDocs) { + public NotDocIdSet(BlockDocIdSet childDocIdSet, int numDocs) { _childDocIdSet = childDocIdSet; _numDocs = numDocs; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java index 6ead802acf5..9464d541bbf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.BitmapBasedDocIdIterator; import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; import org.apache.pinot.core.operator.dociditerators.OrDocIdIterator; @@ -30,9 +31,9 @@ /** - * The FilterBlockDocIdSet to perform OR on all child FilterBlockDocIdSets. + * The BlockDocIdSet to perform OR on all child BlockDocIdSets. *

    The OrBlockDocIdSet will construct the BlockDocIdIterator based on the BlockDocIdIterators from the child - * FilterBlockDocIdSets: + * BlockDocIdSets: *

      *
    • * When there are more than one index-base BlockDocIdIterator (SortedDocIdIterator or BitmapBasedDocIdIterator), @@ -45,11 +46,11 @@ *
    • *
    */ -public final class OrDocIdSet implements FilterBlockDocIdSet { - private final List _docIdSets; +public final class OrDocIdSet implements BlockDocIdSet { + private final List _docIdSets; private final int _numDocs; - public OrDocIdSet(List docIdSets, int numDocs) { + public OrDocIdSet(List docIdSets, int numDocs) { _docIdSets = docIdSets; _numDocs = numDocs; } @@ -112,7 +113,7 @@ public BlockDocIdIterator iterator() { @Override public long getNumEntriesScannedInFilter() { long numEntriesScannedInFilter = 0L; - for (FilterBlockDocIdSet docIdSet : _docIdSets) { + for (BlockDocIdSet docIdSet : _docIdSets) { numEntriesScannedInFilter += docIdSet.getNumEntriesScannedInFilter(); } return numEntriesScannedInFilter; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java index 463a2df84ab..20fb0af6f57 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java @@ -18,11 +18,12 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; -public class RangelessBitmapDocIdSet implements FilterBlockDocIdSet { +public class RangelessBitmapDocIdSet implements BlockDocIdSet { private final RangelessBitmapDocIdIterator _iterator; public RangelessBitmapDocIdSet(ImmutableRoaringBitmap docIds) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java index 9167304561b..40fc00a6213 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java @@ -18,19 +18,20 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.SVScanDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; -public final class SVScanDocIdSet implements FilterBlockDocIdSet { +public final class SVScanDocIdSet implements BlockDocIdSet { private final SVScanDocIdIterator _docIdIterator; public SVScanDocIdSet(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, - boolean nullHandlingEnabled) { + boolean nullHandlingEnabled, int batchSize) { NullValueVectorReader nullValueVector = nullHandlingEnabled ? dataSource.getNullValueVector() : null; - _docIdIterator = new SVScanDocIdIterator(predicateEvaluator, dataSource, numDocs, nullValueVector); + _docIdIterator = new SVScanDocIdIterator(predicateEvaluator, dataSource, numDocs, nullValueVector, batchSize); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java index 2c57caf2357..280f9daeb99 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java @@ -19,11 +19,12 @@ package org.apache.pinot.core.operator.docidsets; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.SortedDocIdIterator; import org.apache.pinot.spi.utils.Pairs.IntPair; -public final class SortedDocIdSet implements FilterBlockDocIdSet { +public final class SortedDocIdSet implements BlockDocIdSet { private final List _docIdRanges; // NOTE: No need to track numDocs because sorted index can only apply to ImmutableSegment, so the document ids are diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java index 97a767cf19c..6ae584a486a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java @@ -19,12 +19,9 @@ package org.apache.pinot.core.operator.docvalsets; import java.math.BigDecimal; -import java.util.HashSet; -import java.util.Set; import javax.annotation.Nullable; -import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; @@ -42,51 +39,26 @@ *

    Caller is responsible for calling the correct method based on the data source metadata for the block value set. */ public class TransformBlockValSet implements BlockValSet { - private final ProjectionBlock _projectionBlock; + private final ValueBlock _valueBlock; private final TransformFunction _transformFunction; - private final ExpressionContext _expression; private boolean _nullBitmapSet; private RoaringBitmap _nullBitmap; private int[] _numMVEntries; - public TransformBlockValSet(ProjectionBlock projectionBlock, TransformFunction transformFunction, - ExpressionContext expression) { - _projectionBlock = projectionBlock; + public TransformBlockValSet(ValueBlock valueBlock, TransformFunction transformFunction) { + _valueBlock = valueBlock; _transformFunction = transformFunction; - _expression = expression; } @Nullable @Override public RoaringBitmap getNullBitmap() { if (!_nullBitmapSet) { - RoaringBitmap nullBitmap = null; - if (_expression.getType() == ExpressionContext.Type.FUNCTION) { - Set columns = new HashSet<>(); - _expression.getFunction().getColumns(columns); - for (String column : columns) { - BlockValSet blockValSet = _projectionBlock.getBlockValueSet(column); - RoaringBitmap columnNullBitmap = blockValSet.getNullBitmap(); - if (columnNullBitmap != null) { - if (nullBitmap == null) { - nullBitmap = columnNullBitmap.clone(); - } - nullBitmap.or(columnNullBitmap); - } - } - } - _nullBitmap = nullBitmap; + _nullBitmap = _transformFunction.getNullBitmap(_valueBlock); _nullBitmapSet = true; } - - // The assumption is that any transformation applied to null values will result in null values. - // Examples: - // CAST(null as STRING) -> null. This is similar to Presto behaviour. - // YEAR(null) -> null. This is similar to Presto behaviour. - // TODO(nhejazi): revisit this part in the future because some transform functions can take null input and return - // non-null result (e.g. isNull()), and we should move this logic into the specific transform function. return _nullBitmap; } @@ -110,7 +82,7 @@ public Dictionary getDictionary() { public int[] getDictionaryIdsSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, true); - return _transformFunction.transformToDictIdsSV(_projectionBlock); + return _transformFunction.transformToDictIdsSV(_valueBlock); } } @@ -118,7 +90,7 @@ public int[] getDictionaryIdsSV() { public int[] getIntValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, true); - return _transformFunction.transformToIntValuesSV(_projectionBlock); + return _transformFunction.transformToIntValuesSV(_valueBlock); } } @@ -126,7 +98,7 @@ public int[] getIntValuesSV() { public long[] getLongValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.LONG, true); - return _transformFunction.transformToLongValuesSV(_projectionBlock); + return _transformFunction.transformToLongValuesSV(_valueBlock); } } @@ -134,7 +106,7 @@ public long[] getLongValuesSV() { public float[] getFloatValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.FLOAT, true); - return _transformFunction.transformToFloatValuesSV(_projectionBlock); + return _transformFunction.transformToFloatValuesSV(_valueBlock); } } @@ -142,7 +114,7 @@ public float[] getFloatValuesSV() { public double[] getDoubleValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.DOUBLE, true); - return _transformFunction.transformToDoubleValuesSV(_projectionBlock); + return _transformFunction.transformToDoubleValuesSV(_valueBlock); } } @@ -150,7 +122,7 @@ public double[] getDoubleValuesSV() { public BigDecimal[] getBigDecimalValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.BIG_DECIMAL, true); - return _transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + return _transformFunction.transformToBigDecimalValuesSV(_valueBlock); } } @@ -158,7 +130,7 @@ public BigDecimal[] getBigDecimalValuesSV() { public String[] getStringValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.STRING, true); - return _transformFunction.transformToStringValuesSV(_projectionBlock); + return _transformFunction.transformToStringValuesSV(_valueBlock); } } @@ -166,7 +138,7 @@ public String[] getStringValuesSV() { public byte[][] getBytesValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.BYTES, true); - return _transformFunction.transformToBytesValuesSV(_projectionBlock); + return _transformFunction.transformToBytesValuesSV(_valueBlock); } } @@ -174,7 +146,7 @@ public byte[][] getBytesValuesSV() { public int[][] getDictionaryIdsMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, false); - return _transformFunction.transformToDictIdsMV(_projectionBlock); + return _transformFunction.transformToDictIdsMV(_valueBlock); } } @@ -182,7 +154,7 @@ public int[][] getDictionaryIdsMV() { public int[][] getIntValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, false); - return _transformFunction.transformToIntValuesMV(_projectionBlock); + return _transformFunction.transformToIntValuesMV(_valueBlock); } } @@ -190,7 +162,7 @@ public int[][] getIntValuesMV() { public long[][] getLongValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.LONG, false); - return _transformFunction.transformToLongValuesMV(_projectionBlock); + return _transformFunction.transformToLongValuesMV(_valueBlock); } } @@ -198,7 +170,7 @@ public long[][] getLongValuesMV() { public float[][] getFloatValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.FLOAT, false); - return _transformFunction.transformToFloatValuesMV(_projectionBlock); + return _transformFunction.transformToFloatValuesMV(_valueBlock); } } @@ -206,7 +178,7 @@ public float[][] getFloatValuesMV() { public double[][] getDoubleValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.DOUBLE, false); - return _transformFunction.transformToDoubleValuesMV(_projectionBlock); + return _transformFunction.transformToDoubleValuesMV(_valueBlock); } } @@ -214,7 +186,7 @@ public double[][] getDoubleValuesMV() { public String[][] getStringValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.STRING, false); - return _transformFunction.transformToStringValuesMV(_projectionBlock); + return _transformFunction.transformToStringValuesMV(_valueBlock); } } @@ -222,7 +194,7 @@ public String[][] getStringValuesMV() { public byte[][][] getBytesValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.BYTES, false); - return _transformFunction.transformToBytesValuesMV(_projectionBlock); + return _transformFunction.transformToBytesValuesMV(_valueBlock); } } @@ -231,7 +203,7 @@ public int[] getNumMVEntries() { if (_numMVEntries == null) { _numMVEntries = new int[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - int numDocs = _projectionBlock.getNumDocs(); + int numDocs = _valueBlock.getNumDocs(); TransformResultMetadata resultMetadata = _transformFunction.getResultMetadata(); if (resultMetadata.hasDictionary()) { int[][] dictionaryIds = getDictionaryIdsMV(); @@ -279,7 +251,7 @@ public int[] getNumMVEntries() { private void recordTransformValues(InvocationRecording recording, DataType dataType, boolean singleValue) { if (recording.isEnabled()) { - int numDocs = _projectionBlock.getNumDocs(); + int numDocs = _valueBlock.getNumDocs(); recording.setNumDocsScanned(numDocs); recording.setFunctionName(_transformFunction.getName()); recording.setOutputDataType(dataType, singleValue); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java index de2062fd6c6..38da261c52c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java @@ -21,10 +21,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.AndDocIdSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.buffer.BufferFastAggregation; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; @@ -48,11 +48,11 @@ public AndFilterOperator(List filterOperators) { @Override protected FilterBlock getNextBlock() { Tracing.activeRecording().setNumChildren(_filterOperators.size()); - List filterBlockDocIdSets = new ArrayList<>(_filterOperators.size()); + List blockDocIdSets = new ArrayList<>(_filterOperators.size()); for (BaseFilterOperator filterOperator : _filterOperators) { - filterBlockDocIdSets.add(filterOperator.nextBlock().getBlockDocIdSet()); + blockDocIdSets.add(filterOperator.nextBlock().getBlockDocIdSet()); } - return new FilterBlock(new AndDocIdSet(filterBlockDocIdSets, _queryOptions)); + return new FilterBlock(new AndDocIdSet(blockDocIdSets, _queryOptions)); } @Override @@ -77,7 +77,6 @@ public int getNumMatchingDocs() { return BufferFastAggregation.andCardinality(bitmaps); } - @Override public List getChildOperators() { return new ArrayList<>(_filterOperators); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java index bd8fb41ce1f..91cb33879bc 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java @@ -20,43 +20,20 @@ import java.util.Collections; import java.util.List; -import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.EmptyFilterBlock; import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; -import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.reader.InvertedIndexReader; -import org.apache.pinot.spi.trace.FilterType; -import org.apache.pinot.spi.trace.InvocationRecording; -import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; -import org.roaringbitmap.buffer.MutableRoaringBitmap; -@SuppressWarnings("rawtypes") public class BitmapBasedFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_INVERTED_INDEX"; + private static final String EXPLAIN_NAME = "FILTER_BITMAP"; - private final PredicateEvaluator _predicateEvaluator; - private final InvertedIndexReader _invertedIndexReader; private final ImmutableRoaringBitmap _docIds; private final boolean _exclusive; private final int _numDocs; - @SuppressWarnings("unchecked") - BitmapBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { - _predicateEvaluator = predicateEvaluator; - _invertedIndexReader = (InvertedIndexReader) dataSource.getInvertedIndex(); - _docIds = null; - _exclusive = predicateEvaluator.isExclusive(); - _numDocs = numDocs; - } - public BitmapBasedFilterOperator(ImmutableRoaringBitmap docIds, boolean exclusive, int numDocs) { - _predicateEvaluator = null; - _invertedIndexReader = null; _docIds = docIds; _exclusive = exclusive; _numDocs = numDocs; @@ -64,48 +41,10 @@ public BitmapBasedFilterOperator(ImmutableRoaringBitmap docIds, boolean exclusiv @Override protected FilterBlock getNextBlock() { - if (_docIds != null) { - if (_exclusive) { - return new FilterBlock(new BitmapDocIdSet(ImmutableRoaringBitmap.flip(_docIds, 0L, _numDocs), _numDocs)); - } else { - return new FilterBlock(new BitmapDocIdSet(_docIds, _numDocs)); - } - } - - int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); - int numDictIds = dictIds.length; - if (numDictIds == 0) { - return EmptyFilterBlock.getInstance(); - } - if (numDictIds == 1) { - ImmutableRoaringBitmap docIds = _invertedIndexReader.getDocIds(dictIds[0]); - if (_exclusive) { - if (docIds instanceof MutableRoaringBitmap) { - MutableRoaringBitmap mutableRoaringBitmap = (MutableRoaringBitmap) docIds; - mutableRoaringBitmap.flip(0L, _numDocs); - return new FilterBlock(new BitmapDocIdSet(mutableRoaringBitmap, _numDocs)); - } else { - return new FilterBlock(new BitmapDocIdSet(ImmutableRoaringBitmap.flip(docIds, 0L, _numDocs), _numDocs)); - } - } else { - return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs)); - } + if (_exclusive) { + return new FilterBlock(new BitmapDocIdSet(ImmutableRoaringBitmap.flip(_docIds, 0L, _numDocs), _numDocs)); } else { - ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[numDictIds]; - for (int i = 0; i < numDictIds; i++) { - bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); - } - MutableRoaringBitmap docIds = ImmutableRoaringBitmap.or(bitmaps); - if (_exclusive) { - docIds.flip(0L, _numDocs); - } - InvocationRecording recording = Tracing.activeRecording(); - if (recording.isEnabled()) { - recording.setColumnName(_predicateEvaluator.getPredicate().getLhs().getIdentifier()); - recording.setNumDocsMatchingAfterFilter(docIds.getCardinality()); - recording.setFilter(FilterType.INDEX, String.valueOf(_predicateEvaluator.getPredicateType())); - } - return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs)); + return new FilterBlock(new BitmapDocIdSet(_docIds, _numDocs)); } } @@ -116,36 +55,7 @@ public boolean canOptimizeCount() { @Override public int getNumMatchingDocs() { - int count = 0; - if (_docIds == null) { - int[] dictIds = _exclusive - ? _predicateEvaluator.getNonMatchingDictIds() - : _predicateEvaluator.getMatchingDictIds(); - switch (dictIds.length) { - case 0: - break; - case 1: { - count = _invertedIndexReader.getDocIds(dictIds[0]).getCardinality(); - break; - } - case 2: { - count = ImmutableRoaringBitmap.orCardinality(_invertedIndexReader.getDocIds(dictIds[0]), - _invertedIndexReader.getDocIds(dictIds[1])); - break; - } - default: { - // this could be optimised if the bitmaps are known to be disjoint (as in a single value bitmap index) - MutableRoaringBitmap bitmap = new MutableRoaringBitmap(); - for (int dictId : dictIds) { - bitmap.or(_invertedIndexReader.getDocIds(dictId)); - } - count = bitmap.getCardinality(); - break; - } - } - } else { - count = _docIds.getCardinality(); - } + int count = _docIds.getCardinality(); return _exclusive ? _numDocs - count : count; } @@ -156,34 +66,18 @@ public boolean canProduceBitmaps() { @Override public BitmapCollection getBitmaps() { - if (_docIds == null) { - int[] dictIds = _exclusive - ? _predicateEvaluator.getNonMatchingDictIds() - : _predicateEvaluator.getMatchingDictIds(); - ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[dictIds.length]; - for (int i = 0; i < dictIds.length; i++) { - bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); - } - return new BitmapCollection(_numDocs, _exclusive, bitmaps); - } else { - return new BitmapCollection(_numDocs, _exclusive, _docIds); - } + return new BitmapCollection(_numDocs, _exclusive, _docIds); } @Override + @SuppressWarnings("rawtypes") public List getChildOperators() { return Collections.emptyList(); } @Override public String toExplainString() { - StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(indexLookUp:inverted_index"); - Predicate predicate = _predicateEvaluator != null ? _predicateEvaluator.getPredicate() : null; - if (predicate != null) { - stringBuilder.append(",operator:").append(predicate.getType()); - stringBuilder.append(",predicate:").append(predicate.toString()); - } - return stringBuilder.append(')').toString(); + return EXPLAIN_NAME; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java index 89b856d9b65..4c4f2a689de 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java @@ -21,10 +21,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.AndDocIdSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.spi.trace.Tracing; @@ -41,13 +40,15 @@ public class CombinedFilterOperator extends BaseFilterOperator { public CombinedFilterOperator(BaseFilterOperator mainFilterOperator, BaseFilterOperator subFilterOperator, Map queryOptions) { + assert !mainFilterOperator.isResultEmpty() && !mainFilterOperator.isResultMatchingAll() + && !subFilterOperator.isResultEmpty() && !subFilterOperator.isResultMatchingAll(); _mainFilterOperator = mainFilterOperator; _subFilterOperator = subFilterOperator; _queryOptions = queryOptions; } @Override - public List getChildOperators() { + public List getChildOperators() { return Arrays.asList(_mainFilterOperator, _subFilterOperator); } @@ -59,8 +60,8 @@ public String toExplainString() { @Override protected FilterBlock getNextBlock() { Tracing.activeRecording().setNumChildren(2); - FilterBlockDocIdSet mainFilterDocIdSet = _mainFilterOperator.nextBlock().getNonScanFilterBLockDocIdSet(); - FilterBlockDocIdSet subFilterDocIdSet = _subFilterOperator.nextBlock().getBlockDocIdSet(); + BlockDocIdSet mainFilterDocIdSet = _mainFilterOperator.nextBlock().getNonScanFilterBLockDocIdSet(); + BlockDocIdSet subFilterDocIdSet = _subFilterOperator.nextBlock().getBlockDocIdSet(); return new FilterBlock(new AndDocIdSet(Arrays.asList(mainFilterDocIdSet, subFilterDocIdSet), _queryOptions)); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java index 974eff26f43..66fe22b110c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.EmptyFilterBlock; import org.apache.pinot.core.operator.blocks.FilterBlock; @@ -71,4 +72,9 @@ public String toExplainString() { public List getChildOperators() { return Collections.emptyList(); } + + @Override + public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + explainPlanRows.setHasEmptyFilter(true); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java index 9342dc80472..9001e48fe24 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java @@ -26,13 +26,16 @@ import java.util.Set; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.ExpressionFilterDocIdSet; +import org.apache.pinot.core.operator.docidsets.ExpressionDocIdSet; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluatorProvider; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunctionFactory; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.datasource.DataSource; @@ -45,32 +48,33 @@ public class ExpressionFilterOperator extends BaseFilterOperator { private final TransformFunction _transformFunction; private final PredicateEvaluator _predicateEvaluator; - public ExpressionFilterOperator(IndexSegment segment, Predicate predicate, int numDocs) { + public ExpressionFilterOperator(IndexSegment segment, QueryContext queryContext, Predicate predicate, int numDocs) { _numDocs = numDocs; - _dataSourceMap = new HashMap<>(); Set columns = new HashSet<>(); ExpressionContext lhs = predicate.getLhs(); lhs.getColumns(columns); - for (String column : columns) { - _dataSourceMap.put(column, segment.getDataSource(column)); - } - - _transformFunction = TransformFunctionFactory.get(lhs, _dataSourceMap); - _predicateEvaluator = PredicateEvaluatorProvider - .getPredicateEvaluator(predicate, _transformFunction.getDictionary(), + int mapCapacity = HashUtil.getHashMapCapacity(columns.size()); + _dataSourceMap = new HashMap<>(mapCapacity); + Map columnContextMap = new HashMap<>(mapCapacity); + columns.forEach(column -> { + DataSource dataSource = segment.getDataSource(column); + _dataSourceMap.put(column, dataSource); + columnContextMap.put(column, ColumnContext.fromDataSource(dataSource)); + }); + _transformFunction = TransformFunctionFactory.get(lhs, columnContextMap, queryContext); + _predicateEvaluator = + PredicateEvaluatorProvider.getPredicateEvaluator(predicate, _transformFunction.getDictionary(), _transformFunction.getResultMetadata().getDataType()); } @Override protected FilterBlock getNextBlock() { - return new FilterBlock( - new ExpressionFilterDocIdSet(_transformFunction, _predicateEvaluator, _dataSourceMap, _numDocs)); + return new FilterBlock(new ExpressionDocIdSet(_transformFunction, _predicateEvaluator, _dataSourceMap, _numDocs)); } - @Override - public List getChildOperators() { + public List> getChildOperators() { return Collections.emptyList(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java index aa738beab4f..7e00400eeec 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.OptionalInt; import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.query.request.context.QueryContext; @@ -28,9 +29,221 @@ public class FilterOperatorUtils { + + private static Implementation _instance = new DefaultImplementation(); + private FilterOperatorUtils() { } + public static void setImplementation(Implementation newImplementation) { + _instance = newImplementation; + } + + public interface Implementation { + + /** + * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or {@link OrFilterOperator}). + */ + BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, + int numDocs, boolean nullHandlingEnabled); + + /** + * Returns the AND filter operator or equivalent filter operator. + */ + BaseFilterOperator getAndFilterOperator(QueryContext queryContext, + List filterOperators, int numDocs); + + /** + * Returns the OR filter operator or equivalent filter operator. + */ + BaseFilterOperator getOrFilterOperator(QueryContext queryContext, + List filterOperators, int numDocs); + + /** + * Returns the NOT filter operator or equivalent filter operator. + */ + BaseFilterOperator getNotFilterOperator(QueryContext queryContext, BaseFilterOperator filterOperator, + int numDocs); + } + + public static class DefaultImplementation implements Implementation { + @Override + public BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, + int numDocs, boolean nullHandlingEnabled) { + if (predicateEvaluator.isAlwaysFalse()) { + return EmptyFilterOperator.getInstance(); + } else if (predicateEvaluator.isAlwaysTrue()) { + return new MatchAllFilterOperator(numDocs); + } + + // Currently sorted index based filtering is supported only for + // dictionary encoded columns. The on-disk segment metadata + // will indicate if the column is sorted or not regardless of + // whether it is raw or dictionary encoded. Here when creating + // the filter operator, we need to make sure that sort filter + // operator is used only if the column is sorted and has dictionary. + Predicate.Type predicateType = predicateEvaluator.getPredicateType(); + if (predicateType == Predicate.Type.RANGE) { + if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null) { + return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + if (RangeIndexBasedFilterOperator.canEvaluate(predicateEvaluator, dataSource)) { + return new RangeIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); + } else if (predicateType == Predicate.Type.REGEXP_LIKE) { + if (dataSource.getFSTIndex() != null && dataSource.getDataSourceMetadata().isSorted()) { + return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + if (dataSource.getFSTIndex() != null && dataSource.getInvertedIndex() != null) { + return new InvertedIndexFilterOperator(predicateEvaluator, dataSource, numDocs); + } + return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); + } else { + if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null) { + return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + if (dataSource.getInvertedIndex() != null) { + return new InvertedIndexFilterOperator(predicateEvaluator, dataSource, numDocs); + } + if (RangeIndexBasedFilterOperator.canEvaluate(predicateEvaluator, dataSource)) { + return new RangeIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); + } + } + + @Override + public BaseFilterOperator getAndFilterOperator(QueryContext queryContext, List filterOperators, + int numDocs) { + List childFilterOperators = new ArrayList<>(filterOperators.size()); + for (BaseFilterOperator filterOperator : filterOperators) { + if (filterOperator.isResultEmpty()) { + return EmptyFilterOperator.getInstance(); + } else if (!filterOperator.isResultMatchingAll()) { + childFilterOperators.add(filterOperator); + } + } + int numChildFilterOperators = childFilterOperators.size(); + if (numChildFilterOperators == 0) { + // Return match all filter operator if all child filter operators match all records + return new MatchAllFilterOperator(numDocs); + } else if (numChildFilterOperators == 1) { + // Return the child filter operator if only one left + return childFilterOperators.get(0); + } else { + // Return the AND filter operator with re-ordered child filter operators + reorderAndFilterChildOperators(queryContext, childFilterOperators); + return new AndFilterOperator(childFilterOperators, queryContext.getQueryOptions()); + } + } + + @Override + public BaseFilterOperator getOrFilterOperator(QueryContext queryContext, + List filterOperators, int numDocs) { + List childFilterOperators = new ArrayList<>(filterOperators.size()); + for (BaseFilterOperator filterOperator : filterOperators) { + if (filterOperator.isResultMatchingAll()) { + return new MatchAllFilterOperator(numDocs); + } else if (!filterOperator.isResultEmpty()) { + childFilterOperators.add(filterOperator); + } + } + int numChildFilterOperators = childFilterOperators.size(); + if (numChildFilterOperators == 0) { + // Return empty filter operator if all child filter operators's result is empty + return EmptyFilterOperator.getInstance(); + } else if (numChildFilterOperators == 1) { + // Return the child filter operator if only one left + return childFilterOperators.get(0); + } else { + // Return the OR filter operator with child filter operators + return new OrFilterOperator(childFilterOperators, numDocs); + } + } + + @Override + public BaseFilterOperator getNotFilterOperator(QueryContext queryContext, BaseFilterOperator filterOperator, + int numDocs) { + if (filterOperator.isResultMatchingAll()) { + return EmptyFilterOperator.getInstance(); + } else if (filterOperator.isResultEmpty()) { + return new MatchAllFilterOperator(numDocs); + } + + return new NotFilterOperator(filterOperator, numDocs); + } + + + /** + * For AND filter operator, reorders its child filter operators based on their cost and puts the ones with + * inverted index first in order to reduce the number of documents to be processed. + *

    Special filter operators such as {@link MatchAllFilterOperator} and {@link EmptyFilterOperator} should be + * removed from the list before calling this method. + */ + protected void reorderAndFilterChildOperators(QueryContext queryContext, List filterOperators) { + filterOperators.sort(new Comparator() { + @Override + public int compare(BaseFilterOperator o1, BaseFilterOperator o2) { + return getPriority(o1) - getPriority(o2); + } + + int getPriority(BaseFilterOperator filterOperator) { + if (filterOperator instanceof PrioritizedFilterOperator) { + OptionalInt priority = ((PrioritizedFilterOperator) filterOperator).getPriority(); + if (priority.isPresent()) { + return priority.getAsInt(); + } + } + if (filterOperator instanceof SortedIndexBasedFilterOperator) { + return PrioritizedFilterOperator.HIGH_PRIORITY; + } + if (filterOperator instanceof BitmapBasedFilterOperator) { + return PrioritizedFilterOperator.MEDIUM_PRIORITY; + } + if (filterOperator instanceof RangeIndexBasedFilterOperator + || filterOperator instanceof TextContainsFilterOperator + || filterOperator instanceof TextMatchFilterOperator || filterOperator instanceof JsonMatchFilterOperator + || filterOperator instanceof H3IndexFilterOperator + || filterOperator instanceof H3InclusionIndexFilterOperator) { + return PrioritizedFilterOperator.LOW_PRIORITY; + } + if (filterOperator instanceof AndFilterOperator) { + return PrioritizedFilterOperator.AND_PRIORITY; + } + if (filterOperator instanceof OrFilterOperator) { + return PrioritizedFilterOperator.OR_PRIORITY; + } + if (filterOperator instanceof NotFilterOperator) { + return getPriority(((NotFilterOperator) filterOperator).getChildFilterOperator()); + } + if (filterOperator instanceof ScanBasedFilterOperator) { + int basePriority = PrioritizedFilterOperator.SCAN_PRIORITY; + return getScanBasedFilterPriority(queryContext, (ScanBasedFilterOperator) filterOperator, basePriority); + } + if (filterOperator instanceof ExpressionFilterOperator) { + return PrioritizedFilterOperator.EXPRESSION_PRIORITY; + } + return PrioritizedFilterOperator.UNKNOWN_FILTER_PRIORITY; + } + }); + } + + public static int getScanBasedFilterPriority(QueryContext queryContext, + ScanBasedFilterOperator scanBasedFilterOperator, int basePriority) { + if (queryContext.isSkipScanFilterReorder()) { + return basePriority; + } + + if (scanBasedFilterOperator.getDataSourceMetadata().isSingleValue()) { + return basePriority; + } else { + // Lower priority for multi-value column + return basePriority + 50; + } + } + } + /** * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or {@link OrFilterOperator}). */ @@ -44,47 +257,7 @@ public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predic */ public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, boolean nullHandlingEnabled) { - if (predicateEvaluator.isAlwaysFalse()) { - return EmptyFilterOperator.getInstance(); - } else if (predicateEvaluator.isAlwaysTrue()) { - return new MatchAllFilterOperator(numDocs); - } - - // Currently sorted index based filtering is supported only for - // dictionary encoded columns. The on-disk segment metadata - // will indicate if the column is sorted or not regardless of - // whether it is raw or dictionary encoded. Here when creating - // the filter operator, we need to make sure that sort filter - // operator is used only if the column is sorted and has dictionary. - Predicate.Type predicateType = predicateEvaluator.getPredicateType(); - if (predicateType == Predicate.Type.RANGE) { - if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null) { - return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - if (RangeIndexBasedFilterOperator.canEvaluate(predicateEvaluator, dataSource)) { - return new RangeIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); - } else if (predicateType == Predicate.Type.REGEXP_LIKE) { - if (dataSource.getFSTIndex() != null && dataSource.getDataSourceMetadata().isSorted()) { - return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - if (dataSource.getFSTIndex() != null && dataSource.getInvertedIndex() != null) { - return new BitmapBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); - } else { - if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null) { - return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - if (dataSource.getInvertedIndex() != null) { - return new BitmapBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - if (RangeIndexBasedFilterOperator.canEvaluate(predicateEvaluator, dataSource)) { - return new RangeIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); - } - return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); - } + return _instance.getLeafFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); } /** @@ -92,26 +265,7 @@ public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predic */ public static BaseFilterOperator getAndFilterOperator(QueryContext queryContext, List filterOperators, int numDocs) { - List childFilterOperators = new ArrayList<>(filterOperators.size()); - for (BaseFilterOperator filterOperator : filterOperators) { - if (filterOperator.isResultEmpty()) { - return EmptyFilterOperator.getInstance(); - } else if (!filterOperator.isResultMatchingAll()) { - childFilterOperators.add(filterOperator); - } - } - int numChildFilterOperators = childFilterOperators.size(); - if (numChildFilterOperators == 0) { - // Return match all filter operator if all child filter operators match all records - return new MatchAllFilterOperator(numDocs); - } else if (numChildFilterOperators == 1) { - // Return the child filter operator if only one left - return childFilterOperators.get(0); - } else { - // Return the AND filter operator with re-ordered child filter operators - FilterOperatorUtils.reorderAndFilterChildOperators(queryContext, childFilterOperators); - return new AndFilterOperator(childFilterOperators, queryContext.getQueryOptions()); - } + return _instance.getAndFilterOperator(queryContext, filterOperators, numDocs); } /** @@ -119,25 +273,7 @@ public static BaseFilterOperator getAndFilterOperator(QueryContext queryContext, */ public static BaseFilterOperator getOrFilterOperator(QueryContext queryContext, List filterOperators, int numDocs) { - List childFilterOperators = new ArrayList<>(filterOperators.size()); - for (BaseFilterOperator filterOperator : filterOperators) { - if (filterOperator.isResultMatchingAll()) { - return new MatchAllFilterOperator(numDocs); - } else if (!filterOperator.isResultEmpty()) { - childFilterOperators.add(filterOperator); - } - } - int numChildFilterOperators = childFilterOperators.size(); - if (numChildFilterOperators == 0) { - // Return empty filter operator if all child filter operators's result is empty - return EmptyFilterOperator.getInstance(); - } else if (numChildFilterOperators == 1) { - // Return the child filter operator if only one left - return childFilterOperators.get(0); - } else { - // Return the OR filter operator with child filter operators - return new OrFilterOperator(childFilterOperators, numDocs); - } + return _instance.getOrFilterOperator(queryContext, filterOperators, numDocs); } /** @@ -145,84 +281,6 @@ public static BaseFilterOperator getOrFilterOperator(QueryContext queryContext, */ public static BaseFilterOperator getNotFilterOperator(QueryContext queryContext, BaseFilterOperator filterOperator, int numDocs) { - if (filterOperator.isResultMatchingAll()) { - return EmptyFilterOperator.getInstance(); - } else if (filterOperator.isResultEmpty()) { - return new MatchAllFilterOperator(numDocs); - } - - return new NotFilterOperator(filterOperator, numDocs); - } - - /** - * For AND filter operator, reorders its child filter operators based on the their cost and puts the ones with - * inverted index first in order to reduce the number of documents to be processed. - *

    Special filter operators such as {@link MatchAllFilterOperator} and {@link EmptyFilterOperator} should be - * removed from the list before calling this method. - */ - private static void reorderAndFilterChildOperators(QueryContext queryContext, - List filterOperators) { - filterOperators.sort(new Comparator() { - @Override - public int compare(BaseFilterOperator o1, BaseFilterOperator o2) { - return getPriority(o1) - getPriority(o2); - } - - int getPriority(BaseFilterOperator filterOperator) { - if (filterOperator instanceof SortedIndexBasedFilterOperator) { - return 0; - } - if (filterOperator instanceof BitmapBasedFilterOperator) { - return 1; - } - if (filterOperator instanceof RangeIndexBasedFilterOperator - || filterOperator instanceof TextContainsFilterOperator || filterOperator instanceof TextMatchFilterOperator - || filterOperator instanceof JsonMatchFilterOperator || filterOperator instanceof H3IndexFilterOperator - || filterOperator instanceof H3InclusionIndexFilterOperator) { - return 2; - } - if (filterOperator instanceof AndFilterOperator) { - return 3; - } - if (filterOperator instanceof OrFilterOperator) { - return 4; - } - if (filterOperator instanceof NotFilterOperator) { - return getPriority(((NotFilterOperator) filterOperator).getChildFilterOperator()); - } - if (filterOperator instanceof ScanBasedFilterOperator) { - return getScanBasedFilterPriority(queryContext, (ScanBasedFilterOperator) filterOperator, 5); - } - if (filterOperator instanceof ExpressionFilterOperator) { - return 10; - } - throw new IllegalStateException(filterOperator.getClass().getSimpleName() - + " should not be reordered, remove it from the list before calling this method"); - } - }); - } - - /** - * Returns the priority for scan based filtering. Multivalue column evaluation is costly, so - * reorder such that multivalue columns are evaluated after single value columns. - * - * TODO: additional cost based prioritization to be added - * - * @param scanBasedFilterOperator the filter operator to prioritize - * @param queryContext query context - * @return the priority to be associated with the filter - */ - private static int getScanBasedFilterPriority(QueryContext queryContext, - ScanBasedFilterOperator scanBasedFilterOperator, int basePriority) { - if (queryContext.isSkipScanFilterReorder()) { - return basePriority; - } - - if (scanBasedFilterOperator.getDataSourceMetadata().isSingleValue()) { - return basePriority; - } else { - // Lower priority for multi-value column - return basePriority + 1; - } + return _instance.getNotFilterOperator(queryContext, filterOperator, numDocs); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java index 081afdf2716..3df7a880884 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java @@ -47,24 +47,23 @@ * A filter operator that uses H3 index for geospatial data inclusion */ public class H3InclusionIndexFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "INCLUSION_FILTER_H3_INDEX"; private static final String LITERAL_H3_CELLS_CACHE_NAME = "st_contain_literal_h3_cells"; private final IndexSegment _segment; + private final QueryContext _queryContext; private final Predicate _predicate; private final int _numDocs; private final H3IndexReader _h3IndexReader; private final Geometry _geometry; private final boolean _isPositiveCheck; - private final QueryContext _queryContext; - public H3InclusionIndexFilterOperator(IndexSegment segment, Predicate predicate, QueryContext queryContext, + public H3InclusionIndexFilterOperator(IndexSegment segment, QueryContext queryContext, Predicate predicate, int numDocs) { _segment = segment; + _queryContext = queryContext; _predicate = predicate; _numDocs = numDocs; - _queryContext = queryContext; List arguments = predicate.getLhs().getFunction().getArguments(); EqPredicate eqPredicate = (EqPredicate) predicate; @@ -72,10 +71,10 @@ public H3InclusionIndexFilterOperator(IndexSegment segment, Predicate predicate, if (arguments.get(0).getType() == ExpressionContext.Type.IDENTIFIER) { _h3IndexReader = segment.getDataSource(arguments.get(0).getIdentifier()).getH3Index(); - _geometry = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(1).getLiteralString())); + _geometry = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(1).getLiteral().getStringValue())); } else { _h3IndexReader = segment.getDataSource(arguments.get(1).getIdentifier()).getH3Index(); - _geometry = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(0).getLiteralString())); + _geometry = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(0).getLiteral().getStringValue())); } // must be some h3 index assert _h3IndexReader != null : "the column must have H3 index setup."; @@ -125,7 +124,8 @@ protected FilterBlock getNextBlock() { * Returns the filter block based on the given the partial match doc ids. */ private FilterBlock getFilterBlock(MutableRoaringBitmap fullMatchDocIds, MutableRoaringBitmap partialMatchDocIds) { - ExpressionFilterOperator expressionFilterOperator = new ExpressionFilterOperator(_segment, _predicate, _numDocs); + ExpressionFilterOperator expressionFilterOperator = + new ExpressionFilterOperator(_segment, _queryContext, _predicate, _numDocs); ScanBasedDocIdIterator docIdIterator = (ScanBasedDocIdIterator) expressionFilterOperator.getNextBlock().getBlockDocIdSet().iterator(); MutableRoaringBitmap result = docIdIterator.applyAnd(partialMatchDocIds); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java index 9f91127c121..96109ba5499 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java @@ -33,6 +33,7 @@ import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.H3Utils; import org.apache.pinot.segment.spi.IndexSegment; @@ -46,9 +47,10 @@ * A filter operator that uses H3 index for geospatial data retrieval */ public class H3IndexFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_H3_INDEX"; + private final IndexSegment _segment; + private final QueryContext _queryContext; private final Predicate _predicate; private final int _numDocs; private final H3IndexReader _h3IndexReader; @@ -57,8 +59,9 @@ public class H3IndexFilterOperator extends BaseFilterOperator { private final double _lowerBound; private final double _upperBound; - public H3IndexFilterOperator(IndexSegment segment, Predicate predicate, int numDocs) { + public H3IndexFilterOperator(IndexSegment segment, QueryContext queryContext, Predicate predicate, int numDocs) { _segment = segment; + _queryContext = queryContext; _predicate = predicate; _numDocs = numDocs; @@ -67,12 +70,12 @@ public H3IndexFilterOperator(IndexSegment segment, Predicate predicate, int numD Coordinate coordinate; if (arguments.get(0).getType() == ExpressionContext.Type.IDENTIFIER) { _h3IndexReader = segment.getDataSource(arguments.get(0).getIdentifier()).getH3Index(); - coordinate = - GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(1).getLiteralString())).getCoordinate(); + coordinate = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(1).getLiteral().getStringValue())) + .getCoordinate(); } else { _h3IndexReader = segment.getDataSource(arguments.get(1).getIdentifier()).getH3Index(); - coordinate = - GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(0).getLiteralString())).getCoordinate(); + coordinate = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(0).getLiteral().getStringValue())) + .getCoordinate(); } assert _h3IndexReader != null; int resolution = _h3IndexReader.getH3IndexResolution().getLowestResolution(); @@ -182,7 +185,7 @@ protected FilterBlock getNextBlock() { return getFilterBlock(fullMatchDocIds, partialMatchDocIds); } catch (Exception e) { // Fall back to ExpressionFilterOperator when the execution encounters exception (e.g. numRings is too large) - return new ExpressionFilterOperator(_segment, _predicate, _numDocs).getNextBlock(); + return new ExpressionFilterOperator(_segment, _queryContext, _predicate, _numDocs).getNextBlock(); } } @@ -229,7 +232,8 @@ private List getH3Ids(int numRings) { * Returns the filter block based on the given full match doc ids and the partial match doc ids. */ private FilterBlock getFilterBlock(MutableRoaringBitmap fullMatchDocIds, MutableRoaringBitmap partialMatchDocIds) { - ExpressionFilterOperator expressionFilterOperator = new ExpressionFilterOperator(_segment, _predicate, _numDocs); + ExpressionFilterOperator expressionFilterOperator = + new ExpressionFilterOperator(_segment, _queryContext, _predicate, _numDocs); ScanBasedDocIdIterator docIdIterator = (ScanBasedDocIdIterator) expressionFilterOperator.getNextBlock().getBlockDocIdSet().iterator(); MutableRoaringBitmap result = docIdIterator.applyAnd(partialMatchDocIds); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/InvertedIndexFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/InvertedIndexFilterOperator.java new file mode 100644 index 00000000000..8f617417a3a --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/InvertedIndexFilterOperator.java @@ -0,0 +1,158 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.Collections; +import java.util.List; +import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.EmptyFilterBlock; +import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; +import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.reader.InvertedIndexReader; +import org.apache.pinot.spi.trace.FilterType; +import org.apache.pinot.spi.trace.InvocationRecording; +import org.apache.pinot.spi.trace.Tracing; +import org.roaringbitmap.buffer.ImmutableRoaringBitmap; +import org.roaringbitmap.buffer.MutableRoaringBitmap; + + +public class InvertedIndexFilterOperator extends BaseFilterOperator { + private static final String EXPLAIN_NAME = "FILTER_INVERTED_INDEX"; + + private final PredicateEvaluator _predicateEvaluator; + private final InvertedIndexReader _invertedIndexReader; + private final boolean _exclusive; + private final int _numDocs; + + InvertedIndexFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { + _predicateEvaluator = predicateEvaluator; + @SuppressWarnings("unchecked") + InvertedIndexReader invertedIndexReader = + (InvertedIndexReader) dataSource.getInvertedIndex(); + _invertedIndexReader = invertedIndexReader; + _exclusive = predicateEvaluator.isExclusive(); + _numDocs = numDocs; + } + + @Override + protected FilterBlock getNextBlock() { + int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); + int numDictIds = dictIds.length; + if (numDictIds == 0) { + return EmptyFilterBlock.getInstance(); + } + if (numDictIds == 1) { + ImmutableRoaringBitmap docIds = _invertedIndexReader.getDocIds(dictIds[0]); + if (_exclusive) { + if (docIds instanceof MutableRoaringBitmap) { + MutableRoaringBitmap mutableRoaringBitmap = (MutableRoaringBitmap) docIds; + mutableRoaringBitmap.flip(0L, _numDocs); + return new FilterBlock(new BitmapDocIdSet(mutableRoaringBitmap, _numDocs)); + } else { + return new FilterBlock(new BitmapDocIdSet(ImmutableRoaringBitmap.flip(docIds, 0L, _numDocs), _numDocs)); + } + } else { + return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs)); + } + } else { + ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[numDictIds]; + for (int i = 0; i < numDictIds; i++) { + bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); + } + MutableRoaringBitmap docIds = ImmutableRoaringBitmap.or(bitmaps); + if (_exclusive) { + docIds.flip(0L, _numDocs); + } + InvocationRecording recording = Tracing.activeRecording(); + if (recording.isEnabled()) { + recording.setColumnName(_predicateEvaluator.getPredicate().getLhs().getIdentifier()); + recording.setNumDocsMatchingAfterFilter(docIds.getCardinality()); + recording.setFilter(FilterType.INDEX, String.valueOf(_predicateEvaluator.getPredicateType())); + } + return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs)); + } + } + + @Override + public boolean canOptimizeCount() { + return true; + } + + @Override + public int getNumMatchingDocs() { + int count = 0; + int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); + switch (dictIds.length) { + case 0: + break; + case 1: { + count = _invertedIndexReader.getDocIds(dictIds[0]).getCardinality(); + break; + } + case 2: { + count = ImmutableRoaringBitmap.orCardinality(_invertedIndexReader.getDocIds(dictIds[0]), + _invertedIndexReader.getDocIds(dictIds[1])); + break; + } + default: { + // this could be optimised if the bitmaps are known to be disjoint (as in a single value bitmap index) + MutableRoaringBitmap bitmap = new MutableRoaringBitmap(); + for (int dictId : dictIds) { + bitmap.or(_invertedIndexReader.getDocIds(dictId)); + } + count = bitmap.getCardinality(); + break; + } + } + return _exclusive ? _numDocs - count : count; + } + + @Override + public boolean canProduceBitmaps() { + return true; + } + + @Override + public BitmapCollection getBitmaps() { + int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); + ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[dictIds.length]; + for (int i = 0; i < dictIds.length; i++) { + bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); + } + return new BitmapCollection(_numDocs, _exclusive, bitmaps); + } + + @Override + @SuppressWarnings("rawtypes") + public List getChildOperators() { + return Collections.emptyList(); + } + + @Override + public String toExplainString() { + StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(indexLookUp:inverted_index"); + Predicate predicate = _predicateEvaluator.getPredicate(); + stringBuilder.append(",operator:").append(predicate.getType()); + stringBuilder.append(",predicate:").append(predicate.toString()); + return stringBuilder.append(')').toString(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java index 490851db8a7..e4f5d9f6622 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; @@ -63,4 +64,9 @@ public int getNumMatchingDocs() { public String toExplainString() { return new StringBuilder(EXPLAIN_NAME).append("(docs:").append(_numDocs).append(')').toString(); } + + @Override + public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + explainPlanRows.setHasMatchAllFilter(true); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java index 3d262d5f017..114703ec062 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java @@ -20,9 +20,9 @@ import java.util.ArrayList; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.core.operator.docidsets.OrDocIdSet; import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.buffer.BufferFastAggregation; @@ -30,8 +30,8 @@ public class OrFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_OR"; + private final List _filterOperators; private final int _numDocs; @@ -43,14 +43,13 @@ public OrFilterOperator(List filterOperators, int numDocs) { @Override protected FilterBlock getNextBlock() { Tracing.activeRecording().setNumChildren(_filterOperators.size()); - List filterBlockDocIdSets = new ArrayList<>(_filterOperators.size()); + List blockDocIdSets = new ArrayList<>(_filterOperators.size()); for (BaseFilterOperator filterOperator : _filterOperators) { - filterBlockDocIdSets.add(filterOperator.nextBlock().getBlockDocIdSet()); + blockDocIdSets.add(filterOperator.nextBlock().getBlockDocIdSet()); } - return new FilterBlock(new OrDocIdSet(filterBlockDocIdSets, _numDocs)); + return new FilterBlock(new OrDocIdSet(blockDocIdSets, _numDocs)); } - @Override public String toExplainString() { return EXPLAIN_NAME; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/PrioritizedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/PrioritizedFilterOperator.java new file mode 100644 index 00000000000..d847a34a6d3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/PrioritizedFilterOperator.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.OptionalInt; +import org.apache.pinot.core.common.Block; +import org.apache.pinot.core.common.Operator; + + +/** + * Operators that implements this interface can define how to be sorted in an AND, as defined in + * {@link FilterOperatorUtils.Implementation#}. + */ +public interface PrioritizedFilterOperator extends Operator { + + int HIGH_PRIORITY = 0; + int MEDIUM_PRIORITY = 100; + int LOW_PRIORITY = 200; + int AND_PRIORITY = 300; + int OR_PRIORITY = 400; + int SCAN_PRIORITY = 500; + int EXPRESSION_PRIORITY = 1000; + int UNKNOWN_FILTER_PRIORITY = 10000; + + /** + * Priority is a number that is used to compare different filters. Some predicates, like AND, sort their sub + * predicates in order to first apply the ones that should be more efficient. + * + * For example, {@link SortedIndexBasedFilterOperator} is assigned to {@link #HIGH_PRIORITY}, + * {@link BitmapBasedFilterOperator} is assigned to {@link #MEDIUM_PRIORITY} and {@link H3IndexFilterOperator} to + * {@link #LOW_PRIORITY} + * + * @return the priority of this filter operator or an empty optional if no special priority should be used. + */ + OptionalInt getPriority(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java index 277fa4429fe..244718998c2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java @@ -21,11 +21,11 @@ import java.util.Collections; import java.util.List; import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.filter.predicate.traits.DoubleRange; import org.apache.pinot.core.operator.filter.predicate.traits.DoubleValue; @@ -95,7 +95,7 @@ private FilterBlock evaluateLegacyRangeFilter() { // Need to scan the first and last range as they might be partially matched ScanBasedFilterOperator scanBasedFilterOperator = new ScanBasedFilterOperator(_predicateEvaluator, _dataSource, _numDocs, false); - FilterBlockDocIdSet scanBasedDocIdSet = scanBasedFilterOperator.getNextBlock().getBlockDocIdSet(); + BlockDocIdSet scanBasedDocIdSet = scanBasedFilterOperator.getNextBlock().getBlockDocIdSet(); MutableRoaringBitmap docIds = ((ScanBasedDocIdIterator) scanBasedDocIdSet.iterator()).applyAnd(partialMatches); if (matches != null) { docIds.or(matches); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java index 89305d24fb1..b2241cfc6f2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdIterator; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.MVScanDocIdSet; @@ -37,9 +38,15 @@ public class ScanBasedFilterOperator extends BaseFilterOperator { private final DataSource _dataSource; private final int _numDocs; private final boolean _nullHandlingEnabled; + private final int _batchSize; - ScanBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, + public ScanBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, boolean nullHandlingEnabled) { + this(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled, BlockDocIdIterator.OPTIMAL_ITERATOR_BATCH_SIZE); + } + + public ScanBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, + boolean nullHandlingEnabled, int batchSize) { _predicateEvaluator = predicateEvaluator; _dataSource = dataSource; _numDocs = numDocs; @@ -47,13 +54,15 @@ public class ScanBasedFilterOperator extends BaseFilterOperator { Preconditions.checkState(_dataSource.getForwardIndex() != null, "Forward index disabled for column: %s, scan based filtering not supported!", _dataSource.getDataSourceMetadata().getFieldSpec().getName()); + _batchSize = batchSize; } @Override protected FilterBlock getNextBlock() { DataSourceMetadata dataSourceMetadata = _dataSource.getDataSourceMetadata(); if (dataSourceMetadata.isSingleValue()) { - return new FilterBlock(new SVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs, _nullHandlingEnabled)); + return new FilterBlock(new SVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs, _nullHandlingEnabled, + _batchSize)); } else { return new FilterBlock(new MVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs)); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java index dcc3f34f65b..14616b36be7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java @@ -21,12 +21,15 @@ import java.math.BigDecimal; import java.util.Arrays; import org.apache.pinot.common.request.context.predicate.EqPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.core.operator.filter.predicate.traits.DoubleValue; import org.apache.pinot.core.operator.filter.predicate.traits.FloatValue; import org.apache.pinot.core.operator.filter.predicate.traits.IntValue; import org.apache.pinot.core.operator.filter.predicate.traits.LongValue; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; +import org.apache.pinot.spi.data.SingleValueVisitor; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.BytesUtils; import org.apache.pinot.spi.utils.TimestampUtils; @@ -59,7 +62,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based EQ predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(EqPredicate eqPredicate, + public static EqRawPredicateEvaluator newRawValueBasedEvaluator(EqPredicate eqPredicate, DataType dataType) { String value = eqPredicate.getValue(); switch (dataType) { @@ -140,8 +143,25 @@ public int getInt() { } } - private static final class IntRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator - implements IntValue { + public static abstract class EqRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public EqRawPredicateEvaluator(Predicate predicate) { + super(predicate); + } + + /** + * Visits the matching value of this predicate. + */ + public abstract R accept(SingleValueVisitor visitor); + + /** + * Visits the matching value of this predicate, which will be transformed into an array with a single value. + */ + public R accept(MultiValueVisitor visitor) { + return accept(visitor.asSingleValueVisitor()); + } + } + + private static final class IntRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator implements IntValue { final int _matchingValue; IntRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, int matchingValue) { @@ -149,6 +169,11 @@ private static final class IntRawValueBasedEqPredicateEvaluator extends BaseRawV _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitInt(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -183,7 +208,7 @@ public int getInt() { } } - private static final class LongRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + private static final class LongRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator implements LongValue { final long _matchingValue; @@ -192,6 +217,16 @@ private static final class LongRawValueBasedEqPredicateEvaluator extends BaseRaw _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitLong(_matchingValue); + } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.asSingleValueVisitor().visitLong(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -226,7 +261,7 @@ public long getLong() { } } - private static final class FloatRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + private static final class FloatRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator implements FloatValue { final float _matchingValue; @@ -235,6 +270,11 @@ private static final class FloatRawValueBasedEqPredicateEvaluator extends BaseRa _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitFloat(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -269,7 +309,7 @@ public float getFloat() { } } - private static final class DoubleRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + private static final class DoubleRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator implements DoubleValue { final double _matchingValue; @@ -278,6 +318,11 @@ private static final class DoubleRawValueBasedEqPredicateEvaluator extends BaseR _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitDouble(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -312,7 +357,7 @@ public double getDouble() { } } - private static final class BigDecimalRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator { final BigDecimal _matchingValue; BigDecimalRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, BigDecimal matchingValue) { @@ -320,6 +365,11 @@ private static final class BigDecimalRawValueBasedEqPredicateEvaluator extends B _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBigDecimal(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -336,7 +386,7 @@ public boolean applySV(BigDecimal value) { } } - private static final class StringRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator { final String _matchingValue; StringRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, String matchingValue) { @@ -344,6 +394,11 @@ private static final class StringRawValueBasedEqPredicateEvaluator extends BaseR _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitString(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -360,7 +415,7 @@ public boolean applySV(String value) { } } - private static final class BytesRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator { final byte[] _matchingValue; BytesRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, byte[] matchingValue) { @@ -368,6 +423,11 @@ private static final class BytesRawValueBasedEqPredicateEvaluator extends BaseRa _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBytes(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java index 6559ba502ff..9ad0a78014c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java @@ -32,10 +32,14 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.InPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; import org.apache.pinot.spi.utils.ByteArray; @@ -49,14 +53,15 @@ private InPredicateEvaluatorFactory() { /** * Create a new instance of dictionary based IN predicate evaluator. * - * @param inPredicate IN predicate to evaluate - * @param dictionary Dictionary for the column - * @param dataType Data type for the column + * @param inPredicate IN predicate to evaluate + * @param dictionary Dictionary for the column + * @param dataType Data type for the column + * @param queryContext Query context * @return Dictionary based IN predicate evaluator */ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator(InPredicate inPredicate, - Dictionary dictionary, DataType dataType) { - return new DictionaryBasedInPredicateEvaluator(inPredicate, dictionary, dataType); + Dictionary dictionary, DataType dataType, @Nullable QueryContext queryContext) { + return new DictionaryBasedInPredicateEvaluator(inPredicate, dictionary, dataType, queryContext); } /** @@ -66,7 +71,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based IN predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(InPredicate inPredicate, + public static InRawPredicateEvaluator newRawValueBasedEvaluator(InPredicate inPredicate, DataType dataType) { switch (dataType) { case INT: { @@ -155,9 +160,10 @@ private static final class DictionaryBasedInPredicateEvaluator extends BaseDicti final int _numMatchingDictIds; int[] _matchingDictIds; - DictionaryBasedInPredicateEvaluator(InPredicate inPredicate, Dictionary dictionary, DataType dataType) { + DictionaryBasedInPredicateEvaluator(InPredicate inPredicate, Dictionary dictionary, DataType dataType, + @Nullable QueryContext queryContext) { super(inPredicate); - _matchingDictIdSet = PredicateUtils.getDictIdSet(inPredicate, dictionary, dataType); + _matchingDictIdSet = PredicateUtils.getDictIdSet(inPredicate, dictionary, dataType, queryContext); _numMatchingDictIds = _matchingDictIdSet.size(); if (_numMatchingDictIds == 0) { _alwaysFalse = true; @@ -203,7 +209,18 @@ public int applySV(int limit, int[] docIds, int[] values) { } } - private static final class IntRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public static abstract class InRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public InRawPredicateEvaluator(Predicate predicate) { + super(predicate); + } + + /** + * Visits the matching value of this predicate. + */ + public abstract R accept(MultiValueVisitor visitor); + } + + private static final class IntRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final IntSet _matchingValues; IntRawValueBasedInPredicateEvaluator(InPredicate inPredicate, IntSet matchingValues) { @@ -238,9 +255,14 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitInt(_matchingValues.toIntArray()); + } } - private static final class LongRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final LongSet _matchingValues; LongRawValueBasedInPredicateEvaluator(InPredicate inPredicate, LongSet matchingValues) { @@ -275,9 +297,14 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitLong(_matchingValues.toLongArray()); + } } - private static final class FloatRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final FloatSet _matchingValues; FloatRawValueBasedInPredicateEvaluator(InPredicate inPredicate, FloatSet matchingValues) { @@ -312,9 +339,14 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitFloat(_matchingValues.toFloatArray()); + } } - private static final class DoubleRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final DoubleSet _matchingValues; DoubleRawValueBasedInPredicateEvaluator(InPredicate inPredicate, DoubleSet matchingValues) { @@ -349,9 +381,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitDouble(_matchingValues.toDoubleArray()); + } } - private static final class BigDecimalRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { // Note: BigDecimal's compareTo is not consistent with equals (e.g. compareTo(3.0, 3) returns zero when // equals(3.0, 3) returns false). // - HashSet implementation consider both hashCode() and equals() for the key. @@ -379,9 +416,14 @@ public DataType getDataType() { public boolean applySV(BigDecimal value) { return _matchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitBigDecimal(_matchingValues.toArray(new BigDecimal[0])); + } } - private static final class StringRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final Set _matchingValues; StringRawValueBasedInPredicateEvaluator(InPredicate inPredicate, Set matchingValues) { @@ -403,9 +445,14 @@ public DataType getDataType() { public boolean applySV(String value) { return _matchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitString(_matchingValues.toArray(new String[0])); + } } - private static final class BytesRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final Set _matchingValues; BytesRawValueBasedInPredicateEvaluator(InPredicate inPredicate, Set matchingValues) { @@ -427,5 +474,13 @@ public DataType getDataType() { public boolean applySV(byte[] value) { return _matchingValues.contains(new ByteArray(value)); } + + @Override + public R accept(MultiValueVisitor visitor) { + byte[][] bytes = _matchingValues.stream() + .map(ByteArray::getBytes) + .toArray(byte[][]::new); + return visitor.visitBytes(bytes); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java index f091039f1df..54ce7df58cb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java @@ -21,8 +21,11 @@ import java.math.BigDecimal; import java.util.Arrays; import org.apache.pinot.common.request.context.predicate.NotEqPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; +import org.apache.pinot.spi.data.SingleValueVisitor; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.BytesUtils; import org.apache.pinot.spi.utils.TimestampUtils; @@ -55,7 +58,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based NOT_EQ predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotEqPredicate notEqPredicate, + public static NeqRawPredicateEvaluator newRawValueBasedEvaluator(NotEqPredicate notEqPredicate, DataType dataType) { String value = notEqPredicate.getValue(); switch (dataType) { @@ -155,7 +158,25 @@ public int[] getNonMatchingDictIds() { } } - private static final class IntRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public static abstract class NeqRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public NeqRawPredicateEvaluator(Predicate predicate) { + super(predicate); + } + + /** + * Visits the not matching value of this predicate. + */ + public abstract R accept(SingleValueVisitor visitor); + + /** + * Visits the not matching value of this predicate, which will be transformed into an array with a single value. + */ + public R accept(MultiValueVisitor visitor) { + return accept(visitor.asSingleValueVisitor()); + } + } + + private static final class IntRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final int _nonMatchingValue; IntRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, int nonMatchingValue) { @@ -190,9 +211,14 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitInt(_nonMatchingValue); + } } - private static final class LongRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final long _nonMatchingValue; LongRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, long nonMatchingValue) { @@ -227,9 +253,14 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitLong(_nonMatchingValue); + } } - private static final class FloatRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final float _nonMatchingValue; FloatRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, float nonMatchingValue) { @@ -264,9 +295,14 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitFloat(_nonMatchingValue); + } } - private static final class DoubleRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final double _nonMatchingValue; DoubleRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, double nonMatchingValue) { @@ -301,9 +337,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitDouble(_nonMatchingValue); + } } - private static final class BigDecimalRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final BigDecimal _nonMatchingValue; BigDecimalRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, BigDecimal nonMatchingValue) { @@ -325,9 +366,14 @@ public DataType getDataType() { public boolean applySV(BigDecimal value) { return _nonMatchingValue.compareTo(value) != 0; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBigDecimal(_nonMatchingValue); + } } - private static final class StringRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final String _nonMatchingValue; StringRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, String nonMatchingValue) { @@ -349,9 +395,14 @@ public DataType getDataType() { public boolean applySV(String value) { return !_nonMatchingValue.equals(value); } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitString(_nonMatchingValue); + } } - private static final class BytesRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final byte[] _nonMatchingValue; BytesRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, byte[] nonMatchingValue) { @@ -373,5 +424,10 @@ public DataType getDataType() { public boolean applySV(byte[] value) { return !Arrays.equals(_nonMatchingValue, value); } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBytes(_nonMatchingValue); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java index b3666b932f1..5fe7b51d357 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java @@ -32,10 +32,14 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.NotInPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; import org.apache.pinot.spi.utils.ByteArray; @@ -50,13 +54,14 @@ private NotInPredicateEvaluatorFactory() { * Create a new instance of dictionary based NOT_IN predicate evaluator. * * @param notInPredicate NOT_IN predicate to evaluate - * @param dictionary Dictionary for the column - * @param dataType Data type for the column + * @param dictionary Dictionary for the column + * @param dataType Data type for the column + * @param queryContext Query context * @return Dictionary based NOT_IN predicate evaluator */ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator(NotInPredicate notInPredicate, - Dictionary dictionary, DataType dataType) { - return new DictionaryBasedNotInPredicateEvaluator(notInPredicate, dictionary, dataType); + Dictionary dictionary, DataType dataType, @Nullable QueryContext queryContext) { + return new DictionaryBasedNotInPredicateEvaluator(notInPredicate, dictionary, dataType, queryContext); } /** @@ -66,7 +71,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based NOT_IN predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotInPredicate notInPredicate, + public static NotInRawPredicateEvaluator newRawValueBasedEvaluator(NotInPredicate notInPredicate, DataType dataType) { switch (dataType) { case INT: { @@ -157,9 +162,10 @@ public static final class DictionaryBasedNotInPredicateEvaluator extends BaseDic int[] _matchingDictIds; int[] _nonMatchingDictIds; - DictionaryBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Dictionary dictionary, DataType dataType) { + DictionaryBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Dictionary dictionary, DataType dataType, + @Nullable QueryContext queryContext) { super(notInPredicate); - _nonMatchingDictIdSet = PredicateUtils.getDictIdSet(notInPredicate, dictionary, dataType); + _nonMatchingDictIdSet = PredicateUtils.getDictIdSet(notInPredicate, dictionary, dataType, queryContext); _numNonMatchingDictIds = _nonMatchingDictIdSet.size(); if (_numNonMatchingDictIds == 0) { _alwaysTrue = true; @@ -221,7 +227,18 @@ public int[] getNonMatchingDictIds() { } } - private static final class IntRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public static abstract class NotInRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public NotInRawPredicateEvaluator(Predicate predicate) { + super(predicate); + } + + /** + * Visits the not matching value of this predicate. + */ + public abstract R accept(MultiValueVisitor visitor); + } + + private static final class IntRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final IntSet _nonMatchingValues; IntRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, IntSet nonMatchingValues) { @@ -256,9 +273,14 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitInt(_nonMatchingValues.toIntArray()); + } } - private static final class LongRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final LongSet _nonMatchingValues; LongRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, LongSet nonMatchingValues) { @@ -293,9 +315,14 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitLong(_nonMatchingValues.toLongArray()); + } } - private static final class FloatRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final FloatSet _nonMatchingValues; FloatRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, FloatSet nonMatchingValues) { @@ -330,9 +357,14 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitFloat(_nonMatchingValues.toFloatArray()); + } } - private static final class DoubleRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final DoubleSet _nonMatchingValues; DoubleRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, DoubleSet nonMatchingValues) { @@ -367,10 +399,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitDouble(_nonMatchingValues.toDoubleArray()); + } } - private static final class BigDecimalRawValueBasedNotInPredicateEvaluator - extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { // See: BigDecimalRawValueBasedInPredicateEvaluator. final TreeSet _nonMatchingValues; @@ -394,9 +430,14 @@ public DataType getDataType() { public boolean applySV(BigDecimal value) { return !_nonMatchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitBigDecimal(_nonMatchingValues.toArray(new BigDecimal[0])); + } } - private static final class StringRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final Set _nonMatchingValues; StringRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Set nonMatchingValues) { @@ -418,9 +459,14 @@ public DataType getDataType() { public boolean applySV(String value) { return !_nonMatchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitString(_nonMatchingValues.toArray(new String[0])); + } } - private static final class BytesRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final Set _nonMatchingValues; BytesRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Set nonMatchingValues) { @@ -442,5 +488,13 @@ public DataType getDataType() { public boolean applySV(byte[] value) { return !_nonMatchingValues.contains(new ByteArray(value)); } + + @Override + public R accept(MultiValueVisitor visitor) { + byte[][] bytes = _nonMatchingValues.stream() + .map(ByteArray::getBytes) + .toArray(byte[][]::new); + return visitor.visitBytes(bytes); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java index 0a3bc7f952f..519168818b8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java @@ -26,6 +26,8 @@ import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.request.context.predicate.RangePredicate; import org.apache.pinot.common.request.context.predicate.RegexpLikePredicate; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.exception.BadQueryRequestException; @@ -37,6 +39,11 @@ private PredicateEvaluatorProvider() { public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nullable Dictionary dictionary, DataType dataType) { + return getPredicateEvaluator(predicate, dictionary, dataType, null); + } + + public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nullable Dictionary dictionary, + DataType dataType, @Nullable QueryContext queryContext) { try { if (dictionary != null) { // dictionary based predicate evaluators @@ -48,11 +55,11 @@ public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nul return NotEqualsPredicateEvaluatorFactory .newDictionaryBasedEvaluator((NotEqPredicate) predicate, dictionary, dataType); case IN: - return InPredicateEvaluatorFactory - .newDictionaryBasedEvaluator((InPredicate) predicate, dictionary, dataType); + return InPredicateEvaluatorFactory.newDictionaryBasedEvaluator((InPredicate) predicate, dictionary, + dataType, queryContext); case NOT_IN: - return NotInPredicateEvaluatorFactory - .newDictionaryBasedEvaluator((NotInPredicate) predicate, dictionary, dataType); + return NotInPredicateEvaluatorFactory.newDictionaryBasedEvaluator((NotInPredicate) predicate, dictionary, + dataType, queryContext); case RANGE: return RangePredicateEvaluatorFactory .newDictionaryBasedEvaluator((RangePredicate) predicate, dictionary, dataType); @@ -87,4 +94,10 @@ public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nul throw new BadQueryRequestException(e); } } + + public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, DataSource dataSource, + QueryContext queryContext) { + return getPredicateEvaluator(predicate, dataSource.getDictionary(), + dataSource.getDataSourceMetadata().getDataType(), queryContext); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java index a487d36654f..52dffb5f042 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java @@ -18,16 +18,21 @@ */ package org.apache.pinot.core.operator.filter.predicate; +import com.google.common.base.Equivalence; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.BaseInPredicate; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.ByteArray; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.TimestampUtils; @@ -70,7 +75,8 @@ public static String getStoredTimestampValue(String timestampValue) { /** * Returns a dictionary id set of the values in the given IN/NOT_IN predicate. */ - public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictionary, DataType dataType) { + public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictionary, DataType dataType, + @Nullable QueryContext queryContext) { List values = inPredicate.getValues(); int hashSetSize = Integer.min(HashUtil.getMinHashSetSize(values.size()), MAX_INITIAL_DICT_ID_SET_SIZE); IntSet dictIdSet = new IntOpenHashSet(hashSetSize); @@ -139,11 +145,23 @@ public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictio } break; case STRING: - for (String value : values) { - int dictId = dictionary.indexOf(value); - if (dictId >= 0) { - dictIdSet.add(dictId); + if (queryContext == null || values.size() <= Integer.parseInt(queryContext.getQueryOptions() + .getOrDefault(CommonConstants.Broker.Request.QueryOptionKey.IN_PREDICATE_SORT_THRESHOLD, + CommonConstants.Broker.Request.QueryOptionValue.DEFAULT_IN_PREDICATE_SORT_THRESHOLD))) { + for (String value : values) { + int dictId = dictionary.indexOf(value); + if (dictId >= 0) { + dictIdSet.add(dictId); + } } + } else { + List sortedValues = + queryContext.getOrComputeSharedValue(List.class, Equivalence.identity().wrap(inPredicate), k -> { + List copyValues = new ArrayList<>(values); + copyValues.sort(null); + return copyValues; + }); + dictionary.getDictIds(sortedValues, dictIdSet); } break; case BYTES: diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java index 346b8440e1e..ca8e1f126a2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java @@ -288,12 +288,14 @@ public boolean applySV(int dictId) { return _rawValueBasedEvaluator.applySV(_dictionary.getFloatValue(dictId)); case DOUBLE: return _rawValueBasedEvaluator.applySV(_dictionary.getDoubleValue(dictId)); + case BIG_DECIMAL: + return _rawValueBasedEvaluator.applySV(_dictionary.getBigDecimalValue(dictId)); case STRING: return _rawValueBasedEvaluator.applySV(_dictionary.getStringValue(dictId)); case BYTES: return _rawValueBasedEvaluator.applySV(_dictionary.getBytesValue(dictId)); default: - throw new IllegalStateException(); + throw new IllegalStateException("Unsupported value type: " + _dictionary.getValueType()); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java index a3af9de1949..82477c35600 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java @@ -44,7 +44,8 @@ private RegexpLikePredicateEvaluatorFactory() { */ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( RegexpLikePredicate regexpLikePredicate, Dictionary dictionary, DataType dataType) { - Preconditions.checkArgument(dataType == DataType.STRING, "Unsupported data type: " + dataType); + boolean condition = (dataType == DataType.STRING || dataType == DataType.JSON); + Preconditions.checkArgument(condition, "Unsupported data type: " + dataType); return new DictionaryBasedRegexpLikePredicateEvaluator(regexpLikePredicate, dictionary); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java index 97d81b61cfa..01d743b2699 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java @@ -20,12 +20,11 @@ import java.util.Collections; import java.util.List; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.AggregationExecutor; import org.apache.pinot.core.query.aggregation.DefaultAggregationExecutor; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; @@ -40,16 +39,16 @@ public class AggregationOperator extends BaseOperator { private static final String EXPLAIN_NAME = "AGGREGATE"; private final AggregationFunction[] _aggregationFunctions; - private final TransformOperator _transformOperator; + private final BaseProjectOperator _projectOperator; private final long _numTotalDocs; private final boolean _useStarTree; private int _numDocsScanned = 0; - public AggregationOperator(AggregationFunction[] aggregationFunctions, TransformOperator transformOperator, + public AggregationOperator(AggregationFunction[] aggregationFunctions, BaseProjectOperator projectOperator, long numTotalDocs, boolean useStarTree) { _aggregationFunctions = aggregationFunctions; - _transformOperator = transformOperator; + _projectOperator = projectOperator; _numTotalDocs = numTotalDocs; _useStarTree = useStarTree; } @@ -63,10 +62,10 @@ protected AggregationResultsBlock getNextBlock() { } else { aggregationExecutor = new DefaultAggregationExecutor(_aggregationFunctions); } - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - _numDocsScanned += transformBlock.getNumDocs(); - aggregationExecutor.aggregate(transformBlock); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + _numDocsScanned += valueBlock.getNumDocs(); + aggregationExecutor.aggregate(valueBlock); } // Build intermediate result block based on aggregation result from the executor @@ -74,14 +73,14 @@ protected AggregationResultsBlock getNextBlock() { } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, _numTotalDocs); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java index e8f1e015ca0..2ab339cb21c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; @@ -29,11 +28,12 @@ import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.ExecutionStatistics; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctTable; -import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.trace.Tracing; /** @@ -42,96 +42,77 @@ public class DictionaryBasedDistinctOperator extends BaseOperator { private static final String EXPLAIN_NAME = "DISTINCT_DICTIONARY"; - private final DistinctAggregationFunction _distinctAggregationFunction; - private final Dictionary _dictionary; - private final int _numTotalDocs; - private final boolean _nullHandlingEnabled; - private final FieldSpec.DataType _dataType; + private final DataSource _dataSource; + private final QueryContext _queryContext; - private boolean _hasOrderBy; - private boolean _isAscending; private int _numDocsScanned; - public DictionaryBasedDistinctOperator(FieldSpec.DataType dataType, - DistinctAggregationFunction distinctAggregationFunction, Dictionary dictionary, int numTotalDocs, - boolean nullHandlingEnabled) { - _dataType = dataType; - _distinctAggregationFunction = distinctAggregationFunction; - _dictionary = dictionary; - _numTotalDocs = numTotalDocs; - _nullHandlingEnabled = nullHandlingEnabled; - - List orderByExpressionContexts = _distinctAggregationFunction.getOrderByExpressions(); - if (orderByExpressionContexts != null) { - OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); - _isAscending = orderByExpressionContext.isAsc(); - _hasOrderBy = true; - } + public DictionaryBasedDistinctOperator(DataSource dataSource, QueryContext queryContext) { + _dataSource = dataSource; + _queryContext = queryContext; } @Override protected DistinctResultsBlock getNextBlock() { - return new DistinctResultsBlock(_distinctAggregationFunction, buildResult()); - } - - /** - * Build the final result for this operation - */ - private DistinctTable buildResult() { - - assert _distinctAggregationFunction.getType() == AggregationFunctionType.DISTINCT; - - List expressions = _distinctAggregationFunction.getInputExpressions(); - ExpressionContext expression = expressions.get(0); - DataSchema dataSchema = new DataSchema(new String[]{expression.toString()}, - new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.fromDataTypeSV(_dataType)}); - int dictLength = _dictionary.length(); - List records; - - int limit = _distinctAggregationFunction.getLimit(); - int actualLimit = Math.min(limit, dictLength); + String column = _queryContext.getSelectExpressions().get(0).getIdentifier(); + Dictionary dictionary = _dataSource.getDictionary(); + assert dictionary != null; + DataSourceMetadata dataSourceMetadata = _dataSource.getDataSourceMetadata(); + DataSchema dataSchema = new DataSchema(new String[]{column}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.fromDataTypeSV(dataSourceMetadata.getDataType())}); + int limit = _queryContext.getLimit(); + int dictLength = dictionary.length(); + int numValuesToKeep = Math.min(limit, dictLength); + boolean nullHandlingEnabled = _queryContext.isNullHandlingEnabled(); // If ORDER BY is not present, we read the first limit values from the dictionary and return. // If ORDER BY is present and the dictionary is sorted, then we read the first/last limit values // from the dictionary. If not sorted, then we read the entire dictionary and return it. - if (!_hasOrderBy) { - records = new ArrayList<>(actualLimit); - - _numDocsScanned = actualLimit; - - for (int i = 0; i < actualLimit; i++) { - records.add(new Record(new Object[]{_dictionary.getInternal(i)})); - } + DistinctTable distinctTable; + List orderByExpressions = _queryContext.getOrderByExpressions(); + if (orderByExpressions == null) { + distinctTable = + new DistinctTable(dataSchema, iterateOnDictionary(dictionary, numValuesToKeep), nullHandlingEnabled); + _numDocsScanned = numValuesToKeep; } else { - if (_dictionary.isSorted()) { - records = new ArrayList<>(actualLimit); - if (_isAscending) { - _numDocsScanned = actualLimit; - for (int i = 0; i < actualLimit; i++) { - records.add(new Record(new Object[]{_dictionary.getInternal(i)})); - } + if (dictionary.isSorted()) { + if (orderByExpressions.get(0).isAsc()) { + distinctTable = + new DistinctTable(dataSchema, iterateOnDictionary(dictionary, numValuesToKeep), nullHandlingEnabled); } else { - _numDocsScanned = actualLimit; - for (int i = dictLength - 1; i >= (dictLength - actualLimit); i--) { - records.add(new Record(new Object[]{_dictionary.getInternal(i)})); - } + distinctTable = + new DistinctTable(dataSchema, iterateOnDictionaryDesc(dictionary, numValuesToKeep), nullHandlingEnabled); } + _numDocsScanned = numValuesToKeep; } else { - // DictionaryBasedDistinctOperator cannot handle nulls. - DistinctTable distinctTable = - new DistinctTable(dataSchema, _distinctAggregationFunction.getOrderByExpressions(), limit, - _nullHandlingEnabled); - - _numDocsScanned = dictLength; + distinctTable = new DistinctTable(dataSchema, orderByExpressions, limit, nullHandlingEnabled); for (int i = 0; i < dictLength; i++) { - distinctTable.addWithOrderBy(new Record(new Object[]{_dictionary.getInternal(i)})); + distinctTable.addWithOrderBy(new Record(new Object[]{dictionary.getInternal(i)})); } - - return distinctTable; + _numDocsScanned = dictLength; } } - return new DistinctTable(dataSchema, records, _nullHandlingEnabled); + return new DistinctResultsBlock(distinctTable); + } + + private static List iterateOnDictionary(Dictionary dictionary, int length) { + List records = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(i); + records.add(new Record(new Object[]{dictionary.getInternal(i)})); + } + return records; + } + + private static List iterateOnDictionaryDesc(Dictionary dictionary, int length) { + List records = new ArrayList<>(length); + int dictLength = dictionary.length(); + for (int i = dictLength - 1, j = 0; i >= (dictLength - length); i--, j++) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(j); + records.add(new Record(new Object[]{dictionary.getInternal(i)})); + } + return records; } @Override @@ -147,6 +128,7 @@ public List getChildOperators() { @Override public ExecutionStatistics getExecutionStatistics() { // NOTE: Set numDocsScanned to numTotalDocs for backward compatibility. - return new ExecutionStatistics(_numDocsScanned, 0, _numDocsScanned, _numTotalDocs); + return new ExecutionStatistics(_numDocsScanned, 0, _numDocsScanned, + _dataSource.getDataSourceMetadata().getNumDocs()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java index 9ba1a65e86b..3d95a0c5473 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java @@ -20,13 +20,12 @@ import java.util.Collections; import java.util.List; -import org.apache.pinot.core.common.Operator; +import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctExecutorFactory; import org.apache.pinot.core.query.request.context.QueryContext; @@ -40,36 +39,34 @@ public class DistinctOperator extends BaseOperator { private static final String EXPLAIN_NAME = "DISTINCT"; private final IndexSegment _indexSegment; - private final DistinctAggregationFunction _distinctAggregationFunction; - private final TransformOperator _transformOperator; - private final DistinctExecutor _distinctExecutor; + private final BaseProjectOperator _projectOperator; + private final QueryContext _queryContext; private int _numDocsScanned = 0; - public DistinctOperator(IndexSegment indexSegment, DistinctAggregationFunction distinctAggregationFunction, - TransformOperator transformOperator, QueryContext queryContext) { + public DistinctOperator(IndexSegment indexSegment, BaseProjectOperator projectOperator, + QueryContext queryContext) { _indexSegment = indexSegment; - _distinctAggregationFunction = distinctAggregationFunction; - _transformOperator = transformOperator; - _distinctExecutor = DistinctExecutorFactory.getDistinctExecutor(distinctAggregationFunction, transformOperator, - queryContext.isNullHandlingEnabled()); + _projectOperator = projectOperator; + _queryContext = queryContext; } @Override protected DistinctResultsBlock getNextBlock() { - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - _numDocsScanned += transformBlock.getNumDocs(); - if (_distinctExecutor.process(transformBlock)) { + DistinctExecutor executor = DistinctExecutorFactory.getDistinctExecutor(_projectOperator, _queryContext); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + _numDocsScanned += valueBlock.getNumDocs(); + if (executor.process(valueBlock)) { break; } } - return new DistinctResultsBlock(_distinctAggregationFunction, _distinctExecutor.getResult()); + return new DistinctResultsBlock(executor.getResult()); } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override @@ -79,8 +76,8 @@ public IndexSegment getIndexSegment() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, numTotalDocs); @@ -88,13 +85,12 @@ public ExecutionStatistics getExecutionStatistics() { @Override public String toExplainString() { - String[] keys = _distinctAggregationFunction.getColumns(); + List expressions = _queryContext.getSelectExpressions(); + int numExpressions = expressions.size(); StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(keyColumns:"); - if (keys.length > 0) { - stringBuilder.append(keys[0]); - for (int i = 1; i < keys.length; i++) { - stringBuilder.append(", ").append(keys[i]); - } + stringBuilder.append(expressions.get(0).toString()); + for (int i = 1; i < numExpressions; i++) { + stringBuilder.append(", ").append(expressions.get(i).toString()); } return stringBuilder.append(')').toString(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java index f2bb79b384c..fa11d3dc39c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java @@ -22,12 +22,11 @@ import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.spi.IndexSegment; @@ -37,25 +36,25 @@ *

    NOTE: this operator short circuit underlying operators and directly returns the data schema without any rows. */ public class EmptySelectionOperator extends BaseOperator { - private static final String EXPLAIN_NAME = "SELECT_EMPTY"; + private final BaseProjectOperator _projectOperator; private final DataSchema _dataSchema; private final ExecutionStatistics _executionStatistics; - private final TransformOperator _transformOperator; public EmptySelectionOperator(IndexSegment indexSegment, List expressions, - TransformOperator transformOperator) { + BaseProjectOperator projectOperator) { + _projectOperator = projectOperator; + int numExpressions = expressions.size(); String[] columnNames = new String[numExpressions]; - _transformOperator = transformOperator; DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numExpressions]; for (int i = 0; i < numExpressions; i++) { ExpressionContext expression = expressions.get(i); - TransformResultMetadata expressionMetadata = _transformOperator.getResultMetadata(expression); columnNames[i] = expression.toString(); + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + DataSchema.ColumnDataType.fromDataType(columnContext.getDataType(), columnContext.isSingleValue()); } _dataSchema = new DataSchema(columnNames, columnDataTypes); @@ -73,8 +72,8 @@ public String toExplainString() { } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java index f478f36d921..1237013fb15 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java @@ -25,10 +25,10 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.AggregationExecutor; import org.apache.pinot.core.query.aggregation.DefaultAggregationExecutor; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; @@ -45,19 +45,17 @@ public class FilteredAggregationOperator extends BaseOperator> _aggFunctionsWithTransformOperator; + private final List>> _projectOperators; private final long _numTotalDocs; private long _numDocsScanned; private long _numEntriesScannedInFilter; private long _numEntriesScannedPostFilter; - // We can potentially do away with aggregationFunctions parameter, but its cleaner to pass it in than to construct - // it from aggFunctionsWithTransformOperator public FilteredAggregationOperator(AggregationFunction[] aggregationFunctions, - List> aggFunctionsWithTransformOperator, long numTotalDocs) { + List>> projectOperators, long numTotalDocs) { _aggregationFunctions = aggregationFunctions; - _aggFunctionsWithTransformOperator = aggFunctionsWithTransformOperator; + _projectOperators = projectOperators; _numTotalDocs = numTotalDocs; } @@ -70,15 +68,15 @@ protected AggregationResultsBlock getNextBlock() { resultIndexMap.put(_aggregationFunctions[i], i); } - for (Pair filteredAggregation : _aggFunctionsWithTransformOperator) { - AggregationFunction[] aggregationFunctions = filteredAggregation.getLeft(); + for (Pair> pair : _projectOperators) { + AggregationFunction[] aggregationFunctions = pair.getLeft(); AggregationExecutor aggregationExecutor = new DefaultAggregationExecutor(aggregationFunctions); - TransformOperator transformOperator = filteredAggregation.getRight(); - TransformBlock transformBlock; + BaseProjectOperator projectOperator = pair.getRight(); + ValueBlock valueBlock; int numDocsScanned = 0; - while ((transformBlock = transformOperator.nextBlock()) != null) { - aggregationExecutor.aggregate(transformBlock); - numDocsScanned += transformBlock.getNumDocs(); + while ((valueBlock = projectOperator.nextBlock()) != null) { + aggregationExecutor.aggregate(valueBlock); + numDocsScanned += valueBlock.getNumDocs(); } List filteredResult = aggregationExecutor.getResult(); @@ -86,15 +84,15 @@ protected AggregationResultsBlock getNextBlock() { result[resultIndexMap.get(aggregationFunctions[i])] = filteredResult.get(i); } _numDocsScanned += numDocsScanned; - _numEntriesScannedInFilter += transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - _numEntriesScannedPostFilter += (long) numDocsScanned * transformOperator.getNumColumnsProjected(); + _numEntriesScannedInFilter += projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + _numEntriesScannedPostFilter += (long) numDocsScanned * projectOperator.getNumColumnsProjected(); } return new AggregationResultsBlock(_aggregationFunctions, Arrays.asList(result)); } @Override public List getChildOperators() { - return _aggFunctionsWithTransformOperator.stream().map(Pair::getRight).collect(Collectors.toList()); + return _projectOperators.stream().map(Pair::getRight).collect(Collectors.toList()); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java index 872a999f546..9be251ae23e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java @@ -30,10 +30,10 @@ import org.apache.pinot.core.data.table.IntermediateRecord; import org.apache.pinot.core.data.table.TableResizer; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; @@ -53,48 +53,48 @@ public class FilteredGroupByOperator extends BaseOperator { private static final String EXPLAIN_NAME = "GROUP_BY_FILTERED"; + private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; - private final List> _aggFunctionsWithTransformOperator; private final ExpressionContext[] _groupByExpressions; + private final List>> _projectOperators; private final long _numTotalDocs; + private final DataSchema _dataSchema; + private long _numDocsScanned; private long _numEntriesScannedInFilter; private long _numEntriesScannedPostFilter; - private final DataSchema _dataSchema; - private final QueryContext _queryContext; - public FilteredGroupByOperator(AggregationFunction[] aggregationFunctions, - List> filteredAggregationFunctions, - List> aggFunctionsWithTransformOperator, - ExpressionContext[] groupByExpressions, long numTotalDocs, QueryContext queryContext) { - _aggregationFunctions = aggregationFunctions; - _aggFunctionsWithTransformOperator = aggFunctionsWithTransformOperator; - _groupByExpressions = groupByExpressions; - _numTotalDocs = numTotalDocs; + public FilteredGroupByOperator(QueryContext queryContext, + List>> projectOperators, long numTotalDocs) { + assert queryContext.getAggregationFunctions() != null && queryContext.getFilteredAggregationFunctions() != null + && queryContext.getGroupByExpressions() != null; _queryContext = queryContext; + _aggregationFunctions = queryContext.getAggregationFunctions(); + _groupByExpressions = queryContext.getGroupByExpressions().toArray(new ExpressionContext[0]); + _projectOperators = projectOperators; + _numTotalDocs = numTotalDocs; // NOTE: The indexedTable expects that the data schema will have group by columns before aggregation columns - int numGroupByExpressions = groupByExpressions.length; - int numAggregationFunctions = aggregationFunctions.length; + int numGroupByExpressions = _groupByExpressions.length; + int numAggregationFunctions = _aggregationFunctions.length; int numColumns = numGroupByExpressions + numAggregationFunctions; String[] columnNames = new String[numColumns]; DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numColumns]; // Extract column names and data types for group-by columns for (int i = 0; i < numGroupByExpressions; i++) { - ExpressionContext groupByExpression = groupByExpressions[i]; + ExpressionContext groupByExpression = _groupByExpressions[i]; columnNames[i] = groupByExpression.toString(); columnDataTypes[i] = DataSchema.ColumnDataType.fromDataTypeSV( - aggFunctionsWithTransformOperator.get(i).getRight().getResultMetadata(groupByExpression).getDataType()); + projectOperators.get(i).getRight().getResultColumnContext(groupByExpression).getDataType()); } // Extract column names and data types for aggregation functions for (int i = 0; i < numAggregationFunctions; i++) { int index = numGroupByExpressions + i; - Pair filteredAggPair = filteredAggregationFunctions.get(i); - AggregationFunction aggregationFunction = filteredAggPair.getLeft(); - String columnName = - AggregationFunctionUtils.getResultColumnName(aggregationFunction, filteredAggPair.getRight()); + Pair pair = queryContext.getFilteredAggregationFunctions().get(i); + AggregationFunction aggregationFunction = pair.getLeft(); + String columnName = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); columnNames[index] = columnName; columnDataTypes[index] = aggregationFunction.getIntermediateResultColumnType(); } @@ -115,9 +115,9 @@ protected GroupByResultsBlock getNextBlock() { } GroupKeyGenerator groupKeyGenerator = null; - for (Pair filteredAggregation : _aggFunctionsWithTransformOperator) { - TransformOperator transformOperator = filteredAggregation.getRight(); - AggregationFunction[] filteredAggFunctions = filteredAggregation.getLeft(); + for (Pair> pair : _projectOperators) { + AggregationFunction[] aggregationFunctions = pair.getLeft(); + BaseProjectOperator projectOperator = pair.getRight(); // Perform aggregation group-by on all the blocks DefaultGroupByExecutor groupByExecutor; @@ -130,27 +130,27 @@ protected GroupByResultsBlock getNextBlock() { // GroupByExecutor with a pre-existing GroupKeyGenerator so that the GroupKeyGenerator can be shared across // loop iterations i.e. across all aggs. groupByExecutor = - new DefaultGroupByExecutor(_queryContext, filteredAggFunctions, _groupByExpressions, transformOperator); + new DefaultGroupByExecutor(_queryContext, aggregationFunctions, _groupByExpressions, projectOperator); groupKeyGenerator = groupByExecutor.getGroupKeyGenerator(); } else { groupByExecutor = - new DefaultGroupByExecutor(_queryContext, filteredAggFunctions, _groupByExpressions, transformOperator, + new DefaultGroupByExecutor(_queryContext, aggregationFunctions, _groupByExpressions, projectOperator, groupKeyGenerator); } int numDocsScanned = 0; - TransformBlock transformBlock; - while ((transformBlock = transformOperator.nextBlock()) != null) { - numDocsScanned += transformBlock.getNumDocs(); - groupByExecutor.process(transformBlock); + ValueBlock valueBlock; + while ((valueBlock = projectOperator.nextBlock()) != null) { + numDocsScanned += valueBlock.getNumDocs(); + groupByExecutor.process(valueBlock); } _numDocsScanned += numDocsScanned; - _numEntriesScannedInFilter += transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - _numEntriesScannedPostFilter += (long) numDocsScanned * transformOperator.getNumColumnsProjected(); + _numEntriesScannedInFilter += projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + _numEntriesScannedPostFilter += (long) numDocsScanned * projectOperator.getNumColumnsProjected(); GroupByResultHolder[] filterGroupByResults = groupByExecutor.getGroupByResultHolders(); - for (int i = 0; i < filteredAggFunctions.length; i++) { - groupByResultHolders[resultHolderIndexMap.get(filteredAggFunctions[i])] = filterGroupByResults[i]; + for (int i = 0; i < aggregationFunctions.length; i++) { + groupByResultHolders[resultHolderIndexMap.get(aggregationFunctions[i])] = filterGroupByResults[i]; } } assert groupKeyGenerator != null; @@ -190,7 +190,7 @@ protected GroupByResultsBlock getNextBlock() { @Override public List getChildOperators() { - return _aggFunctionsWithTransformOperator.stream().map(Pair::getRight).collect(Collectors.toList()); + return _projectOperators.stream().map(Pair::getRight).collect(Collectors.toList()); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java index 463cc60d441..3465f653e9f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java @@ -27,10 +27,10 @@ import org.apache.pinot.core.data.table.IntermediateRecord; import org.apache.pinot.core.data.table.TableResizer; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.groupby.DefaultGroupByExecutor; import org.apache.pinot.core.query.aggregation.groupby.GroupByExecutor; @@ -49,7 +49,7 @@ public class GroupByOperator extends BaseOperator { private final AggregationFunction[] _aggregationFunctions; private final ExpressionContext[] _groupByExpressions; - private final TransformOperator _transformOperator; + private final BaseProjectOperator _projectOperator; private final long _numTotalDocs; private final boolean _useStarTree; private final DataSchema _dataSchema; @@ -58,10 +58,10 @@ public class GroupByOperator extends BaseOperator { private int _numDocsScanned = 0; public GroupByOperator(AggregationFunction[] aggregationFunctions, ExpressionContext[] groupByExpressions, - TransformOperator transformOperator, long numTotalDocs, QueryContext queryContext, boolean useStarTree) { + BaseProjectOperator projectOperator, long numTotalDocs, QueryContext queryContext, boolean useStarTree) { _aggregationFunctions = aggregationFunctions; _groupByExpressions = groupByExpressions; - _transformOperator = transformOperator; + _projectOperator = projectOperator; _numTotalDocs = numTotalDocs; _useStarTree = useStarTree; _queryContext = queryContext; @@ -78,7 +78,7 @@ public GroupByOperator(AggregationFunction[] aggregationFunctions, ExpressionCon ExpressionContext groupByExpression = groupByExpressions[i]; columnNames[i] = groupByExpression.toString(); columnDataTypes[i] = DataSchema.ColumnDataType.fromDataTypeSV( - _transformOperator.getResultMetadata(groupByExpression).getDataType()); + _projectOperator.getResultColumnContext(groupByExpression).getDataType()); } // Extract column names and data types for aggregation functions @@ -97,14 +97,14 @@ protected GroupByResultsBlock getNextBlock() { // Perform aggregation group-by on all the blocks GroupByExecutor groupByExecutor; if (_useStarTree) { - groupByExecutor = new StarTreeGroupByExecutor(_queryContext, _groupByExpressions, _transformOperator); + groupByExecutor = new StarTreeGroupByExecutor(_queryContext, _groupByExpressions, _projectOperator); } else { - groupByExecutor = new DefaultGroupByExecutor(_queryContext, _groupByExpressions, _transformOperator); + groupByExecutor = new DefaultGroupByExecutor(_queryContext, _groupByExpressions, _projectOperator); } - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - _numDocsScanned += transformBlock.getNumDocs(); - groupByExecutor.process(transformBlock); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + _numDocsScanned += valueBlock.getNumDocs(); + groupByExecutor.process(valueBlock); } // Check if the groups limit is reached @@ -136,13 +136,13 @@ protected GroupByResultsBlock getNextBlock() { @Override public List getChildOperators() { - return Collections.singletonList(_transformOperator); + return Collections.singletonList(_projectOperator); } @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, _numTotalDocs); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java index e9fe801262b..b0904508a10 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java @@ -32,14 +32,13 @@ import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.common.RowBasedBlockValueFetcher; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.core.query.utils.OrderByComparatorFactory; @@ -70,28 +69,23 @@ public abstract class LinearSelectionOrderByOperator extends BaseOperator

    \n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    4a8ab4f149fix(bazel): enable dts bundling for Ivy packages (#42728)
    \n

    common

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    d654c7933afix(common): re-sort output of KeyValuePipe when compareFn changes (#42821)
    \n

    compiler

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    2566cbb48cfix(compiler): add mappings for all HTML entities (#42818)
    65330f03a9fix(compiler): incorrect context object being referenced from listener instructions inside embedded views (#42755)
    \n

    compiler-cli

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    17d3de25dafix(compiler-cli): properly emit literal types when recreating type parameters in a different file (#42761)
    0a17e98ae2fix(compiler-cli): inline type checking instructions no longer prevent incremental reuse (#42759)
    45116097c1fix(compiler-cli): support reflecting namespace declarations (#42728)
    df5cc1fbbffix(compiler-cli): return directives for an element on a microsyntax template (#42640)
    \n

    core

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    63013546e1fix(core): associate the NgModule scope for an overridden component (#42817)
    9ebd41e39cfix(core): allow proper type inference when ngFor is used with a trackBy function (#42692)
    41c6877c01fix(core): error in TestBed if module is reset mid-compilation in ViewEngine (#42669)
    \n

    language-service

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    97c18f4527fix(language-service): Do not override TS LS methods not supported by VE NgLS (#42727)
    \n

    service-worker

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    d87917542afix(service-worker): correctly handle unrecoverable state when a client no longer exists (#42736)
    f2523a8feffix(service-worker): avoid storing redundant metadata for hashed assets (#42606)
    \n

    Special Thanks:

    \n

    Alan Agius, Andrew Kushnir, Andrew Scott, Arthur Ming, Bastian, Borislav Ivanov, David Gilson, David Shevitz, Gabriele Franchitto, George Kalpakas, Joey Perrott, JoostK, Kristiyan Kostadinov, Mark Goho, Meir Blumenfeld, Paul Gschwendtner, Pete Bacon Darwin, Renovate Bot, Ryan Andersen, Theoklitos Bampouris, behrooz bozorg chami, dario-piotrowicz, ivanwonder and mgechev

    \n\n\n
    \nChangelog\n

    Sourced from @​angular/common's changelog.

    \n
    \n

    12.1.2 (2021-07-14)

    \n

    bazel

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    4a8ab4f149fix(bazel): enable dts bundling for Ivy packages (#42728)
    \n

    common

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    d654c7933afix(common): re-sort output of KeyValuePipe when compareFn changes (#42821)
    \n

    compiler

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    2566cbb48cfix(compiler): add mappings for all HTML entities (#42818)
    65330f03a9fix(compiler): incorrect context object being referenced from listener instructions inside embedded views (#42755)
    \n

    compiler-cli

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    17d3de25dafix(compiler-cli): properly emit literal types when recreating type parameters in a different file (#42761)
    0a17e98ae2fix(compiler-cli): inline type checking instructions no longer prevent incremental reuse (#42759)
    45116097c1fix(compiler-cli): support reflecting namespace declarations (#42728)
    df5cc1fbbffix(compiler-cli): return directives for an element on a microsyntax template (#42640)
    \n

    core

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    63013546e1fix(core): associate the NgModule scope for an overridden component (#42817)
    9ebd41e39cfix(core): allow proper type inference when ngFor is used with a trackBy function (#42692)
    41c6877c01fix(core): error in TestBed if module is reset mid-compilation in ViewEngine (#42669)
    \n

    language-service

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    97c18f4527fix(language-service): Do not override TS LS methods not supported by VE NgLS (#42727)
    \n

    service-worker

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    d87917542afix(service-worker): correctly handle unrecoverable state when a client no longer exists (#42736)
    f2523a8feffix(service-worker): avoid storing redundant metadata for hashed assets (#42606)
    \n

    Special Thanks:

    \n

    Alan Agius, Andrew Kushnir, Andrew Scott, Arthur Ming, Bastian, Borislav Ivanov, David Gilson, David Shevitz, Gabriele Franchitto, George Kalpakas, Joey Perrott, JoostK, Kristiyan Kostadinov, Mark Goho, Meir Blumenfeld, Paul Gschwendtner, Pete Bacon Darwin, Ryan Andersen, Theoklitos Bampouris, behrooz bozorg chami, dario-piotrowicz, ivanwonder and mgechev

    \n

    \n

    12.2.0-next.1 (2021-06-30)

    \n

    compiler

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    9f5cc7c808feat(compiler): support number separators in templates (#42672)
    \n

    compiler-cli

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    CommitDescription
    37a740c659fix(compiler-cli): add support for partially evaluating types (#41661)
    \n\n
    \n

    ... (truncated)

    \n
    \n
    \nCommits\n
      \n
    • d654c79 fix(common): re-sort output of KeyValuePipe when compareFn changes (#42821)
    • \n
    • 04472d2 refactor: add override keyword to members implementing abstract declarations ...
    • \n
    • 1f84f01 refactor(common): ensure compatibility with noImplicitOverride (#42512)
    • \n
    • See full diff in compare view
    • \n
    \n
    \n
    \n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@angular/common&package-manager=npm_and_yarn&previous-version=12.1.1&new-version=12.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
    ","created_at":"2021-07-19T20:06:46Z","updated_at":"2021-07-21T06:00:04Z","closed_at":"2021-07-21T06:00:04Z","merged_at":"2021-07-21T06:00:04Z","merge_commit_sha":"02a705eebb49f91fab457ba053d73c9362739a16","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":2961171209,"node_id":"MDU6TGFiZWwyOTYxMTcxMjA5","url":"https://api.github.com/repos/f-club/my-angular-app/labels/dependencies","name":"dependencies","color":"0366d6","default":false,"description":"Pull requests that update a dependency file"}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/f-club/my-angular-app/pulls/189/commits","review_comments_url":"https://api.github.com/repos/f-club/my-angular-app/pulls/189/comments","review_comment_url":"https://api.github.com/repos/f-club/my-angular-app/pulls/comments{/number}","comments_url":"https://api.github.com/repos/f-club/my-angular-app/issues/189/comments","statuses_url":"https://api.github.com/repos/f-club/my-angular-app/statuses/1b6c31899f2c70ab4e1fca761d93b9cb034cbaf2","head":{"label":"f-club:dependabot/npm_and_yarn/angular/common-12.1.2","ref":"dependabot/npm_and_yarn/angular/common-12.1.2","sha":"1b6c31899f2c70ab4e1fca761d93b9cb034cbaf2","user":{"login":"f-club","id":83454129,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0MTI5","avatar_url":"https://avatars.githubusercontent.com/u/83454129?v=4","gravatar_id":"","url":"https://api.github.com/users/f-club","html_url":"https://github.com/f-club","followers_url":"https://api.github.com/users/f-club/followers","following_url":"https://api.github.com/users/f-club/following{/other_user}","gists_url":"https://api.github.com/users/f-club/gists{/gist_id}","starred_url":"https://api.github.com/users/f-club/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/f-club/subscriptions","organizations_url":"https://api.github.com/users/f-club/orgs","repos_url":"https://api.github.com/users/f-club/repos","events_url":"https://api.github.com/users/f-club/events{/privacy}","received_events_url":"https://api.github.com/users/f-club/received_events","type":"Organization","site_admin":false},"repo":{"id":157529668,"node_id":"MDEwOlJlcG9zaXRvcnkxNTc1Mjk2Njg=","name":"my-angular-app","full_name":"f-club/my-angular-app","private":false,"owner":{"login":"f-club","id":83454129,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0MTI5","avatar_url":"https://avatars.githubusercontent.com/u/83454129?v=4","gravatar_id":"","url":"https://api.github.com/users/f-club","html_url":"https://github.com/f-club","followers_url":"https://api.github.com/users/f-club/followers","following_url":"https://api.github.com/users/f-club/following{/other_user}","gists_url":"https://api.github.com/users/f-club/gists{/gist_id}","starred_url":"https://api.github.com/users/f-club/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/f-club/subscriptions","organizations_url":"https://api.github.com/users/f-club/orgs","repos_url":"https://api.github.com/users/f-club/repos","events_url":"https://api.github.com/users/f-club/events{/privacy}","received_events_url":"https://api.github.com/users/f-club/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/f-club/my-angular-app","description":null,"fork":false,"url":"https://api.github.com/repos/f-club/my-angular-app","forks_url":"https://api.github.com/repos/f-club/my-angular-app/forks","keys_url":"https://api.github.com/repos/f-club/my-angular-app/keys{/key_id}","collaborators_url":"https://api.github.com/repos/f-club/my-angular-app/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/f-club/my-angular-app/teams","hooks_url":"https://api.github.com/repos/f-club/my-angular-app/hooks","issue_events_url":"https://api.github.com/repos/f-club/my-angular-app/issues/events{/number}","events_url":"https://api.github.com/repos/f-club/my-angular-app/events","assignees_url":"https://api.github.com/repos/f-club/my-angular-app/assignees{/user}","branches_url":"https://api.github.com/repos/f-club/my-angular-app/branches{/branch}","tags_url":"https://api.github.com/repos/f-club/my-angular-app/tags","blobs_url":"https://api.github.com/repos/f-club/my-angular-app/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/f-club/my-angular-app/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/f-club/my-angular-app/git/refs{/sha}","trees_url":"https://api.github.com/repos/f-club/my-angular-app/git/trees{/sha}","statuses_url":"https://api.github.com/repos/f-club/my-angular-app/statuses/{sha}","languages_url":"https://api.github.com/repos/f-club/my-angular-app/languages","stargazers_url":"https://api.github.com/repos/f-club/my-angular-app/stargazers","contributors_url":"https://api.github.com/repos/f-club/my-angular-app/contributors","subscribers_url":"https://api.github.com/repos/f-club/my-angular-app/subscribers","subscription_url":"https://api.github.com/repos/f-club/my-angular-app/subscription","commits_url":"https://api.github.com/repos/f-club/my-angular-app/commits{/sha}","git_commits_url":"https://api.github.com/repos/f-club/my-angular-app/git/commits{/sha}","comments_url":"https://api.github.com/repos/f-club/my-angular-app/comments{/number}","issue_comment_url":"https://api.github.com/repos/f-club/my-angular-app/issues/comments{/number}","contents_url":"https://api.github.com/repos/f-club/my-angular-app/contents/{+path}","compare_url":"https://api.github.com/repos/f-club/my-angular-app/compare/{base}...{head}","merges_url":"https://api.github.com/repos/f-club/my-angular-app/merges","archive_url":"https://api.github.com/repos/f-club/my-angular-app/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/f-club/my-angular-app/downloads","issues_url":"https://api.github.com/repos/f-club/my-angular-app/issues{/number}","pulls_url":"https://api.github.com/repos/f-club/my-angular-app/pulls{/number}","milestones_url":"https://api.github.com/repos/f-club/my-angular-app/milestones{/number}","notifications_url":"https://api.github.com/repos/f-club/my-angular-app/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/f-club/my-angular-app/labels{/name}","releases_url":"https://api.github.com/repos/f-club/my-angular-app/releases{/id}","deployments_url":"https://api.github.com/repos/f-club/my-angular-app/deployments","created_at":"2018-11-14T10:11:29Z","updated_at":"2021-07-21T05:59:59Z","pushed_at":"2021-07-21T06:00:04Z","git_url":"git://github.com/f-club/my-angular-app.git","ssh_url":"git@github.com:f-club/my-angular-app.git","clone_url":"https://github.com/f-club/my-angular-app.git","svn_url":"https://github.com/f-club/my-angular-app","homepage":null,"size":18107,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":null,"forks":0,"open_issues":5,"watchers":0,"default_branch":"master"}},"base":{"label":"f-club:master","ref":"master","sha":"ee3ab69f81ea6737ed91c3b2ab2834798e52000f","user":{"login":"f-club","id":83454129,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0MTI5","avatar_url":"https://avatars.githubusercontent.com/u/83454129?v=4","gravatar_id":"","url":"https://api.github.com/users/f-club","html_url":"https://github.com/f-club","followers_url":"https://api.github.com/users/f-club/followers","following_url":"https://api.github.com/users/f-club/following{/other_user}","gists_url":"https://api.github.com/users/f-club/gists{/gist_id}","starred_url":"https://api.github.com/users/f-club/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/f-club/subscriptions","organizations_url":"https://api.github.com/users/f-club/orgs","repos_url":"https://api.github.com/users/f-club/repos","events_url":"https://api.github.com/users/f-club/events{/privacy}","received_events_url":"https://api.github.com/users/f-club/received_events","type":"Organization","site_admin":false},"repo":{"id":157529668,"node_id":"MDEwOlJlcG9zaXRvcnkxNTc1Mjk2Njg=","name":"my-angular-app","full_name":"f-club/my-angular-app","private":false,"owner":{"login":"f-club","id":83454129,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0MTI5","avatar_url":"https://avatars.githubusercontent.com/u/83454129?v=4","gravatar_id":"","url":"https://api.github.com/users/f-club","html_url":"https://github.com/f-club","followers_url":"https://api.github.com/users/f-club/followers","following_url":"https://api.github.com/users/f-club/following{/other_user}","gists_url":"https://api.github.com/users/f-club/gists{/gist_id}","starred_url":"https://api.github.com/users/f-club/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/f-club/subscriptions","organizations_url":"https://api.github.com/users/f-club/orgs","repos_url":"https://api.github.com/users/f-club/repos","events_url":"https://api.github.com/users/f-club/events{/privacy}","received_events_url":"https://api.github.com/users/f-club/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/f-club/my-angular-app","description":null,"fork":false,"url":"https://api.github.com/repos/f-club/my-angular-app","forks_url":"https://api.github.com/repos/f-club/my-angular-app/forks","keys_url":"https://api.github.com/repos/f-club/my-angular-app/keys{/key_id}","collaborators_url":"https://api.github.com/repos/f-club/my-angular-app/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/f-club/my-angular-app/teams","hooks_url":"https://api.github.com/repos/f-club/my-angular-app/hooks","issue_events_url":"https://api.github.com/repos/f-club/my-angular-app/issues/events{/number}","events_url":"https://api.github.com/repos/f-club/my-angular-app/events","assignees_url":"https://api.github.com/repos/f-club/my-angular-app/assignees{/user}","branches_url":"https://api.github.com/repos/f-club/my-angular-app/branches{/branch}","tags_url":"https://api.github.com/repos/f-club/my-angular-app/tags","blobs_url":"https://api.github.com/repos/f-club/my-angular-app/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/f-club/my-angular-app/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/f-club/my-angular-app/git/refs{/sha}","trees_url":"https://api.github.com/repos/f-club/my-angular-app/git/trees{/sha}","statuses_url":"https://api.github.com/repos/f-club/my-angular-app/statuses/{sha}","languages_url":"https://api.github.com/repos/f-club/my-angular-app/languages","stargazers_url":"https://api.github.com/repos/f-club/my-angular-app/stargazers","contributors_url":"https://api.github.com/repos/f-club/my-angular-app/contributors","subscribers_url":"https://api.github.com/repos/f-club/my-angular-app/subscribers","subscription_url":"https://api.github.com/repos/f-club/my-angular-app/subscription","commits_url":"https://api.github.com/repos/f-club/my-angular-app/commits{/sha}","git_commits_url":"https://api.github.com/repos/f-club/my-angular-app/git/commits{/sha}","comments_url":"https://api.github.com/repos/f-club/my-angular-app/comments{/number}","issue_comment_url":"https://api.github.com/repos/f-club/my-angular-app/issues/comments{/number}","contents_url":"https://api.github.com/repos/f-club/my-angular-app/contents/{+path}","compare_url":"https://api.github.com/repos/f-club/my-angular-app/compare/{base}...{head}","merges_url":"https://api.github.com/repos/f-club/my-angular-app/merges","archive_url":"https://api.github.com/repos/f-club/my-angular-app/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/f-club/my-angular-app/downloads","issues_url":"https://api.github.com/repos/f-club/my-angular-app/issues{/number}","pulls_url":"https://api.github.com/repos/f-club/my-angular-app/pulls{/number}","milestones_url":"https://api.github.com/repos/f-club/my-angular-app/milestones{/number}","notifications_url":"https://api.github.com/repos/f-club/my-angular-app/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/f-club/my-angular-app/labels{/name}","releases_url":"https://api.github.com/repos/f-club/my-angular-app/releases{/id}","deployments_url":"https://api.github.com/repos/f-club/my-angular-app/deployments","created_at":"2018-11-14T10:11:29Z","updated_at":"2021-07-21T05:59:59Z","pushed_at":"2021-07-21T06:00:04Z","git_url":"git://github.com/f-club/my-angular-app.git","ssh_url":"git@github.com:f-club/my-angular-app.git","clone_url":"https://github.com/f-club/my-angular-app.git","svn_url":"https://github.com/f-club/my-angular-app","homepage":null,"size":18107,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":null,"forks":0,"open_issues":5,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/f-club/my-angular-app/pulls/189"},"html":{"href":"https://github.com/f-club/my-angular-app/pull/189"},"issue":{"href":"https://api.github.com/repos/f-club/my-angular-app/issues/189"},"comments":{"href":"https://api.github.com/repos/f-club/my-angular-app/issues/189/comments"},"review_comments":{"href":"https://api.github.com/repos/f-club/my-angular-app/pulls/189/comments"},"review_comment":{"href":"https://api.github.com/repos/f-club/my-angular-app/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/f-club/my-angular-app/pulls/189/commits"},"statuses":{"href":"https://api.github.com/repos/f-club/my-angular-app/statuses/1b6c31899f2c70ab4e1fca761d93b9cb034cbaf2"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"cg1101","id":6924409,"node_id":"MDQ6VXNlcjY5MjQ0MDk=","avatar_url":"https://avatars.githubusercontent.com/u/6924409?v=4","gravatar_id":"","url":"https://api.github.com/users/cg1101","html_url":"https://github.com/cg1101","followers_url":"https://api.github.com/users/cg1101/followers","following_url":"https://api.github.com/users/cg1101/following{/other_user}","gists_url":"https://api.github.com/users/cg1101/gists{/gist_id}","starred_url":"https://api.github.com/users/cg1101/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cg1101/subscriptions","organizations_url":"https://api.github.com/users/cg1101/orgs","repos_url":"https://api.github.com/users/cg1101/repos","events_url":"https://api.github.com/users/cg1101/events{/privacy}","received_events_url":"https://api.github.com/users/cg1101/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":9,"deletions":9,"changed_files":2}},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":83454129,"login":"f-club","gravatar_id":"","url":"https://api.github.com/orgs/f-club","avatar_url":"https://avatars.githubusercontent.com/u/83454129?"}} +{"id":"17244208050","type":"PullRequestEvent","actor":{"id":80881229,"login":"ItsMeVishal007","display_login":"ItsMeVishal007","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","avatar_url":"https://avatars.githubusercontent.com/u/80881229?"},"repo":{"id":373055818,"name":"ItsMeVishal007/Share-Site","url":"https://api.github.com/repos/ItsMeVishal007/Share-Site"},"payload":{"action":"closed","number":29,"pull_request":{"url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/29","id":694073936,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDczOTM2","html_url":"https://github.com/ItsMeVishal007/Share-Site/pull/29","diff_url":"https://github.com/ItsMeVishal007/Share-Site/pull/29.diff","patch_url":"https://github.com/ItsMeVishal007/Share-Site/pull/29.patch","issue_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/29","number":29,"state":"closed","locked":false,"title":"established relationship between user and place","user":{"login":"ItsMeVishal007","id":80881229,"node_id":"MDQ6VXNlcjgwODgxMjI5","avatar_url":"https://avatars.githubusercontent.com/u/80881229?v=4","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","html_url":"https://github.com/ItsMeVishal007","followers_url":"https://api.github.com/users/ItsMeVishal007/followers","following_url":"https://api.github.com/users/ItsMeVishal007/following{/other_user}","gists_url":"https://api.github.com/users/ItsMeVishal007/gists{/gist_id}","starred_url":"https://api.github.com/users/ItsMeVishal007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ItsMeVishal007/subscriptions","organizations_url":"https://api.github.com/users/ItsMeVishal007/orgs","repos_url":"https://api.github.com/users/ItsMeVishal007/repos","events_url":"https://api.github.com/users/ItsMeVishal007/events{/privacy}","received_events_url":"https://api.github.com/users/ItsMeVishal007/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T05:59:56Z","updated_at":"2021-07-21T06:00:04Z","closed_at":"2021-07-21T06:00:04Z","merged_at":"2021-07-21T06:00:04Z","merge_commit_sha":"cd138585cef701abc91e63a31e1462b367f4ac70","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/29/commits","review_comments_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/29/comments","review_comment_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/29/comments","statuses_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/statuses/ea2894db8c72dae243f5e7532b71375ac13ddcbd","head":{"label":"ItsMeVishal007:feat/cyber-dev","ref":"feat/cyber-dev","sha":"ea2894db8c72dae243f5e7532b71375ac13ddcbd","user":{"login":"ItsMeVishal007","id":80881229,"node_id":"MDQ6VXNlcjgwODgxMjI5","avatar_url":"https://avatars.githubusercontent.com/u/80881229?v=4","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","html_url":"https://github.com/ItsMeVishal007","followers_url":"https://api.github.com/users/ItsMeVishal007/followers","following_url":"https://api.github.com/users/ItsMeVishal007/following{/other_user}","gists_url":"https://api.github.com/users/ItsMeVishal007/gists{/gist_id}","starred_url":"https://api.github.com/users/ItsMeVishal007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ItsMeVishal007/subscriptions","organizations_url":"https://api.github.com/users/ItsMeVishal007/orgs","repos_url":"https://api.github.com/users/ItsMeVishal007/repos","events_url":"https://api.github.com/users/ItsMeVishal007/events{/privacy}","received_events_url":"https://api.github.com/users/ItsMeVishal007/received_events","type":"User","site_admin":false},"repo":{"id":373055818,"node_id":"MDEwOlJlcG9zaXRvcnkzNzMwNTU4MTg=","name":"Share-Site","full_name":"ItsMeVishal007/Share-Site","private":false,"owner":{"login":"ItsMeVishal007","id":80881229,"node_id":"MDQ6VXNlcjgwODgxMjI5","avatar_url":"https://avatars.githubusercontent.com/u/80881229?v=4","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","html_url":"https://github.com/ItsMeVishal007","followers_url":"https://api.github.com/users/ItsMeVishal007/followers","following_url":"https://api.github.com/users/ItsMeVishal007/following{/other_user}","gists_url":"https://api.github.com/users/ItsMeVishal007/gists{/gist_id}","starred_url":"https://api.github.com/users/ItsMeVishal007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ItsMeVishal007/subscriptions","organizations_url":"https://api.github.com/users/ItsMeVishal007/orgs","repos_url":"https://api.github.com/users/ItsMeVishal007/repos","events_url":"https://api.github.com/users/ItsMeVishal007/events{/privacy}","received_events_url":"https://api.github.com/users/ItsMeVishal007/received_events","type":"User","site_admin":false},"html_url":"https://github.com/ItsMeVishal007/Share-Site","description":"a MERN app through which you can share locations","fork":false,"url":"https://api.github.com/repos/ItsMeVishal007/Share-Site","forks_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/forks","keys_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/teams","hooks_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/hooks","issue_events_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/events{/number}","events_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/events","assignees_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/assignees{/user}","branches_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/branches{/branch}","tags_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/tags","blobs_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/refs{/sha}","trees_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/statuses/{sha}","languages_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/languages","stargazers_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/stargazers","contributors_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/contributors","subscribers_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/subscribers","subscription_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/subscription","commits_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/commits{/sha}","git_commits_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/commits{/sha}","comments_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/comments{/number}","issue_comment_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/comments{/number}","contents_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/contents/{+path}","compare_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/merges","archive_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/downloads","issues_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues{/number}","pulls_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls{/number}","milestones_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/milestones{/number}","notifications_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/labels{/name}","releases_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/releases{/id}","deployments_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/deployments","created_at":"2021-06-02T05:57:02Z","updated_at":"2021-07-21T05:23:46Z","pushed_at":"2021-07-21T06:00:04Z","git_url":"git://github.com/ItsMeVishal007/Share-Site.git","ssh_url":"git@github.com:ItsMeVishal007/Share-Site.git","clone_url":"https://github.com/ItsMeVishal007/Share-Site.git","svn_url":"https://github.com/ItsMeVishal007/Share-Site","homepage":null,"size":740,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"ItsMeVishal007:master","ref":"master","sha":"4466e60114723817ab2624226d2e99f49c36378b","user":{"login":"ItsMeVishal007","id":80881229,"node_id":"MDQ6VXNlcjgwODgxMjI5","avatar_url":"https://avatars.githubusercontent.com/u/80881229?v=4","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","html_url":"https://github.com/ItsMeVishal007","followers_url":"https://api.github.com/users/ItsMeVishal007/followers","following_url":"https://api.github.com/users/ItsMeVishal007/following{/other_user}","gists_url":"https://api.github.com/users/ItsMeVishal007/gists{/gist_id}","starred_url":"https://api.github.com/users/ItsMeVishal007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ItsMeVishal007/subscriptions","organizations_url":"https://api.github.com/users/ItsMeVishal007/orgs","repos_url":"https://api.github.com/users/ItsMeVishal007/repos","events_url":"https://api.github.com/users/ItsMeVishal007/events{/privacy}","received_events_url":"https://api.github.com/users/ItsMeVishal007/received_events","type":"User","site_admin":false},"repo":{"id":373055818,"node_id":"MDEwOlJlcG9zaXRvcnkzNzMwNTU4MTg=","name":"Share-Site","full_name":"ItsMeVishal007/Share-Site","private":false,"owner":{"login":"ItsMeVishal007","id":80881229,"node_id":"MDQ6VXNlcjgwODgxMjI5","avatar_url":"https://avatars.githubusercontent.com/u/80881229?v=4","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","html_url":"https://github.com/ItsMeVishal007","followers_url":"https://api.github.com/users/ItsMeVishal007/followers","following_url":"https://api.github.com/users/ItsMeVishal007/following{/other_user}","gists_url":"https://api.github.com/users/ItsMeVishal007/gists{/gist_id}","starred_url":"https://api.github.com/users/ItsMeVishal007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ItsMeVishal007/subscriptions","organizations_url":"https://api.github.com/users/ItsMeVishal007/orgs","repos_url":"https://api.github.com/users/ItsMeVishal007/repos","events_url":"https://api.github.com/users/ItsMeVishal007/events{/privacy}","received_events_url":"https://api.github.com/users/ItsMeVishal007/received_events","type":"User","site_admin":false},"html_url":"https://github.com/ItsMeVishal007/Share-Site","description":"a MERN app through which you can share locations","fork":false,"url":"https://api.github.com/repos/ItsMeVishal007/Share-Site","forks_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/forks","keys_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/teams","hooks_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/hooks","issue_events_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/events{/number}","events_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/events","assignees_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/assignees{/user}","branches_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/branches{/branch}","tags_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/tags","blobs_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/refs{/sha}","trees_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/statuses/{sha}","languages_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/languages","stargazers_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/stargazers","contributors_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/contributors","subscribers_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/subscribers","subscription_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/subscription","commits_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/commits{/sha}","git_commits_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/git/commits{/sha}","comments_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/comments{/number}","issue_comment_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/comments{/number}","contents_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/contents/{+path}","compare_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/merges","archive_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/downloads","issues_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues{/number}","pulls_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls{/number}","milestones_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/milestones{/number}","notifications_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/labels{/name}","releases_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/releases{/id}","deployments_url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/deployments","created_at":"2021-06-02T05:57:02Z","updated_at":"2021-07-21T05:23:46Z","pushed_at":"2021-07-21T06:00:04Z","git_url":"git://github.com/ItsMeVishal007/Share-Site.git","ssh_url":"git@github.com:ItsMeVishal007/Share-Site.git","clone_url":"https://github.com/ItsMeVishal007/Share-Site.git","svn_url":"https://github.com/ItsMeVishal007/Share-Site","homepage":null,"size":740,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/29"},"html":{"href":"https://github.com/ItsMeVishal007/Share-Site/pull/29"},"issue":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/29"},"comments":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/issues/29/comments"},"review_comments":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/29/comments"},"review_comment":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/pulls/29/commits"},"statuses":{"href":"https://api.github.com/repos/ItsMeVishal007/Share-Site/statuses/ea2894db8c72dae243f5e7532b71375ac13ddcbd"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"ItsMeVishal007","id":80881229,"node_id":"MDQ6VXNlcjgwODgxMjI5","avatar_url":"https://avatars.githubusercontent.com/u/80881229?v=4","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","html_url":"https://github.com/ItsMeVishal007","followers_url":"https://api.github.com/users/ItsMeVishal007/followers","following_url":"https://api.github.com/users/ItsMeVishal007/following{/other_user}","gists_url":"https://api.github.com/users/ItsMeVishal007/gists{/gist_id}","starred_url":"https://api.github.com/users/ItsMeVishal007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ItsMeVishal007/subscriptions","organizations_url":"https://api.github.com/users/ItsMeVishal007/orgs","repos_url":"https://api.github.com/users/ItsMeVishal007/repos","events_url":"https://api.github.com/users/ItsMeVishal007/events{/privacy}","received_events_url":"https://api.github.com/users/ItsMeVishal007/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":13,"deletions":8,"changed_files":3}},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208052","type":"CreateEvent","actor":{"id":86180918,"login":"sinta222","display_login":"sinta222","gravatar_id":"","url":"https://api.github.com/users/sinta222","avatar_url":"https://avatars.githubusercontent.com/u/86180918?"},"repo":{"id":388010510,"name":"sinta222/quis1_blog","url":"https://api.github.com/repos/sinta222/quis1_blog"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208056","type":"PushEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":227131980,"name":"SDA-SE/sda-dropwizard-commons","url":"https://api.github.com/repos/SDA-SE/sda-dropwizard-commons"},"payload":{"push_id":7561415160,"size":2,"distinct_size":1,"ref":"refs/heads/dependabot/gradle/master/com.github.tomakehurst-wiremock-jre8-2.29.1","head":"2b11b0abe4d0f5c12ca145648e3d57c2c7722fbb","before":"d5c98e30a3e411e9c2616eea697f9ea486d44ab9","commits":[{"sha":"1eb7ae12e7e38630f115b821244168f160d2db24","author":{"name":"dependabot-preview[bot]","email":"6a4c1c4838f800d1998274cd5234e1f65c55e90c@users.noreply.github.com"},"message":"chore(deps): bump com.diffplug.spotless from 5.14.1 to 5.14.2\n\nBumps com.diffplug.spotless from 5.14.1 to 5.14.2.\n\nSigned-off-by: dependabot-preview[bot] ","distinct":false,"url":"https://api.github.com/repos/SDA-SE/sda-dropwizard-commons/commits/1eb7ae12e7e38630f115b821244168f160d2db24"},{"sha":"2b11b0abe4d0f5c12ca145648e3d57c2c7722fbb","author":{"name":"dependabot-preview[bot]","email":"6a4c1c4838f800d1998274cd5234e1f65c55e90c@users.noreply.github.com"},"message":"fix(deps): bump wiremock-jre8 from 2.28.1 to 2.29.1\n\nBumps [wiremock-jre8](https://github.com/tomakehurst/wiremock) from 2.28.1 to 2.29.1.\n- [Release notes](https://github.com/tomakehurst/wiremock/releases)\n- [Commits](https://github.com/tomakehurst/wiremock/compare/2.28.1...2.29.1)\n\nSigned-off-by: dependabot-preview[bot] ","distinct":true,"url":"https://api.github.com/repos/SDA-SE/sda-dropwizard-commons/commits/2b11b0abe4d0f5c12ca145648e3d57c2c7722fbb"}]},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":28891100,"login":"SDA-SE","gravatar_id":"","url":"https://api.github.com/orgs/SDA-SE","avatar_url":"https://avatars.githubusercontent.com/u/28891100?"}} +{"id":"17244208057","type":"IssuesEvent","actor":{"id":5110976,"login":"kebekus","display_login":"kebekus","gravatar_id":"","url":"https://api.github.com/users/kebekus","avatar_url":"https://avatars.githubusercontent.com/u/5110976?"},"repo":{"id":230746946,"name":"Akaflieg-Freiburg/enrouteServer","url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer/issues/22","repository_url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer","labels_url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer/issues/22/labels{/name}","comments_url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer/issues/22/comments","events_url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer/issues/22/events","html_url":"https://github.com/Akaflieg-Freiburg/enrouteServer/issues/22","id":947443223,"node_id":"MDU6SXNzdWU5NDc0NDMyMjM=","number":22,"title":"Macedonia","user":{"login":"Tridson","id":45800925,"node_id":"MDQ6VXNlcjQ1ODAwOTI1","avatar_url":"https://avatars.githubusercontent.com/u/45800925?v=4","gravatar_id":"","url":"https://api.github.com/users/Tridson","html_url":"https://github.com/Tridson","followers_url":"https://api.github.com/users/Tridson/followers","following_url":"https://api.github.com/users/Tridson/following{/other_user}","gists_url":"https://api.github.com/users/Tridson/gists{/gist_id}","starred_url":"https://api.github.com/users/Tridson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tridson/subscriptions","organizations_url":"https://api.github.com/users/Tridson/orgs","repos_url":"https://api.github.com/users/Tridson/repos","events_url":"https://api.github.com/users/Tridson/events{/privacy}","received_events_url":"https://api.github.com/users/Tridson/received_events","type":"User","site_admin":false},"labels":[{"id":1758762617,"node_id":"MDU6TGFiZWwxNzU4NzYyNjE3","url":"https://api.github.com/repos/Akaflieg-Freiburg/enrouteServer/labels/enhancement","name":"enhancement","color":"a2eeef","default":true,"description":"New feature or request"}],"state":"closed","locked":false,"assignee":{"login":"kebekus","id":5110976,"node_id":"MDQ6VXNlcjUxMTA5NzY=","avatar_url":"https://avatars.githubusercontent.com/u/5110976?v=4","gravatar_id":"","url":"https://api.github.com/users/kebekus","html_url":"https://github.com/kebekus","followers_url":"https://api.github.com/users/kebekus/followers","following_url":"https://api.github.com/users/kebekus/following{/other_user}","gists_url":"https://api.github.com/users/kebekus/gists{/gist_id}","starred_url":"https://api.github.com/users/kebekus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kebekus/subscriptions","organizations_url":"https://api.github.com/users/kebekus/orgs","repos_url":"https://api.github.com/users/kebekus/repos","events_url":"https://api.github.com/users/kebekus/events{/privacy}","received_events_url":"https://api.github.com/users/kebekus/received_events","type":"User","site_admin":false},"assignees":[{"login":"kebekus","id":5110976,"node_id":"MDQ6VXNlcjUxMTA5NzY=","avatar_url":"https://avatars.githubusercontent.com/u/5110976?v=4","gravatar_id":"","url":"https://api.github.com/users/kebekus","html_url":"https://github.com/kebekus","followers_url":"https://api.github.com/users/kebekus/followers","following_url":"https://api.github.com/users/kebekus/following{/other_user}","gists_url":"https://api.github.com/users/kebekus/gists{/gist_id}","starred_url":"https://api.github.com/users/kebekus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kebekus/subscriptions","organizations_url":"https://api.github.com/users/kebekus/orgs","repos_url":"https://api.github.com/users/kebekus/repos","events_url":"https://api.github.com/users/kebekus/events{/privacy}","received_events_url":"https://api.github.com/users/kebekus/received_events","type":"User","site_admin":false}],"milestone":null,"comments":3,"created_at":"2021-07-19T09:11:36Z","updated_at":"2021-07-21T06:00:04Z","closed_at":"2021-07-21T06:00:04Z","author_association":"NONE","active_lock_reason":null,"body":"Hi,\r\nI am a pilot from North Macedonia and I think that it would be very useful to have this tool available on the phone/tablet in flight as a back up to the Garmin GPS. I looked up at the openAIP maps and everything seems to match up to the official aviation maps.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":59165675,"login":"Akaflieg-Freiburg","gravatar_id":"","url":"https://api.github.com/orgs/Akaflieg-Freiburg","avatar_url":"https://avatars.githubusercontent.com/u/59165675?"}} +{"id":"17244208058","type":"PushEvent","actor":{"id":156685,"login":"MarkEWaite","display_login":"MarkEWaite","gravatar_id":"","url":"https://api.github.com/users/MarkEWaite","avatar_url":"https://avatars.githubusercontent.com/u/156685?"},"repo":{"id":51146391,"name":"MarkEWaite/jenkins-bugs","url":"https://api.github.com/repos/MarkEWaite/jenkins-bugs"},"payload":{"push_id":7561415157,"size":4,"distinct_size":2,"ref":"refs/heads/JENKINS-33202","head":"a7e50bd5f9e190ef1d81c20b359b47ae8741b913","before":"13975618c6c0795ff363dac396249589aa3a97a1","commits":[{"sha":"4997b5af0510dcbe11904f4d679feb381d0cf8a3","author":{"name":"Jenkins Docker User","email":"9acaf5e46fbf3d05de45a7843ca7090cb5245aa3@mark-pc2.markwaite.net"},"message":"Added date stamp 2021-07-20 23:59:16 on branch-1","distinct":false,"url":"https://api.github.com/repos/MarkEWaite/jenkins-bugs/commits/4997b5af0510dcbe11904f4d679feb381d0cf8a3"},{"sha":"35ebeaac621b727ce9e1ed181ff34fc0f537cbbc","author":{"name":"Jenkins Docker User","email":"9acaf5e46fbf3d05de45a7843ca7090cb5245aa3@mark-pc2.markwaite.net"},"message":"Added date stamp 2021-07-21 05:59:23 on branch-2","distinct":false,"url":"https://api.github.com/repos/MarkEWaite/jenkins-bugs/commits/35ebeaac621b727ce9e1ed181ff34fc0f537cbbc"},{"sha":"73c7a3e7a5d45de7989d60bcbdcb2dea31524b66","author":{"name":"Jenkins Docker User","email":"9acaf5e46fbf3d05de45a7843ca7090cb5245aa3@mark-pc2.markwaite.net"},"message":"jagent on JENKINS-33202, build was 35697","distinct":true,"url":"https://api.github.com/repos/MarkEWaite/jenkins-bugs/commits/73c7a3e7a5d45de7989d60bcbdcb2dea31524b66"},{"sha":"a7e50bd5f9e190ef1d81c20b359b47ae8741b913","author":{"name":"Jenkins Docker User","email":"9acaf5e46fbf3d05de45a7843ca7090cb5245aa3@mark-pc2.markwaite.net"},"message":"Merge remote-tracking branches 'origin/JENKINS-33202-x/branch-1' and 'origin/JENKINS-33202-x/branch-2' into JENKINS-33202","distinct":true,"url":"https://api.github.com/repos/MarkEWaite/jenkins-bugs/commits/a7e50bd5f9e190ef1d81c20b359b47ae8741b913"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208059","type":"PushEvent","actor":{"id":37988559,"login":"Murayu0225","display_login":"Murayu0225","gravatar_id":"","url":"https://api.github.com/users/Murayu0225","avatar_url":"https://avatars.githubusercontent.com/u/37988559?"},"repo":{"id":387998433,"name":"Murayu0225/Kanagawa-covid19-Vaccine","url":"https://api.github.com/repos/Murayu0225/Kanagawa-covid19-Vaccine"},"payload":{"push_id":7561415174,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"b9d60319341459608150ed94a96ff2f0d3360c1c","before":"c5b4532e5d9697dcdc1805d4fd94815a6d1aac03","commits":[{"sha":"b9d60319341459608150ed94a96ff2f0d3360c1c","author":{"name":"Yu Muramatsu","email":"49567a3a2f29ca52b10c138eb040792c3996b310@assistant-app.com"},"message":"Update auto-download.yml","distinct":true,"url":"https://api.github.com/repos/Murayu0225/Kanagawa-covid19-Vaccine/commits/b9d60319341459608150ed94a96ff2f0d3360c1c"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208065","type":"PushEvent","actor":{"id":17319293,"login":"peerstelter","display_login":"peerstelter","gravatar_id":"","url":"https://api.github.com/users/peerstelter","avatar_url":"https://avatars.githubusercontent.com/u/17319293?"},"repo":{"id":388010100,"name":"peerstelter/EosKit","url":"https://api.github.com/repos/peerstelter/EosKit"},"payload":{"push_id":7561415164,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"807d087dafca0d0fefd34cdcebf8468b7f2a9a7d","before":"d5f2730cbc8dbda5e72c9f4ea0749d2c27444cc4","commits":[{"sha":"807d087dafca0d0fefd34cdcebf8468b7f2a9a7d","author":{"name":"peerstelter","email":"d527b65d1787e9085549b7541b12d076c007f4d7@gmx.de"},"message":"Update Package.swift","distinct":true,"url":"https://api.github.com/repos/peerstelter/EosKit/commits/807d087dafca0d0fefd34cdcebf8468b7f2a9a7d"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208074","type":"PushEvent","actor":{"id":38429025,"login":"chuan12","display_login":"chuan12","gravatar_id":"","url":"https://api.github.com/users/chuan12","avatar_url":"https://avatars.githubusercontent.com/u/38429025?"},"repo":{"id":129750934,"name":"chuan12/shenzhouzd","url":"https://api.github.com/repos/chuan12/shenzhouzd"},"payload":{"push_id":7561415175,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"6933e29b4e4717dd6831301db2e23c12b9c23ebd","before":"3d35ff169ac76234790397a7f35cd116f9c3d75c","commits":[{"sha":"6933e29b4e4717dd6831301db2e23c12b9c23ebd","author":{"name":"chuan12","email":"355aa477360314b41707201943c676d1da11ebe9@users.noreply.github.com"},"message":"Update data.json","distinct":true,"url":"https://api.github.com/repos/chuan12/shenzhouzd/commits/6933e29b4e4717dd6831301db2e23c12b9c23ebd"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208078","type":"DeleteEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":252766607,"name":"Georges7Haddad/BubblyEverAfter","url":"https://api.github.com/repos/Georges7Haddad/BubblyEverAfter"},"payload":{"ref":"dependabot/pip/develop/pylint-2.9.3","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208079","type":"PushEvent","actor":{"id":23554983,"login":"Commando18","display_login":"Commando18","gravatar_id":"","url":"https://api.github.com/users/Commando18","avatar_url":"https://avatars.githubusercontent.com/u/23554983?"},"repo":{"id":289866099,"name":"Commando18/liferay-portal","url":"https://api.github.com/repos/Commando18/liferay-portal"},"payload":{"push_id":7561415117,"size":653,"distinct_size":653,"ref":"refs/heads/COMMERCE-6904","head":"e5cc2b55f93e95212718f66e4bc9cdd4f07cde0e","before":"0c984b636ea010da3dfe45e74f29d9e35a729e19","commits":[{"sha":"39083469641949b1b563fd4965c5aec15ae72db6","author":{"name":"Liferay Translation","email":"213530a25197c98d4bca489589ef744fc924a59b@liferay.com"},"message":"LPS-77699 Update Translations","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/39083469641949b1b563fd4965c5aec15ae72db6"},{"sha":"a2b767d8e796785adb24d63b16de5442f66b83e8","author":{"name":"Adolfo Pérez Álvarez","email":"65bd3d53a3df115ce246ecaeb98cf1c15b9cd636@liferay.com"},"message":"LPS-135773 This should check 'visibleNodes'","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/a2b767d8e796785adb24d63b16de5442f66b83e8"},{"sha":"2e00acb6d2deea7faa3b684b157f2f666286f51b","author":{"name":"Adolfo Pérez Álvarez","email":"65bd3d53a3df115ce246ecaeb98cf1c15b9cd636@liferay.com"},"message":"LPS-135773 Extract method","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/2e00acb6d2deea7faa3b684b157f2f666286f51b"},{"sha":"683bffc1c66d41c1f0e532815942f1d3a0d9211b","author":{"name":"Joyce Wang","email":"4c49c297f0472200b7228d625ded3af5abf4beb6@liferay.com"},"message":"LPS-134649 Update case to mouse over the tooltip","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/683bffc1c66d41c1f0e532815942f1d3a0d9211b"},{"sha":"f4de18047aa7cfb96c20eab41c364803725e612d","author":{"name":"Joyce Wang","email":"4c49c297f0472200b7228d625ded3af5abf4beb6@liferay.com"},"message":"LPS-135779 Update macro to login multiple IdPs","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/f4de18047aa7cfb96c20eab41c364803725e612d"},{"sha":"7b8bd422d9c0a61487218ce70595f567b7dba61c","author":{"name":"Jürgen","email":"1f9212a2f0c3daf5dd2ca663957c5636691bf24e@liferay.com"},"message":"LPS-135798 Remove dead code","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/7b8bd422d9c0a61487218ce70595f567b7dba61c"},{"sha":"4a8bb09e9a14af7698415a9ac8553df175bf9526","author":{"name":"Adolfo Pérez Álvarez","email":"65bd3d53a3df115ce246ecaeb98cf1c15b9cd636@liferay.com"},"message":"LPS-135789 Remove Xuggler UI from server admin","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/4a8bb09e9a14af7698415a9ac8553df175bf9526"},{"sha":"299145801b6caa0a450c3395777d9d5a8d355c3c","author":{"name":"Adolfo Pérez Álvarez","email":"65bd3d53a3df115ce246ecaeb98cf1c15b9cd636@liferay.com"},"message":"LPS-135789 Deprecate classes","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/299145801b6caa0a450c3395777d9d5a8d355c3c"},{"sha":"92c013657a70a376bcbb42fdcce9cf949b9b0732","author":{"name":"Adolfo Pérez Álvarez","email":"65bd3d53a3df115ce246ecaeb98cf1c15b9cd636@liferay.com"},"message":"LPS-135789 Remove lang keys","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/92c013657a70a376bcbb42fdcce9cf949b9b0732"},{"sha":"72ba4bf15069654306f6691538dcc461aadd139f","author":{"name":"Adolfo Pérez Álvarez","email":"65bd3d53a3df115ce246ecaeb98cf1c15b9cd636@liferay.com"},"message":"LPS-135789 Build lang","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/72ba4bf15069654306f6691538dcc461aadd139f"},{"sha":"02eb1c39a72f28d179b689b46954068fb9155abd","author":{"name":"Minhchau","email":"3018eee16c93484da21988ccabb0ac4ef483df08@liferay.com"},"message":"LPS-135364 Allow all standard HTML5 attributes and all styles for table elements","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/02eb1c39a72f28d179b689b46954068fb9155abd"},{"sha":"6d5855a180f20b56b5c4c0eac2404b80d6118744","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8125 Modify ASSETS_TABLE path","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/6d5855a180f20b56b5c4c0eac2404b80d6118744"},{"sha":"2afb6544ab68f0063d5635b63f62314e9f1892e3","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8125 Upgrade ACUtils macro","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/2afb6544ab68f0063d5635b63f62314e9f1892e3"},{"sha":"561833a01d8ecef4d07ae9f2bda627bc1e4e8e3c","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8125 Add ViewAllWebContentShownInAssetList testcase","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/561833a01d8ecef4d07ae9f2bda627bc1e4e8e3c"},{"sha":"92d02444a199a838ebaaf85549c5c38e5554bf2f","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8125 SF","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/92d02444a199a838ebaaf85549c5c38e5554bf2f"},{"sha":"470f47b06dbe2602f799879007e6f6dc23c8e8fc","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8130 Add viewCards macro","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/470f47b06dbe2602f799879007e6f6dc23c8e8fc"},{"sha":"b1b1d109f69c5f48c06afcf76b65142d06d1a799","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8130 Add viewActiveSwitchTab macro","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/b1b1d109f69c5f48c06afcf76b65142d06d1a799"},{"sha":"4d50bf0a21decca76ed9712a6f02b014281b8cb8","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8130 Add viewVisitorsBehaviorMetric macro","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/4d50bf0a21decca76ed9712a6f02b014281b8cb8"},{"sha":"e95ea2c821c2dfd441f97a7eca6527c72fb532b8","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8130 Update some testcases with the new macro","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/e95ea2c821c2dfd441f97a7eca6527c72fb532b8"},{"sha":"8bdd8922e06a6ae562dc2afd13e58e44ea35c75d","author":{"name":"Vinicius Lopes","email":"9f13398adbae4e290c833a82fae82279780de78c@liferay.com"},"message":"LRAC-8130 Add CanNavigateToWebContentOverviewPage testcase","distinct":true,"url":"https://api.github.com/repos/Commando18/liferay-portal/commits/8bdd8922e06a6ae562dc2afd13e58e44ea35c75d"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208082","type":"CreateEvent","actor":{"id":180613,"login":"tuxtof","display_login":"tuxtof","gravatar_id":"","url":"https://api.github.com/users/tuxtof","avatar_url":"https://avatars.githubusercontent.com/u/180613?"},"repo":{"id":246599412,"name":"nutanix/docker-machine","url":"https://api.github.com/repos/nutanix/docker-machine"},"payload":{"ref":"v3.0.0-beta5","ref_type":"tag","master_branch":"master","description":"Docker Machine driver for Nutanix AHV","pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":6165865,"login":"nutanix","gravatar_id":"","url":"https://api.github.com/orgs/nutanix","avatar_url":"https://avatars.githubusercontent.com/u/6165865?"}} +{"id":"17244208088","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561415185,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"fbe21faba3add7d6f7d6695e2e89ca505d06755e","before":"31f2d408506c0dc9c2b60b5084e9ebb508f74614","commits":[{"sha":"fbe21faba3add7d6f7d6695e2e89ca505d06755e","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/fbe21faba3add7d6f7d6695e2e89ca505d06755e"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208090","type":"PushEvent","actor":{"id":40587912,"login":"supermobiteam2","display_login":"supermobiteam2","gravatar_id":"","url":"https://api.github.com/users/supermobiteam2","avatar_url":"https://avatars.githubusercontent.com/u/40587912?"},"repo":{"id":138681984,"name":"supermobiteam2/Tizi","url":"https://api.github.com/repos/supermobiteam2/Tizi"},"payload":{"push_id":7561415183,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"57e1abccecf63a96f30fc887420186f2cb9f1b37","before":"07afbeef76f0417300a3b126097df58cce2d8496","commits":[{"sha":"57e1abccecf63a96f30fc887420186f2cb9f1b37","author":{"name":"supermobiteam2","email":"f34688687956b708ea5937840a164da02e7b6797@users.noreply.github.com"},"message":"tizi ios","distinct":true,"url":"https://api.github.com/repos/supermobiteam2/Tizi/commits/57e1abccecf63a96f30fc887420186f2cb9f1b37"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208091","type":"PushEvent","actor":{"id":38429025,"login":"chuan12","display_login":"chuan12","gravatar_id":"","url":"https://api.github.com/users/chuan12","avatar_url":"https://avatars.githubusercontent.com/u/38429025?"},"repo":{"id":386557993,"name":"chuan12/chuan12.github.io","url":"https://api.github.com/repos/chuan12/chuan12.github.io"},"payload":{"push_id":7561415180,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"537af5e40705f7a7db7609f163a26b207bb0dfcf","before":"cd590dc3ab43d949529159382e56d03a4eefffe2","commits":[{"sha":"537af5e40705f7a7db7609f163a26b207bb0dfcf","author":{"name":"chuan12","email":"355aa477360314b41707201943c676d1da11ebe9@users.noreply.github.com"},"message":"Update data.json","distinct":true,"url":"https://api.github.com/repos/chuan12/chuan12.github.io/commits/537af5e40705f7a7db7609f163a26b207bb0dfcf"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208096","type":"PushEvent","actor":{"id":22955941,"login":"Volodichev","display_login":"Volodichev","gravatar_id":"","url":"https://api.github.com/users/Volodichev","avatar_url":"https://avatars.githubusercontent.com/u/22955941?"},"repo":{"id":374556291,"name":"Volodichev/proxy-list","url":"https://api.github.com/repos/Volodichev/proxy-list"},"payload":{"push_id":7561415177,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"5a7d1169bccad9e6a184fce04ee21bc7213310d9","before":"640ddec83af1fa5dc0ebcc8cd81579040255dac5","commits":[{"sha":"5a7d1169bccad9e6a184fce04ee21bc7213310d9","author":{"name":"Alexander","email":"db02dbb7d5376400a8069895ae273c92e0d1f31d@gmail.com"},"message":"hmn keys 21.07.21 09:00:01","distinct":true,"url":"https://api.github.com/repos/Volodichev/proxy-list/commits/5a7d1169bccad9e6a184fce04ee21bc7213310d9"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208105","type":"PushEvent","actor":{"id":3379460,"login":"beanslee2012","display_login":"beanslee2012","gravatar_id":"","url":"https://api.github.com/users/beanslee2012","avatar_url":"https://avatars.githubusercontent.com/u/3379460?"},"repo":{"id":215003385,"name":"beanslee2012/games","url":"https://api.github.com/repos/beanslee2012/games"},"payload":{"push_id":7561415198,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"5d245be4221b56177f0a64b28df7e8b8bc2f2692","before":"9041a835777613d91e9a7f59a74012781dcb3948","commits":[{"sha":"5d245be4221b56177f0a64b28df7e8b8bc2f2692","author":{"name":"beanslee2012","email":"9aadcc12fe6ab8c01e6245204a102e9e34317220@gmail.com"},"message":"daily update","distinct":true,"url":"https://api.github.com/repos/beanslee2012/games/commits/5d245be4221b56177f0a64b28df7e8b8bc2f2692"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208109","type":"PushEvent","actor":{"id":82322312,"login":"ifan-master","display_login":"ifan-master","gravatar_id":"","url":"https://api.github.com/users/ifan-master","avatar_url":"https://avatars.githubusercontent.com/u/82322312?"},"repo":{"id":372350393,"name":"ifan-master/ifan-master.github.io","url":"https://api.github.com/repos/ifan-master/ifan-master.github.io"},"payload":{"push_id":7561415066,"size":1,"distinct_size":1,"ref":"refs/heads/hexo","head":"314ac4dc64aa943f3806c2dd28eca054dce59366","before":"f25d95dc3f9b033a162f211b191cc72ef2528fd6","commits":[{"sha":"314ac4dc64aa943f3806c2dd28eca054dce59366","author":{"name":"ifan-master","email":"6fb0d5a9c69da8e92c5ca4c7990c1dbf272b9260@users.noreply.github.com"},"message":"Delete node_modules directory","distinct":true,"url":"https://api.github.com/repos/ifan-master/ifan-master.github.io/commits/314ac4dc64aa943f3806c2dd28eca054dce59366"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208110","type":"CreateEvent","actor":{"id":87747956,"login":"sayak58","display_login":"sayak58","gravatar_id":"","url":"https://api.github.com/users/sayak58","avatar_url":"https://avatars.githubusercontent.com/u/87747956?"},"repo":{"id":388010511,"name":"sayak58/sayak58","url":"https://api.github.com/repos/sayak58/sayak58"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":"Config files for my GitHub profile.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208116","type":"PushEvent","actor":{"id":5033193,"login":"rtx4d","display_login":"rtx4d","gravatar_id":"","url":"https://api.github.com/users/rtx4d","avatar_url":"https://avatars.githubusercontent.com/u/5033193?"},"repo":{"id":387563334,"name":"rtx4d/android_device_oneplus_oneplus9","url":"https://api.github.com/repos/rtx4d/android_device_oneplus_oneplus9"},"payload":{"push_id":7561415190,"size":1,"distinct_size":1,"ref":"refs/heads/ruby","head":"a200b9c016e1ae9412846a5bd8a70d16e899e328","before":"25f911fa703a8543c02c2f726e2f6691f980fd9a","commits":[{"sha":"a200b9c016e1ae9412846a5bd8a70d16e899e328","author":{"name":"Łukasz Patron","email":"6c9bff5473b68c1565a176ad458785864409abaa@gmail.com"},"message":"op9: sepolicy: Write PowerShare policy","distinct":true,"url":"https://api.github.com/repos/rtx4d/android_device_oneplus_oneplus9/commits/a200b9c016e1ae9412846a5bd8a70d16e899e328"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208117","type":"CreateEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":183236577,"name":"BlueBaseJS/plugin-native-web-swiper","url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper"},"payload":{"ref":"dependabot/npm_and_yarn/babel/cli-7.14.8","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":44442180,"login":"BlueBaseJS","gravatar_id":"","url":"https://api.github.com/orgs/BlueBaseJS","avatar_url":"https://avatars.githubusercontent.com/u/44442180?"}} +{"id":"17244208121","type":"PushEvent","actor":{"id":6924409,"login":"cg1101","display_login":"cg1101","gravatar_id":"","url":"https://api.github.com/users/cg1101","avatar_url":"https://avatars.githubusercontent.com/u/6924409?"},"repo":{"id":157529668,"name":"f-club/my-angular-app","url":"https://api.github.com/repos/f-club/my-angular-app"},"payload":{"push_id":7561415201,"size":2,"distinct_size":1,"ref":"refs/heads/master","head":"02a705eebb49f91fab457ba053d73c9362739a16","before":"ee3ab69f81ea6737ed91c3b2ab2834798e52000f","commits":[{"sha":"1b6c31899f2c70ab4e1fca761d93b9cb034cbaf2","author":{"name":"dependabot[bot]","email":"1c358da00a777d4e9898c1280ab801e2df165188@users.noreply.github.com"},"message":"Bump @angular/common from 12.1.1 to 12.1.2\n\nBumps [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) from 12.1.1 to 12.1.2.\n- [Release notes](https://github.com/angular/angular/releases)\n- [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md)\n- [Commits](https://github.com/angular/angular/commits/12.1.2/packages/common)\n\n---\nupdated-dependencies:\n- dependency-name: \"@angular/common\"\n dependency-type: direct:production\n update-type: version-update:semver-patch\n...\n\nSigned-off-by: dependabot[bot] ","distinct":false,"url":"https://api.github.com/repos/f-club/my-angular-app/commits/1b6c31899f2c70ab4e1fca761d93b9cb034cbaf2"},{"sha":"02a705eebb49f91fab457ba053d73c9362739a16","author":{"name":"cg1101","email":"e872fbfcf937194499ec2854e712a6b3cc3570f5@gmail.com"},"message":"Merge pull request #189 from f-club/dependabot/npm_and_yarn/angular/common-12.1.2\n\nBump @angular/common from 12.1.1 to 12.1.2","distinct":true,"url":"https://api.github.com/repos/f-club/my-angular-app/commits/02a705eebb49f91fab457ba053d73c9362739a16"}]},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":83454129,"login":"f-club","gravatar_id":"","url":"https://api.github.com/orgs/f-club","avatar_url":"https://avatars.githubusercontent.com/u/83454129?"}} +{"id":"17244208142","type":"PushEvent","actor":{"id":80881229,"login":"ItsMeVishal007","display_login":"ItsMeVishal007","gravatar_id":"","url":"https://api.github.com/users/ItsMeVishal007","avatar_url":"https://avatars.githubusercontent.com/u/80881229?"},"repo":{"id":373055818,"name":"ItsMeVishal007/Share-Site","url":"https://api.github.com/repos/ItsMeVishal007/Share-Site"},"payload":{"push_id":7561415208,"size":2,"distinct_size":1,"ref":"refs/heads/master","head":"cd138585cef701abc91e63a31e1462b367f4ac70","before":"4466e60114723817ab2624226d2e99f49c36378b","commits":[{"sha":"ea2894db8c72dae243f5e7532b71375ac13ddcbd","author":{"name":"Cd-Vishal007","email":"0e130d04afb61092e8b8a1fe644f5ec0d0e0b09b@gmail.com"},"message":"established relationship between user and place","distinct":false,"url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/commits/ea2894db8c72dae243f5e7532b71375ac13ddcbd"},{"sha":"cd138585cef701abc91e63a31e1462b367f4ac70","author":{"name":"Vishal kumar","email":"baa2cf5bdc227d95286c48ea254c811c15ffd8c0@users.noreply.github.com"},"message":"Merge pull request #29 from ItsMeVishal007/feat/cyber-dev\n\nestablished relationship between user and place","distinct":true,"url":"https://api.github.com/repos/ItsMeVishal007/Share-Site/commits/cd138585cef701abc91e63a31e1462b367f4ac70"}]},"public":true,"created_at":"2021-07-21T06:00:05Z"} +{"id":"17244208155","type":"PushEvent","actor":{"id":59435267,"login":"albertoabellagarcia","display_login":"albertoabellagarcia","gravatar_id":"","url":"https://api.github.com/users/albertoabellagarcia","avatar_url":"https://avatars.githubusercontent.com/u/59435267?"},"repo":{"id":190601053,"name":"smart-data-models/dataModel.PointOfInterest","url":"https://api.github.com/repos/smart-data-models/dataModel.PointOfInterest"},"payload":{"push_id":7561415210,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8dc8ab81028aa735144fccfa270c01ecb624055b","before":"8b36851baaaa34afb5837f5ef1bfcbef9d0ca469","commits":[{"sha":"8dc8ab81028aa735144fccfa270c01ecb624055b","author":{"name":"Alberto Abella","email":"4641006ed1a70ab96eea2556876687c02238d7d2@fiware.org"},"message":"updated on the 2021-07-21 08:00:04.445252","distinct":true,"url":"https://api.github.com/repos/smart-data-models/dataModel.PointOfInterest/commits/8dc8ab81028aa735144fccfa270c01ecb624055b"}]},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":44724489,"login":"smart-data-models","gravatar_id":"","url":"https://api.github.com/orgs/smart-data-models","avatar_url":"https://avatars.githubusercontent.com/u/44724489?"}} +{"id":"17244208163","type":"PullRequestReviewEvent","actor":{"id":26274310,"login":"bucherarnold","display_login":"bucherarnold","gravatar_id":"","url":"https://api.github.com/users/bucherarnold","avatar_url":"https://avatars.githubusercontent.com/u/26274310?"},"repo":{"id":293791520,"name":"i-Cell-Mobilsoft-Open-Source/roaster","url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster"},"payload":{"action":"created","review":{"id":711282688,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMjgyNjg4","user":{"login":"bucherarnold","id":26274310,"node_id":"MDQ6VXNlcjI2Mjc0MzEw","avatar_url":"https://avatars.githubusercontent.com/u/26274310?v=4","gravatar_id":"","url":"https://api.github.com/users/bucherarnold","html_url":"https://github.com/bucherarnold","followers_url":"https://api.github.com/users/bucherarnold/followers","following_url":"https://api.github.com/users/bucherarnold/following{/other_user}","gists_url":"https://api.github.com/users/bucherarnold/gists{/gist_id}","starred_url":"https://api.github.com/users/bucherarnold/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bucherarnold/subscriptions","organizations_url":"https://api.github.com/users/bucherarnold/orgs","repos_url":"https://api.github.com/users/bucherarnold/repos","events_url":"https://api.github.com/users/bucherarnold/events{/privacy}","received_events_url":"https://api.github.com/users/bucherarnold/received_events","type":"User","site_admin":false},"body":"","commit_id":"0eed8e69fac1746ef7c4cbdc9d320393d2eb9caf","submitted_at":"2021-07-21T06:00:05Z","state":"approved","html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster/pull/39#pullrequestreview-711282688","pull_request_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39","author_association":"NONE","_links":{"html":{"href":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster/pull/39#pullrequestreview-711282688"},"pull_request":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39"}}},"pull_request":{"url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39","id":693488670,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDg4Njcw","html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster/pull/39","diff_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster/pull/39.diff","patch_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster/pull/39.patch","issue_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/39","number":39,"state":"open","locked":false,"title":"Bugfix/37 fix noclassdeffounderror from coffee ","user":{"login":"rombow","id":5195827,"node_id":"MDQ6VXNlcjUxOTU4Mjc=","avatar_url":"https://avatars.githubusercontent.com/u/5195827?v=4","gravatar_id":"","url":"https://api.github.com/users/rombow","html_url":"https://github.com/rombow","followers_url":"https://api.github.com/users/rombow/followers","following_url":"https://api.github.com/users/rombow/following{/other_user}","gists_url":"https://api.github.com/users/rombow/gists{/gist_id}","starred_url":"https://api.github.com/users/rombow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rombow/subscriptions","organizations_url":"https://api.github.com/users/rombow/orgs","repos_url":"https://api.github.com/users/rombow/repos","events_url":"https://api.github.com/users/rombow/events{/privacy}","received_events_url":"https://api.github.com/users/rombow/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T13:55:42Z","updated_at":"2021-07-21T06:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bf78aaea90a1ccd5d2efb2a62af3793e389849d6","assignee":null,"assignees":[],"requested_reviewers":[{"login":"petrenyi-mark","id":46930755,"node_id":"MDQ6VXNlcjQ2OTMwNzU1","avatar_url":"https://avatars.githubusercontent.com/u/46930755?v=4","gravatar_id":"","url":"https://api.github.com/users/petrenyi-mark","html_url":"https://github.com/petrenyi-mark","followers_url":"https://api.github.com/users/petrenyi-mark/followers","following_url":"https://api.github.com/users/petrenyi-mark/following{/other_user}","gists_url":"https://api.github.com/users/petrenyi-mark/gists{/gist_id}","starred_url":"https://api.github.com/users/petrenyi-mark/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petrenyi-mark/subscriptions","organizations_url":"https://api.github.com/users/petrenyi-mark/orgs","repos_url":"https://api.github.com/users/petrenyi-mark/repos","events_url":"https://api.github.com/users/petrenyi-mark/events{/privacy}","received_events_url":"https://api.github.com/users/petrenyi-mark/received_events","type":"User","site_admin":false},{"login":"vasizs","id":63969139,"node_id":"MDQ6VXNlcjYzOTY5MTM5","avatar_url":"https://avatars.githubusercontent.com/u/63969139?v=4","gravatar_id":"","url":"https://api.github.com/users/vasizs","html_url":"https://github.com/vasizs","followers_url":"https://api.github.com/users/vasizs/followers","following_url":"https://api.github.com/users/vasizs/following{/other_user}","gists_url":"https://api.github.com/users/vasizs/gists{/gist_id}","starred_url":"https://api.github.com/users/vasizs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vasizs/subscriptions","organizations_url":"https://api.github.com/users/vasizs/orgs","repos_url":"https://api.github.com/users/vasizs/repos","events_url":"https://api.github.com/users/vasizs/events{/privacy}","received_events_url":"https://api.github.com/users/vasizs/received_events","type":"User","site_admin":false},{"login":"balazsbakos","id":64023218,"node_id":"MDQ6VXNlcjY0MDIzMjE4","avatar_url":"https://avatars.githubusercontent.com/u/64023218?v=4","gravatar_id":"","url":"https://api.github.com/users/balazsbakos","html_url":"https://github.com/balazsbakos","followers_url":"https://api.github.com/users/balazsbakos/followers","following_url":"https://api.github.com/users/balazsbakos/following{/other_user}","gists_url":"https://api.github.com/users/balazsbakos/gists{/gist_id}","starred_url":"https://api.github.com/users/balazsbakos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/balazsbakos/subscriptions","organizations_url":"https://api.github.com/users/balazsbakos/orgs","repos_url":"https://api.github.com/users/balazsbakos/repos","events_url":"https://api.github.com/users/balazsbakos/events{/privacy}","received_events_url":"https://api.github.com/users/balazsbakos/received_events","type":"User","site_admin":false},{"login":"csabasuli","id":64023961,"node_id":"MDQ6VXNlcjY0MDIzOTYx","avatar_url":"https://avatars.githubusercontent.com/u/64023961?v=4","gravatar_id":"","url":"https://api.github.com/users/csabasuli","html_url":"https://github.com/csabasuli","followers_url":"https://api.github.com/users/csabasuli/followers","following_url":"https://api.github.com/users/csabasuli/following{/other_user}","gists_url":"https://api.github.com/users/csabasuli/gists{/gist_id}","starred_url":"https://api.github.com/users/csabasuli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/csabasuli/subscriptions","organizations_url":"https://api.github.com/users/csabasuli/orgs","repos_url":"https://api.github.com/users/csabasuli/repos","events_url":"https://api.github.com/users/csabasuli/events{/privacy}","received_events_url":"https://api.github.com/users/csabasuli/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39/commits","review_comments_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39/comments","review_comment_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/comments{/number}","comments_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/39/comments","statuses_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/statuses/0eed8e69fac1746ef7c4cbdc9d320393d2eb9caf","head":{"label":"i-Cell-Mobilsoft-Open-Source:bugfix/37-fix-noclassdeffounderror-from-coffee-","ref":"bugfix/37-fix-noclassdeffounderror-from-coffee-","sha":"0eed8e69fac1746ef7c4cbdc9d320393d2eb9caf","user":{"login":"i-Cell-Mobilsoft-Open-Source","id":63906642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYzOTA2NjQy","avatar_url":"https://avatars.githubusercontent.com/u/63906642?v=4","gravatar_id":"","url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source","html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source","followers_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/followers","following_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/following{/other_user}","gists_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/gists{/gist_id}","starred_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/subscriptions","organizations_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/orgs","repos_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/repos","events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/events{/privacy}","received_events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/received_events","type":"Organization","site_admin":false},"repo":{"id":293791520,"node_id":"MDEwOlJlcG9zaXRvcnkyOTM3OTE1MjA=","name":"roaster","full_name":"i-Cell-Mobilsoft-Open-Source/roaster","private":false,"owner":{"login":"i-Cell-Mobilsoft-Open-Source","id":63906642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYzOTA2NjQy","avatar_url":"https://avatars.githubusercontent.com/u/63906642?v=4","gravatar_id":"","url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source","html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source","followers_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/followers","following_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/following{/other_user}","gists_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/gists{/gist_id}","starred_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/subscriptions","organizations_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/orgs","repos_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/repos","events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/events{/privacy}","received_events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster","description":"Developer and Intergration test framework based on coff:ee","fork":false,"url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster","forks_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/forks","keys_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/keys{/key_id}","collaborators_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/teams","hooks_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/hooks","issue_events_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/events{/number}","events_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/events","assignees_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/assignees{/user}","branches_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/branches{/branch}","tags_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/tags","blobs_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/refs{/sha}","trees_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/trees{/sha}","statuses_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/statuses/{sha}","languages_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/languages","stargazers_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/stargazers","contributors_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/contributors","subscribers_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/subscribers","subscription_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/subscription","commits_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/commits{/sha}","git_commits_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/commits{/sha}","comments_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/comments{/number}","issue_comment_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/comments{/number}","contents_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/contents/{+path}","compare_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/compare/{base}...{head}","merges_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/merges","archive_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/downloads","issues_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues{/number}","pulls_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls{/number}","milestones_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/milestones{/number}","notifications_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/labels{/name}","releases_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/releases{/id}","deployments_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/deployments","created_at":"2020-09-08T11:36:43Z","updated_at":"2021-07-19T11:17:35Z","pushed_at":"2021-07-20T14:01:41Z","git_url":"git://github.com/i-Cell-Mobilsoft-Open-Source/roaster.git","ssh_url":"git@github.com:i-Cell-Mobilsoft-Open-Source/roaster.git","clone_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster.git","svn_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster","homepage":"https://i-cell-mobilsoft-open-source.github.io/roaster/","size":471,"stargazers_count":7,"watchers_count":7,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":6,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":3,"open_issues":6,"watchers":7,"default_branch":"master"}},"base":{"label":"i-Cell-Mobilsoft-Open-Source:master","ref":"master","sha":"b08154d2688dbbb29b7bcefbf81c49e55b203887","user":{"login":"i-Cell-Mobilsoft-Open-Source","id":63906642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYzOTA2NjQy","avatar_url":"https://avatars.githubusercontent.com/u/63906642?v=4","gravatar_id":"","url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source","html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source","followers_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/followers","following_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/following{/other_user}","gists_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/gists{/gist_id}","starred_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/subscriptions","organizations_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/orgs","repos_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/repos","events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/events{/privacy}","received_events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/received_events","type":"Organization","site_admin":false},"repo":{"id":293791520,"node_id":"MDEwOlJlcG9zaXRvcnkyOTM3OTE1MjA=","name":"roaster","full_name":"i-Cell-Mobilsoft-Open-Source/roaster","private":false,"owner":{"login":"i-Cell-Mobilsoft-Open-Source","id":63906642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYzOTA2NjQy","avatar_url":"https://avatars.githubusercontent.com/u/63906642?v=4","gravatar_id":"","url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source","html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source","followers_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/followers","following_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/following{/other_user}","gists_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/gists{/gist_id}","starred_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/subscriptions","organizations_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/orgs","repos_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/repos","events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/events{/privacy}","received_events_url":"https://api.github.com/users/i-Cell-Mobilsoft-Open-Source/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster","description":"Developer and Intergration test framework based on coff:ee","fork":false,"url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster","forks_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/forks","keys_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/keys{/key_id}","collaborators_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/teams","hooks_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/hooks","issue_events_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/events{/number}","events_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/events","assignees_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/assignees{/user}","branches_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/branches{/branch}","tags_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/tags","blobs_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/refs{/sha}","trees_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/trees{/sha}","statuses_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/statuses/{sha}","languages_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/languages","stargazers_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/stargazers","contributors_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/contributors","subscribers_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/subscribers","subscription_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/subscription","commits_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/commits{/sha}","git_commits_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/git/commits{/sha}","comments_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/comments{/number}","issue_comment_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/comments{/number}","contents_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/contents/{+path}","compare_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/compare/{base}...{head}","merges_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/merges","archive_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/downloads","issues_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues{/number}","pulls_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls{/number}","milestones_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/milestones{/number}","notifications_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/labels{/name}","releases_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/releases{/id}","deployments_url":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/deployments","created_at":"2020-09-08T11:36:43Z","updated_at":"2021-07-19T11:17:35Z","pushed_at":"2021-07-20T14:01:41Z","git_url":"git://github.com/i-Cell-Mobilsoft-Open-Source/roaster.git","ssh_url":"git@github.com:i-Cell-Mobilsoft-Open-Source/roaster.git","clone_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster.git","svn_url":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster","homepage":"https://i-cell-mobilsoft-open-source.github.io/roaster/","size":471,"stargazers_count":7,"watchers_count":7,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":6,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":3,"open_issues":6,"watchers":7,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39"},"html":{"href":"https://github.com/i-Cell-Mobilsoft-Open-Source/roaster/pull/39"},"issue":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/39"},"comments":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/issues/39/comments"},"review_comments":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39/comments"},"review_comment":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/pulls/39/commits"},"statuses":{"href":"https://api.github.com/repos/i-Cell-Mobilsoft-Open-Source/roaster/statuses/0eed8e69fac1746ef7c4cbdc9d320393d2eb9caf"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":63906642,"login":"i-Cell-Mobilsoft-Open-Source","gravatar_id":"","url":"https://api.github.com/orgs/i-Cell-Mobilsoft-Open-Source","avatar_url":"https://avatars.githubusercontent.com/u/63906642?"}} +{"id":"17244208166","type":"PullRequestEvent","actor":{"id":58432773,"login":"hakumizuki","display_login":"hakumizuki","gravatar_id":"","url":"https://api.github.com/users/hakumizuki","avatar_url":"https://avatars.githubusercontent.com/u/58432773?"},"repo":{"id":82206054,"name":"weseek/growi","url":"https://api.github.com/repos/weseek/growi"},"payload":{"action":"opened","number":4051,"pull_request":{"url":"https://api.github.com/repos/weseek/growi/pulls/4051","id":694073982,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDczOTgy","html_url":"https://github.com/weseek/growi/pull/4051","diff_url":"https://github.com/weseek/growi/pull/4051.diff","patch_url":"https://github.com/weseek/growi/pull/4051.patch","issue_url":"https://api.github.com/repos/weseek/growi/issues/4051","number":4051,"state":"open","locked":false,"title":"Imprv/gw 5883 error handling using http errors","user":{"login":"hakumizuki","id":58432773,"node_id":"MDQ6VXNlcjU4NDMyNzcz","avatar_url":"https://avatars.githubusercontent.com/u/58432773?v=4","gravatar_id":"","url":"https://api.github.com/users/hakumizuki","html_url":"https://github.com/hakumizuki","followers_url":"https://api.github.com/users/hakumizuki/followers","following_url":"https://api.github.com/users/hakumizuki/following{/other_user}","gists_url":"https://api.github.com/users/hakumizuki/gists{/gist_id}","starred_url":"https://api.github.com/users/hakumizuki/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hakumizuki/subscriptions","organizations_url":"https://api.github.com/users/hakumizuki/orgs","repos_url":"https://api.github.com/users/hakumizuki/repos","events_url":"https://api.github.com/users/hakumizuki/events{/privacy}","received_events_url":"https://api.github.com/users/hakumizuki/received_events","type":"User","site_admin":false},"body":"GW-6738 Review&Merge(5883)\r\nGW-5883 エラーハンドリングの改善\r\n\r\nのマージです。動作確認済みです。","created_at":"2021-07-21T06:00:05Z","updated_at":"2021-07-21T06:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/weseek/growi/pulls/4051/commits","review_comments_url":"https://api.github.com/repos/weseek/growi/pulls/4051/comments","review_comment_url":"https://api.github.com/repos/weseek/growi/pulls/comments{/number}","comments_url":"https://api.github.com/repos/weseek/growi/issues/4051/comments","statuses_url":"https://api.github.com/repos/weseek/growi/statuses/5f92e03b39d16c57ae5657b0fa9e1b246281c2a2","head":{"label":"weseek:imprv/GW-5883-error-handling-using-http-errors","ref":"imprv/GW-5883-error-handling-using-http-errors","sha":"5f92e03b39d16c57ae5657b0fa9e1b246281c2a2","user":{"login":"weseek","id":6468105,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0NjgxMDU=","avatar_url":"https://avatars.githubusercontent.com/u/6468105?v=4","gravatar_id":"","url":"https://api.github.com/users/weseek","html_url":"https://github.com/weseek","followers_url":"https://api.github.com/users/weseek/followers","following_url":"https://api.github.com/users/weseek/following{/other_user}","gists_url":"https://api.github.com/users/weseek/gists{/gist_id}","starred_url":"https://api.github.com/users/weseek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weseek/subscriptions","organizations_url":"https://api.github.com/users/weseek/orgs","repos_url":"https://api.github.com/users/weseek/repos","events_url":"https://api.github.com/users/weseek/events{/privacy}","received_events_url":"https://api.github.com/users/weseek/received_events","type":"Organization","site_admin":false},"repo":{"id":82206054,"node_id":"MDEwOlJlcG9zaXRvcnk4MjIwNjA1NA==","name":"growi","full_name":"weseek/growi","private":false,"owner":{"login":"weseek","id":6468105,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0NjgxMDU=","avatar_url":"https://avatars.githubusercontent.com/u/6468105?v=4","gravatar_id":"","url":"https://api.github.com/users/weseek","html_url":"https://github.com/weseek","followers_url":"https://api.github.com/users/weseek/followers","following_url":"https://api.github.com/users/weseek/following{/other_user}","gists_url":"https://api.github.com/users/weseek/gists{/gist_id}","starred_url":"https://api.github.com/users/weseek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weseek/subscriptions","organizations_url":"https://api.github.com/users/weseek/orgs","repos_url":"https://api.github.com/users/weseek/repos","events_url":"https://api.github.com/users/weseek/events{/privacy}","received_events_url":"https://api.github.com/users/weseek/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/weseek/growi","description":":anchor: GROWI - Team collaboration software using markdown","fork":false,"url":"https://api.github.com/repos/weseek/growi","forks_url":"https://api.github.com/repos/weseek/growi/forks","keys_url":"https://api.github.com/repos/weseek/growi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weseek/growi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weseek/growi/teams","hooks_url":"https://api.github.com/repos/weseek/growi/hooks","issue_events_url":"https://api.github.com/repos/weseek/growi/issues/events{/number}","events_url":"https://api.github.com/repos/weseek/growi/events","assignees_url":"https://api.github.com/repos/weseek/growi/assignees{/user}","branches_url":"https://api.github.com/repos/weseek/growi/branches{/branch}","tags_url":"https://api.github.com/repos/weseek/growi/tags","blobs_url":"https://api.github.com/repos/weseek/growi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weseek/growi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weseek/growi/git/refs{/sha}","trees_url":"https://api.github.com/repos/weseek/growi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weseek/growi/statuses/{sha}","languages_url":"https://api.github.com/repos/weseek/growi/languages","stargazers_url":"https://api.github.com/repos/weseek/growi/stargazers","contributors_url":"https://api.github.com/repos/weseek/growi/contributors","subscribers_url":"https://api.github.com/repos/weseek/growi/subscribers","subscription_url":"https://api.github.com/repos/weseek/growi/subscription","commits_url":"https://api.github.com/repos/weseek/growi/commits{/sha}","git_commits_url":"https://api.github.com/repos/weseek/growi/git/commits{/sha}","comments_url":"https://api.github.com/repos/weseek/growi/comments{/number}","issue_comment_url":"https://api.github.com/repos/weseek/growi/issues/comments{/number}","contents_url":"https://api.github.com/repos/weseek/growi/contents/{+path}","compare_url":"https://api.github.com/repos/weseek/growi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weseek/growi/merges","archive_url":"https://api.github.com/repos/weseek/growi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weseek/growi/downloads","issues_url":"https://api.github.com/repos/weseek/growi/issues{/number}","pulls_url":"https://api.github.com/repos/weseek/growi/pulls{/number}","milestones_url":"https://api.github.com/repos/weseek/growi/milestones{/number}","notifications_url":"https://api.github.com/repos/weseek/growi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weseek/growi/labels{/name}","releases_url":"https://api.github.com/repos/weseek/growi/releases{/id}","deployments_url":"https://api.github.com/repos/weseek/growi/deployments","created_at":"2017-02-16T17:15:47Z","updated_at":"2021-07-21T04:02:55Z","pushed_at":"2021-07-21T05:38:26Z","git_url":"git://github.com/weseek/growi.git","ssh_url":"git@github.com:weseek/growi.git","clone_url":"https://github.com/weseek/growi.git","svn_url":"https://github.com/weseek/growi","homepage":"https://growi.org","size":58035,"stargazers_count":905,"watchers_count":905,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":185,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":81,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":185,"open_issues":81,"watchers":905,"default_branch":"master"}},"base":{"label":"weseek:master","ref":"master","sha":"22d7937681adcf542910fe6510011ed5f7783e43","user":{"login":"weseek","id":6468105,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0NjgxMDU=","avatar_url":"https://avatars.githubusercontent.com/u/6468105?v=4","gravatar_id":"","url":"https://api.github.com/users/weseek","html_url":"https://github.com/weseek","followers_url":"https://api.github.com/users/weseek/followers","following_url":"https://api.github.com/users/weseek/following{/other_user}","gists_url":"https://api.github.com/users/weseek/gists{/gist_id}","starred_url":"https://api.github.com/users/weseek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weseek/subscriptions","organizations_url":"https://api.github.com/users/weseek/orgs","repos_url":"https://api.github.com/users/weseek/repos","events_url":"https://api.github.com/users/weseek/events{/privacy}","received_events_url":"https://api.github.com/users/weseek/received_events","type":"Organization","site_admin":false},"repo":{"id":82206054,"node_id":"MDEwOlJlcG9zaXRvcnk4MjIwNjA1NA==","name":"growi","full_name":"weseek/growi","private":false,"owner":{"login":"weseek","id":6468105,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0NjgxMDU=","avatar_url":"https://avatars.githubusercontent.com/u/6468105?v=4","gravatar_id":"","url":"https://api.github.com/users/weseek","html_url":"https://github.com/weseek","followers_url":"https://api.github.com/users/weseek/followers","following_url":"https://api.github.com/users/weseek/following{/other_user}","gists_url":"https://api.github.com/users/weseek/gists{/gist_id}","starred_url":"https://api.github.com/users/weseek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weseek/subscriptions","organizations_url":"https://api.github.com/users/weseek/orgs","repos_url":"https://api.github.com/users/weseek/repos","events_url":"https://api.github.com/users/weseek/events{/privacy}","received_events_url":"https://api.github.com/users/weseek/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/weseek/growi","description":":anchor: GROWI - Team collaboration software using markdown","fork":false,"url":"https://api.github.com/repos/weseek/growi","forks_url":"https://api.github.com/repos/weseek/growi/forks","keys_url":"https://api.github.com/repos/weseek/growi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weseek/growi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weseek/growi/teams","hooks_url":"https://api.github.com/repos/weseek/growi/hooks","issue_events_url":"https://api.github.com/repos/weseek/growi/issues/events{/number}","events_url":"https://api.github.com/repos/weseek/growi/events","assignees_url":"https://api.github.com/repos/weseek/growi/assignees{/user}","branches_url":"https://api.github.com/repos/weseek/growi/branches{/branch}","tags_url":"https://api.github.com/repos/weseek/growi/tags","blobs_url":"https://api.github.com/repos/weseek/growi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weseek/growi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weseek/growi/git/refs{/sha}","trees_url":"https://api.github.com/repos/weseek/growi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weseek/growi/statuses/{sha}","languages_url":"https://api.github.com/repos/weseek/growi/languages","stargazers_url":"https://api.github.com/repos/weseek/growi/stargazers","contributors_url":"https://api.github.com/repos/weseek/growi/contributors","subscribers_url":"https://api.github.com/repos/weseek/growi/subscribers","subscription_url":"https://api.github.com/repos/weseek/growi/subscription","commits_url":"https://api.github.com/repos/weseek/growi/commits{/sha}","git_commits_url":"https://api.github.com/repos/weseek/growi/git/commits{/sha}","comments_url":"https://api.github.com/repos/weseek/growi/comments{/number}","issue_comment_url":"https://api.github.com/repos/weseek/growi/issues/comments{/number}","contents_url":"https://api.github.com/repos/weseek/growi/contents/{+path}","compare_url":"https://api.github.com/repos/weseek/growi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weseek/growi/merges","archive_url":"https://api.github.com/repos/weseek/growi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weseek/growi/downloads","issues_url":"https://api.github.com/repos/weseek/growi/issues{/number}","pulls_url":"https://api.github.com/repos/weseek/growi/pulls{/number}","milestones_url":"https://api.github.com/repos/weseek/growi/milestones{/number}","notifications_url":"https://api.github.com/repos/weseek/growi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weseek/growi/labels{/name}","releases_url":"https://api.github.com/repos/weseek/growi/releases{/id}","deployments_url":"https://api.github.com/repos/weseek/growi/deployments","created_at":"2017-02-16T17:15:47Z","updated_at":"2021-07-21T04:02:55Z","pushed_at":"2021-07-21T05:38:26Z","git_url":"git://github.com/weseek/growi.git","ssh_url":"git@github.com:weseek/growi.git","clone_url":"https://github.com/weseek/growi.git","svn_url":"https://github.com/weseek/growi","homepage":"https://growi.org","size":58035,"stargazers_count":905,"watchers_count":905,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":185,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":81,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":185,"open_issues":81,"watchers":905,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/weseek/growi/pulls/4051"},"html":{"href":"https://github.com/weseek/growi/pull/4051"},"issue":{"href":"https://api.github.com/repos/weseek/growi/issues/4051"},"comments":{"href":"https://api.github.com/repos/weseek/growi/issues/4051/comments"},"review_comments":{"href":"https://api.github.com/repos/weseek/growi/pulls/4051/comments"},"review_comment":{"href":"https://api.github.com/repos/weseek/growi/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/weseek/growi/pulls/4051/commits"},"statuses":{"href":"https://api.github.com/repos/weseek/growi/statuses/5f92e03b39d16c57ae5657b0fa9e1b246281c2a2"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":16,"additions":150,"deletions":15,"changed_files":15}},"public":true,"created_at":"2021-07-21T06:00:05Z","org":{"id":6468105,"login":"weseek","gravatar_id":"","url":"https://api.github.com/orgs/weseek","avatar_url":"https://avatars.githubusercontent.com/u/6468105?"}} +{"id":"17244208178","type":"PullRequestEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":183236577,"name":"BlueBaseJS/plugin-native-web-swiper","url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper"},"payload":{"action":"opened","number":248,"pull_request":{"url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/248","id":694073981,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDczOTgx","html_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper/pull/248","diff_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper/pull/248.diff","patch_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper/pull/248.patch","issue_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/248","number":248,"state":"open","locked":false,"title":"build(deps-dev): bump @babel/cli from 7.4.3 to 7.14.8","user":{"login":"dependabot-preview[bot]","id":27856297,"node_id":"MDM6Qm90Mjc4NTYyOTc=","avatar_url":"https://avatars.githubusercontent.com/in/2141?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview%5Bbot%5D","html_url":"https://github.com/apps/dependabot-preview","followers_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bumps [@babel/cli](https://github.com/babel/babel/tree/HEAD/packages/babel-cli) from 7.4.3 to 7.14.8.\n
    \nRelease notes\n

    Sourced from @​babel/cli's releases.

    \n
    \n

    v7.14.8 (2021-07-20)

    \n

    Thanks @​colinaaa, @​jaeseokk and @​nme077 for your first PRs!

    \n

    :eyeglasses: Spec Compliance

    \n
      \n
    • babel-helper-create-class-features-plugin, babel-plugin-proposal-class-static-block, babel-plugin-transform-new-target\n
        \n
      • #13560 fix(class-properties): replace new.target in static properties with undefined (@​colinaaa)
      • \n
      \n
    • \n
    • babel-parser\n\n
    • \n
    • babel-helper-module-transforms, babel-helper-simple-access, babel-plugin-transform-modules-commonjs\n\n
    • \n
    \n

    :bug: Bug Fix

    \n\n

    :nail_care: Polish

    \n\n

    :memo: Documentation

    \n\n

    :house: Internal

    \n
      \n
    • babel-helpers\n\n
    • \n
    \n

    :running_woman: Performance

    \n\n

    Committers: 12

    \n\n\n
    \n

    ... (truncated)

    \n
    \n
    \nChangelog\n

    Sourced from @​babel/cli's changelog.

    \n
    \n

    v7.14.8 (2021-07-20)

    \n

    :eyeglasses: Spec Compliance

    \n
      \n
    • babel-helper-create-class-features-plugin, babel-plugin-proposal-class-static-block, babel-plugin-transform-new-target\n
        \n
      • #13560 fix(class-properties): replace new.target in static properties with undefined (@​colinaaa)
      • \n
      \n
    • \n
    • babel-parser\n\n
    • \n
    • babel-helper-module-transforms, babel-helper-simple-access, babel-plugin-transform-modules-commonjs\n\n
    • \n
    \n

    :bug: Bug Fix

    \n\n

    :nail_care: Polish

    \n\n

    :memo: Documentation

    \n\n

    :house: Internal

    \n
      \n
    • babel-helpers\n\n
    • \n
    \n

    :running_woman: Performance

    \n\n

    v7.14.7 (2021-06-21)

    \n

    :bug: Bug Fix

    \n
      \n
    • babel-plugin-proposal-object-rest-spread\n\n
    • \n
    • babel-plugin-transform-destructuring\n\n
    • \n
    • babel-traverse
    • \n
    \n\n
    \n

    ... (truncated)

    \n
    \n
    \nCommits\n\n
    \n
    \n\n\n[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=@babel/cli&package-manager=npm_and_yarn&previous-version=7.4.3&new-version=7.14.8)](https://dependabot.com/compatibility-score/?dependency-name=@babel/cli&package-manager=npm_and_yarn&previous-version=7.4.3&new-version=7.14.8)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language\n- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language\n- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language\n- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language\n- `@dependabot badge me` will comment on this PR with code to add a \"Dependabot enabled\" badge to your readme\n\nAdditionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):\n- Update frequency (including time of day and day of week)\n- Pull request limits (per update run and/or open at any time)\n- Out-of-range updates (receive only lockfile updates, if desired)\n- Security updates (receive only security updates, if desired)\n\n\n\n
    ","created_at":"2021-07-21T06:00:05Z","updated_at":"2021-07-21T06:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/248/commits","review_comments_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/248/comments","review_comment_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/comments{/number}","comments_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/248/comments","statuses_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/statuses/ecdc17bf091f7a9eeb3336d0dc0ebe2c83591b04","head":{"label":"BlueBaseJS:dependabot/npm_and_yarn/babel/cli-7.14.8","ref":"dependabot/npm_and_yarn/babel/cli-7.14.8","sha":"ecdc17bf091f7a9eeb3336d0dc0ebe2c83591b04","user":{"login":"BlueBaseJS","id":44442180,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NDQyMTgw","avatar_url":"https://avatars.githubusercontent.com/u/44442180?v=4","gravatar_id":"","url":"https://api.github.com/users/BlueBaseJS","html_url":"https://github.com/BlueBaseJS","followers_url":"https://api.github.com/users/BlueBaseJS/followers","following_url":"https://api.github.com/users/BlueBaseJS/following{/other_user}","gists_url":"https://api.github.com/users/BlueBaseJS/gists{/gist_id}","starred_url":"https://api.github.com/users/BlueBaseJS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BlueBaseJS/subscriptions","organizations_url":"https://api.github.com/users/BlueBaseJS/orgs","repos_url":"https://api.github.com/users/BlueBaseJS/repos","events_url":"https://api.github.com/users/BlueBaseJS/events{/privacy}","received_events_url":"https://api.github.com/users/BlueBaseJS/received_events","type":"Organization","site_admin":false},"repo":{"id":183236577,"node_id":"MDEwOlJlcG9zaXRvcnkxODMyMzY1Nzc=","name":"plugin-native-web-swiper","full_name":"BlueBaseJS/plugin-native-web-swiper","private":false,"owner":{"login":"BlueBaseJS","id":44442180,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NDQyMTgw","avatar_url":"https://avatars.githubusercontent.com/u/44442180?v=4","gravatar_id":"","url":"https://api.github.com/users/BlueBaseJS","html_url":"https://github.com/BlueBaseJS","followers_url":"https://api.github.com/users/BlueBaseJS/followers","following_url":"https://api.github.com/users/BlueBaseJS/following{/other_user}","gists_url":"https://api.github.com/users/BlueBaseJS/gists{/gist_id}","starred_url":"https://api.github.com/users/BlueBaseJS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BlueBaseJS/subscriptions","organizations_url":"https://api.github.com/users/BlueBaseJS/orgs","repos_url":"https://api.github.com/users/BlueBaseJS/repos","events_url":"https://api.github.com/users/BlueBaseJS/events{/privacy}","received_events_url":"https://api.github.com/users/BlueBaseJS/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper","description":null,"fork":false,"url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper","forks_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/forks","keys_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/teams","hooks_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/hooks","issue_events_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/events{/number}","events_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/events","assignees_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/assignees{/user}","branches_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/branches{/branch}","tags_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/tags","blobs_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/refs{/sha}","trees_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/statuses/{sha}","languages_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/languages","stargazers_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/stargazers","contributors_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/contributors","subscribers_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/subscribers","subscription_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/subscription","commits_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/commits{/sha}","git_commits_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/commits{/sha}","comments_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/comments{/number}","issue_comment_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/comments{/number}","contents_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/contents/{+path}","compare_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/merges","archive_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/downloads","issues_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues{/number}","pulls_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls{/number}","milestones_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/milestones{/number}","notifications_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/labels{/name}","releases_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/releases{/id}","deployments_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/deployments","created_at":"2019-04-24T13:38:50Z","updated_at":"2019-05-25T07:42:18Z","pushed_at":"2021-07-21T06:00:05Z","git_url":"git://github.com/BlueBaseJS/plugin-native-web-swiper.git","ssh_url":"git@github.com:BlueBaseJS/plugin-native-web-swiper.git","clone_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper.git","svn_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper","homepage":null,"size":5203,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":27,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":27,"watchers":0,"default_branch":"master"}},"base":{"label":"BlueBaseJS:master","ref":"master","sha":"21b93164e63404deca457cdf57695ddbb4a055b6","user":{"login":"BlueBaseJS","id":44442180,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NDQyMTgw","avatar_url":"https://avatars.githubusercontent.com/u/44442180?v=4","gravatar_id":"","url":"https://api.github.com/users/BlueBaseJS","html_url":"https://github.com/BlueBaseJS","followers_url":"https://api.github.com/users/BlueBaseJS/followers","following_url":"https://api.github.com/users/BlueBaseJS/following{/other_user}","gists_url":"https://api.github.com/users/BlueBaseJS/gists{/gist_id}","starred_url":"https://api.github.com/users/BlueBaseJS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BlueBaseJS/subscriptions","organizations_url":"https://api.github.com/users/BlueBaseJS/orgs","repos_url":"https://api.github.com/users/BlueBaseJS/repos","events_url":"https://api.github.com/users/BlueBaseJS/events{/privacy}","received_events_url":"https://api.github.com/users/BlueBaseJS/received_events","type":"Organization","site_admin":false},"repo":{"id":183236577,"node_id":"MDEwOlJlcG9zaXRvcnkxODMyMzY1Nzc=","name":"plugin-native-web-swiper","full_name":"BlueBaseJS/plugin-native-web-swiper","private":false,"owner":{"login":"BlueBaseJS","id":44442180,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NDQyMTgw","avatar_url":"https://avatars.githubusercontent.com/u/44442180?v=4","gravatar_id":"","url":"https://api.github.com/users/BlueBaseJS","html_url":"https://github.com/BlueBaseJS","followers_url":"https://api.github.com/users/BlueBaseJS/followers","following_url":"https://api.github.com/users/BlueBaseJS/following{/other_user}","gists_url":"https://api.github.com/users/BlueBaseJS/gists{/gist_id}","starred_url":"https://api.github.com/users/BlueBaseJS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BlueBaseJS/subscriptions","organizations_url":"https://api.github.com/users/BlueBaseJS/orgs","repos_url":"https://api.github.com/users/BlueBaseJS/repos","events_url":"https://api.github.com/users/BlueBaseJS/events{/privacy}","received_events_url":"https://api.github.com/users/BlueBaseJS/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper","description":null,"fork":false,"url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper","forks_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/forks","keys_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/teams","hooks_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/hooks","issue_events_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/events{/number}","events_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/events","assignees_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/assignees{/user}","branches_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/branches{/branch}","tags_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/tags","blobs_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/refs{/sha}","trees_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/statuses/{sha}","languages_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/languages","stargazers_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/stargazers","contributors_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/contributors","subscribers_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/subscribers","subscription_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/subscription","commits_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/commits{/sha}","git_commits_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/git/commits{/sha}","comments_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/comments{/number}","issue_comment_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/comments{/number}","contents_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/contents/{+path}","compare_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/merges","archive_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/downloads","issues_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues{/number}","pulls_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls{/number}","milestones_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/milestones{/number}","notifications_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/labels{/name}","releases_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/releases{/id}","deployments_url":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/deployments","created_at":"2019-04-24T13:38:50Z","updated_at":"2019-05-25T07:42:18Z","pushed_at":"2021-07-21T06:00:05Z","git_url":"git://github.com/BlueBaseJS/plugin-native-web-swiper.git","ssh_url":"git@github.com:BlueBaseJS/plugin-native-web-swiper.git","clone_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper.git","svn_url":"https://github.com/BlueBaseJS/plugin-native-web-swiper","homepage":null,"size":5203,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":27,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":27,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/248"},"html":{"href":"https://github.com/BlueBaseJS/plugin-native-web-swiper/pull/248"},"issue":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/248"},"comments":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/issues/248/comments"},"review_comments":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/248/comments"},"review_comment":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/pulls/248/commits"},"statuses":{"href":"https://api.github.com/repos/BlueBaseJS/plugin-native-web-swiper/statuses/ecdc17bf091f7a9eeb3336d0dc0ebe2c83591b04"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":118,"deletions":21,"changed_files":1}},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":44442180,"login":"BlueBaseJS","gravatar_id":"","url":"https://api.github.com/orgs/BlueBaseJS","avatar_url":"https://avatars.githubusercontent.com/u/44442180?"}} +{"id":"17244208180","type":"CreateEvent","actor":{"id":62318101,"login":"SyifaAinnur","display_login":"SyifaAinnur","gravatar_id":"","url":"https://api.github.com/users/SyifaAinnur","avatar_url":"https://avatars.githubusercontent.com/u/62318101?"},"repo":{"id":388010515,"name":"SyifaAinnur/Money_management-flutter","url":"https://api.github.com/repos/SyifaAinnur/Money_management-flutter"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208182","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561415228,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"2102bfa1028e8abd5a10b8d8cc49caaf58a4665d","before":"fbe21faba3add7d6f7d6695e2e89ca505d06755e","commits":[{"sha":"2102bfa1028e8abd5a10b8d8cc49caaf58a4665d","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/2102bfa1028e8abd5a10b8d8cc49caaf58a4665d"}]},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208190","type":"CreateEvent","actor":{"id":86511397,"login":"AvijeetSahoo","display_login":"AvijeetSahoo","gravatar_id":"","url":"https://api.github.com/users/AvijeetSahoo","avatar_url":"https://avatars.githubusercontent.com/u/86511397?"},"repo":{"id":380437695,"name":"AvijeetSahoo/AvijeetSahoo","url":"https://api.github.com/repos/AvijeetSahoo/AvijeetSahoo"},"payload":{"ref":"master","ref_type":"branch","master_branch":"main","description":"Config files for my GitHub profile.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208195","type":"PushEvent","actor":{"id":53513,"login":"nsuan","display_login":"nsuan","gravatar_id":"","url":"https://api.github.com/users/nsuan","avatar_url":"https://avatars.githubusercontent.com/u/53513?"},"repo":{"id":370881581,"name":"nsuan/furry-fortnight","url":"https://api.github.com/repos/nsuan/furry-fortnight"},"payload":{"push_id":7561415235,"size":4,"distinct_size":4,"ref":"refs/heads/main","head":"42cf3a6e89ef5891997a6758f09ebbc4297426c1","before":"ca40b77ab40cac010598e537cf0058bc4fad93b7","commits":[{"sha":"e6d9dea7cc63a00770776c67a5891e7b0b1f94f4","author":{"name":"Nick Suan","email":"cf0bc5f1c1ddb0fada692904e0e398c835913fed@nonexiste.net"},"message":"update userinfo Wed Jul 21 01:00:36 EDT 2021","distinct":true,"url":"https://api.github.com/repos/nsuan/furry-fortnight/commits/e6d9dea7cc63a00770776c67a5891e7b0b1f94f4"},{"sha":"f99e3e0144de566b751029f776d25af4f66ac90d","author":{"name":"Nick Suan","email":"cf0bc5f1c1ddb0fada692904e0e398c835913fed@nonexiste.net"},"message":"update userinfo Wed Jul 21 01:16:42 EDT 2021","distinct":true,"url":"https://api.github.com/repos/nsuan/furry-fortnight/commits/f99e3e0144de566b751029f776d25af4f66ac90d"},{"sha":"aac0aac6b6f864eeaa718440f1e27a6a94c95dea","author":{"name":"Nick Suan","email":"cf0bc5f1c1ddb0fada692904e0e398c835913fed@nonexiste.net"},"message":"update userinfo Wed Jul 21 01:32:48 EDT 2021","distinct":true,"url":"https://api.github.com/repos/nsuan/furry-fortnight/commits/aac0aac6b6f864eeaa718440f1e27a6a94c95dea"},{"sha":"42cf3a6e89ef5891997a6758f09ebbc4297426c1","author":{"name":"Nick Suan","email":"cf0bc5f1c1ddb0fada692904e0e398c835913fed@nonexiste.net"},"message":"update userinfo Wed Jul 21 01:48:53 EDT 2021","distinct":true,"url":"https://api.github.com/repos/nsuan/furry-fortnight/commits/42cf3a6e89ef5891997a6758f09ebbc4297426c1"}]},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208198","type":"PushEvent","actor":{"id":82876799,"login":"hvaish01","display_login":"hvaish01","gravatar_id":"","url":"https://api.github.com/users/hvaish01","avatar_url":"https://avatars.githubusercontent.com/u/82876799?"},"repo":{"id":359772248,"name":"hvaish01/StixBundles","url":"https://api.github.com/repos/hvaish01/StixBundles"},"payload":{"push_id":7561415238,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"9706084c932ef2b39a19ff7002d538d0629f8504","before":"c80f87169380d203827443d9c611f4638fa1f741","commits":[{"sha":"9706084c932ef2b39a19ff7002d538d0629f8504","author":{"name":"hvaish01","email":"2afd248fb67ac19cdeb0148e32ca7ee71c30c76d@users.noreply.github.com"},"message":"Updated at 11:00:03 20-07-2021","distinct":true,"url":"https://api.github.com/repos/hvaish01/StixBundles/commits/9706084c932ef2b39a19ff7002d538d0629f8504"}]},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208199","type":"PushEvent","actor":{"id":60849980,"login":"jordans-bot","display_login":"jordans-bot","gravatar_id":"","url":"https://api.github.com/users/jordans-bot","avatar_url":"https://avatars.githubusercontent.com/u/60849980?"},"repo":{"id":139190305,"name":"JTuwiner/bitcoinclock","url":"https://api.github.com/repos/JTuwiner/bitcoinclock"},"payload":{"push_id":7561415225,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"a7d5f669fd71ebd564eb9934aff62af1bb1cae7f","before":"36d4ef46fef03b4ca626aa20d5053be93b3eef3b","commits":[{"sha":"a7d5f669fd71ebd564eb9934aff62af1bb1cae7f","author":{"name":"jordans-bot","email":"6323b4aabf9938a958e69e4471281fb61992bb8f@users.noreply.github.com"},"message":"Updated block height","distinct":true,"url":"https://api.github.com/repos/JTuwiner/bitcoinclock/commits/a7d5f669fd71ebd564eb9934aff62af1bb1cae7f"}]},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208204","type":"WatchEvent","actor":{"id":8991635,"login":"wangchongya","display_login":"wangchongya","gravatar_id":"","url":"https://api.github.com/users/wangchongya","avatar_url":"https://avatars.githubusercontent.com/u/8991635?"},"repo":{"id":172491011,"name":"baidu-security/openrasp-v8","url":"https://api.github.com/repos/baidu-security/openrasp-v8"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":24645557,"login":"baidu-security","gravatar_id":"","url":"https://api.github.com/orgs/baidu-security","avatar_url":"https://avatars.githubusercontent.com/u/24645557?"}} +{"id":"17244208211","type":"WatchEvent","actor":{"id":73470270,"login":"outscale-hai","display_login":"outscale-hai","gravatar_id":"","url":"https://api.github.com/users/outscale-hai","avatar_url":"https://avatars.githubusercontent.com/u/73470270?"},"repo":{"id":165101310,"name":"outscale/osc-cli","url":"https://api.github.com/repos/outscale/osc-cli"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":8233735,"login":"outscale","gravatar_id":"","url":"https://api.github.com/orgs/outscale","avatar_url":"https://avatars.githubusercontent.com/u/8233735?"}} +{"id":"17244208220","type":"PushEvent","actor":{"id":1614429,"login":"skabashnyuk","display_login":"skabashnyuk","gravatar_id":"","url":"https://api.github.com/users/skabashnyuk","avatar_url":"https://avatars.githubusercontent.com/u/1614429?"},"repo":{"id":281644786,"name":"skabashnyuk/che-docs","url":"https://api.github.com/repos/skabashnyuk/che-docs"},"payload":{"push_id":7561415243,"size":1,"distinct_size":1,"ref":"refs/heads/che19944","head":"7b8847be43549340a3b1b0d346acd07e0c383759","before":"ad342c2f1cf67b623d39fad43f83c5e6dc9848d5","commits":[{"sha":"7b8847be43549340a3b1b0d346acd07e0c383759","author":{"name":"Sergii Kabashniuk","email":"29c0f357da1539d701cf38481baf17db5d000cda@redhat.com"},"message":"fixup! Enable Devworkspaces on k8s\n\nSigned-off-by: Sergii Kabashniuk ","distinct":true,"url":"https://api.github.com/repos/skabashnyuk/che-docs/commits/7b8847be43549340a3b1b0d346acd07e0c383759"}]},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208237","type":"PullRequestEvent","actor":{"id":38143731,"login":"joonvena","display_login":"joonvena","gravatar_id":"","url":"https://api.github.com/users/joonvena","avatar_url":"https://avatars.githubusercontent.com/u/38143731?"},"repo":{"id":87910985,"name":"City-of-Helsinki/mvj","url":"https://api.github.com/repos/City-of-Helsinki/mvj"},"payload":{"action":"closed","number":268,"pull_request":{"url":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/268","id":692576288,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyNTc2Mjg4","html_url":"https://github.com/City-of-Helsinki/mvj/pull/268","diff_url":"https://github.com/City-of-Helsinki/mvj/pull/268.diff","patch_url":"https://github.com/City-of-Helsinki/mvj/pull/268.patch","issue_url":"https://api.github.com/repos/City-of-Helsinki/mvj/issues/268","number":268,"state":"closed","locked":false,"title":"Fix invoicing preview error","user":{"login":"joonvena","id":38143731,"node_id":"MDQ6VXNlcjM4MTQzNzMx","avatar_url":"https://avatars.githubusercontent.com/u/38143731?v=4","gravatar_id":"","url":"https://api.github.com/users/joonvena","html_url":"https://github.com/joonvena","followers_url":"https://api.github.com/users/joonvena/followers","following_url":"https://api.github.com/users/joonvena/following{/other_user}","gists_url":"https://api.github.com/users/joonvena/gists{/gist_id}","starred_url":"https://api.github.com/users/joonvena/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joonvena/subscriptions","organizations_url":"https://api.github.com/users/joonvena/orgs","repos_url":"https://api.github.com/users/joonvena/repos","events_url":"https://api.github.com/users/joonvena/events{/privacy}","received_events_url":"https://api.github.com/users/joonvena/received_events","type":"User","site_admin":false},"body":"When tenant is used as billing contact on another tenant and vice versa shares will have duplicate tenant keys which leads to KeyError. This will fix the issue.","created_at":"2021-07-19T12:49:05Z","updated_at":"2021-07-21T06:00:06Z","closed_at":"2021-07-21T06:00:05Z","merged_at":"2021-07-21T06:00:05Z","merge_commit_sha":"24983699432dccb432683cd272f83a40b8399c0e","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/268/commits","review_comments_url":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/268/comments","review_comment_url":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/comments{/number}","comments_url":"https://api.github.com/repos/City-of-Helsinki/mvj/issues/268/comments","statuses_url":"https://api.github.com/repos/City-of-Helsinki/mvj/statuses/909d699e3c50c3934e287c9165f2254baf183524","head":{"label":"joonvena:fix/billing-contacts","ref":"fix/billing-contacts","sha":"909d699e3c50c3934e287c9165f2254baf183524","user":{"login":"joonvena","id":38143731,"node_id":"MDQ6VXNlcjM4MTQzNzMx","avatar_url":"https://avatars.githubusercontent.com/u/38143731?v=4","gravatar_id":"","url":"https://api.github.com/users/joonvena","html_url":"https://github.com/joonvena","followers_url":"https://api.github.com/users/joonvena/followers","following_url":"https://api.github.com/users/joonvena/following{/other_user}","gists_url":"https://api.github.com/users/joonvena/gists{/gist_id}","starred_url":"https://api.github.com/users/joonvena/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joonvena/subscriptions","organizations_url":"https://api.github.com/users/joonvena/orgs","repos_url":"https://api.github.com/users/joonvena/repos","events_url":"https://api.github.com/users/joonvena/events{/privacy}","received_events_url":"https://api.github.com/users/joonvena/received_events","type":"User","site_admin":false},"repo":{"id":360854370,"node_id":"MDEwOlJlcG9zaXRvcnkzNjA4NTQzNzA=","name":"mvj","full_name":"joonvena/mvj","private":false,"owner":{"login":"joonvena","id":38143731,"node_id":"MDQ6VXNlcjM4MTQzNzMx","avatar_url":"https://avatars.githubusercontent.com/u/38143731?v=4","gravatar_id":"","url":"https://api.github.com/users/joonvena","html_url":"https://github.com/joonvena","followers_url":"https://api.github.com/users/joonvena/followers","following_url":"https://api.github.com/users/joonvena/following{/other_user}","gists_url":"https://api.github.com/users/joonvena/gists{/gist_id}","starred_url":"https://api.github.com/users/joonvena/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joonvena/subscriptions","organizations_url":"https://api.github.com/users/joonvena/orgs","repos_url":"https://api.github.com/users/joonvena/repos","events_url":"https://api.github.com/users/joonvena/events{/privacy}","received_events_url":"https://api.github.com/users/joonvena/received_events","type":"User","site_admin":false},"html_url":"https://github.com/joonvena/mvj","description":"City of Helsinki ground rent system","fork":true,"url":"https://api.github.com/repos/joonvena/mvj","forks_url":"https://api.github.com/repos/joonvena/mvj/forks","keys_url":"https://api.github.com/repos/joonvena/mvj/keys{/key_id}","collaborators_url":"https://api.github.com/repos/joonvena/mvj/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/joonvena/mvj/teams","hooks_url":"https://api.github.com/repos/joonvena/mvj/hooks","issue_events_url":"https://api.github.com/repos/joonvena/mvj/issues/events{/number}","events_url":"https://api.github.com/repos/joonvena/mvj/events","assignees_url":"https://api.github.com/repos/joonvena/mvj/assignees{/user}","branches_url":"https://api.github.com/repos/joonvena/mvj/branches{/branch}","tags_url":"https://api.github.com/repos/joonvena/mvj/tags","blobs_url":"https://api.github.com/repos/joonvena/mvj/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/joonvena/mvj/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/joonvena/mvj/git/refs{/sha}","trees_url":"https://api.github.com/repos/joonvena/mvj/git/trees{/sha}","statuses_url":"https://api.github.com/repos/joonvena/mvj/statuses/{sha}","languages_url":"https://api.github.com/repos/joonvena/mvj/languages","stargazers_url":"https://api.github.com/repos/joonvena/mvj/stargazers","contributors_url":"https://api.github.com/repos/joonvena/mvj/contributors","subscribers_url":"https://api.github.com/repos/joonvena/mvj/subscribers","subscription_url":"https://api.github.com/repos/joonvena/mvj/subscription","commits_url":"https://api.github.com/repos/joonvena/mvj/commits{/sha}","git_commits_url":"https://api.github.com/repos/joonvena/mvj/git/commits{/sha}","comments_url":"https://api.github.com/repos/joonvena/mvj/comments{/number}","issue_comment_url":"https://api.github.com/repos/joonvena/mvj/issues/comments{/number}","contents_url":"https://api.github.com/repos/joonvena/mvj/contents/{+path}","compare_url":"https://api.github.com/repos/joonvena/mvj/compare/{base}...{head}","merges_url":"https://api.github.com/repos/joonvena/mvj/merges","archive_url":"https://api.github.com/repos/joonvena/mvj/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/joonvena/mvj/downloads","issues_url":"https://api.github.com/repos/joonvena/mvj/issues{/number}","pulls_url":"https://api.github.com/repos/joonvena/mvj/pulls{/number}","milestones_url":"https://api.github.com/repos/joonvena/mvj/milestones{/number}","notifications_url":"https://api.github.com/repos/joonvena/mvj/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/joonvena/mvj/labels{/name}","releases_url":"https://api.github.com/repos/joonvena/mvj/releases{/id}","deployments_url":"https://api.github.com/repos/joonvena/mvj/deployments","created_at":"2021-04-23T10:59:31Z","updated_at":"2021-05-20T12:40:39Z","pushed_at":"2021-07-19T12:35:24Z","git_url":"git://github.com/joonvena/mvj.git","ssh_url":"git@github.com:joonvena/mvj.git","clone_url":"https://github.com/joonvena/mvj.git","svn_url":"https://github.com/joonvena/mvj","homepage":null,"size":2273,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":2,"watchers":0,"default_branch":"develop"}},"base":{"label":"City-of-Helsinki:develop","ref":"develop","sha":"2191a6e23067f8e7fdda2bcbfe5e80a5dd749abc","user":{"login":"City-of-Helsinki","id":1875564,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE4NzU1NjQ=","avatar_url":"https://avatars.githubusercontent.com/u/1875564?v=4","gravatar_id":"","url":"https://api.github.com/users/City-of-Helsinki","html_url":"https://github.com/City-of-Helsinki","followers_url":"https://api.github.com/users/City-of-Helsinki/followers","following_url":"https://api.github.com/users/City-of-Helsinki/following{/other_user}","gists_url":"https://api.github.com/users/City-of-Helsinki/gists{/gist_id}","starred_url":"https://api.github.com/users/City-of-Helsinki/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/City-of-Helsinki/subscriptions","organizations_url":"https://api.github.com/users/City-of-Helsinki/orgs","repos_url":"https://api.github.com/users/City-of-Helsinki/repos","events_url":"https://api.github.com/users/City-of-Helsinki/events{/privacy}","received_events_url":"https://api.github.com/users/City-of-Helsinki/received_events","type":"Organization","site_admin":false},"repo":{"id":87910985,"node_id":"MDEwOlJlcG9zaXRvcnk4NzkxMDk4NQ==","name":"mvj","full_name":"City-of-Helsinki/mvj","private":false,"owner":{"login":"City-of-Helsinki","id":1875564,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE4NzU1NjQ=","avatar_url":"https://avatars.githubusercontent.com/u/1875564?v=4","gravatar_id":"","url":"https://api.github.com/users/City-of-Helsinki","html_url":"https://github.com/City-of-Helsinki","followers_url":"https://api.github.com/users/City-of-Helsinki/followers","following_url":"https://api.github.com/users/City-of-Helsinki/following{/other_user}","gists_url":"https://api.github.com/users/City-of-Helsinki/gists{/gist_id}","starred_url":"https://api.github.com/users/City-of-Helsinki/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/City-of-Helsinki/subscriptions","organizations_url":"https://api.github.com/users/City-of-Helsinki/orgs","repos_url":"https://api.github.com/users/City-of-Helsinki/repos","events_url":"https://api.github.com/users/City-of-Helsinki/events{/privacy}","received_events_url":"https://api.github.com/users/City-of-Helsinki/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/City-of-Helsinki/mvj","description":"City of Helsinki ground rent system","fork":false,"url":"https://api.github.com/repos/City-of-Helsinki/mvj","forks_url":"https://api.github.com/repos/City-of-Helsinki/mvj/forks","keys_url":"https://api.github.com/repos/City-of-Helsinki/mvj/keys{/key_id}","collaborators_url":"https://api.github.com/repos/City-of-Helsinki/mvj/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/City-of-Helsinki/mvj/teams","hooks_url":"https://api.github.com/repos/City-of-Helsinki/mvj/hooks","issue_events_url":"https://api.github.com/repos/City-of-Helsinki/mvj/issues/events{/number}","events_url":"https://api.github.com/repos/City-of-Helsinki/mvj/events","assignees_url":"https://api.github.com/repos/City-of-Helsinki/mvj/assignees{/user}","branches_url":"https://api.github.com/repos/City-of-Helsinki/mvj/branches{/branch}","tags_url":"https://api.github.com/repos/City-of-Helsinki/mvj/tags","blobs_url":"https://api.github.com/repos/City-of-Helsinki/mvj/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/City-of-Helsinki/mvj/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/City-of-Helsinki/mvj/git/refs{/sha}","trees_url":"https://api.github.com/repos/City-of-Helsinki/mvj/git/trees{/sha}","statuses_url":"https://api.github.com/repos/City-of-Helsinki/mvj/statuses/{sha}","languages_url":"https://api.github.com/repos/City-of-Helsinki/mvj/languages","stargazers_url":"https://api.github.com/repos/City-of-Helsinki/mvj/stargazers","contributors_url":"https://api.github.com/repos/City-of-Helsinki/mvj/contributors","subscribers_url":"https://api.github.com/repos/City-of-Helsinki/mvj/subscribers","subscription_url":"https://api.github.com/repos/City-of-Helsinki/mvj/subscription","commits_url":"https://api.github.com/repos/City-of-Helsinki/mvj/commits{/sha}","git_commits_url":"https://api.github.com/repos/City-of-Helsinki/mvj/git/commits{/sha}","comments_url":"https://api.github.com/repos/City-of-Helsinki/mvj/comments{/number}","issue_comment_url":"https://api.github.com/repos/City-of-Helsinki/mvj/issues/comments{/number}","contents_url":"https://api.github.com/repos/City-of-Helsinki/mvj/contents/{+path}","compare_url":"https://api.github.com/repos/City-of-Helsinki/mvj/compare/{base}...{head}","merges_url":"https://api.github.com/repos/City-of-Helsinki/mvj/merges","archive_url":"https://api.github.com/repos/City-of-Helsinki/mvj/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/City-of-Helsinki/mvj/downloads","issues_url":"https://api.github.com/repos/City-of-Helsinki/mvj/issues{/number}","pulls_url":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls{/number}","milestones_url":"https://api.github.com/repos/City-of-Helsinki/mvj/milestones{/number}","notifications_url":"https://api.github.com/repos/City-of-Helsinki/mvj/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/City-of-Helsinki/mvj/labels{/name}","releases_url":"https://api.github.com/repos/City-of-Helsinki/mvj/releases{/id}","deployments_url":"https://api.github.com/repos/City-of-Helsinki/mvj/deployments","created_at":"2017-04-11T08:43:59Z","updated_at":"2021-05-20T08:56:44Z","pushed_at":"2021-07-21T06:00:05Z","git_url":"git://github.com/City-of-Helsinki/mvj.git","ssh_url":"git@github.com:City-of-Helsinki/mvj.git","clone_url":"https://github.com/City-of-Helsinki/mvj.git","svn_url":"https://github.com/City-of-Helsinki/mvj","homepage":null,"size":2263,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":6,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":14,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":6,"open_issues":14,"watchers":1,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/268"},"html":{"href":"https://github.com/City-of-Helsinki/mvj/pull/268"},"issue":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/issues/268"},"comments":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/issues/268/comments"},"review_comments":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/268/comments"},"review_comment":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/pulls/268/commits"},"statuses":{"href":"https://api.github.com/repos/City-of-Helsinki/mvj/statuses/909d699e3c50c3934e287c9165f2254baf183524"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"joonvena","id":38143731,"node_id":"MDQ6VXNlcjM4MTQzNzMx","avatar_url":"https://avatars.githubusercontent.com/u/38143731?v=4","gravatar_id":"","url":"https://api.github.com/users/joonvena","html_url":"https://github.com/joonvena","followers_url":"https://api.github.com/users/joonvena/followers","following_url":"https://api.github.com/users/joonvena/following{/other_user}","gists_url":"https://api.github.com/users/joonvena/gists{/gist_id}","starred_url":"https://api.github.com/users/joonvena/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joonvena/subscriptions","organizations_url":"https://api.github.com/users/joonvena/orgs","repos_url":"https://api.github.com/users/joonvena/repos","events_url":"https://api.github.com/users/joonvena/events{/privacy}","received_events_url":"https://api.github.com/users/joonvena/received_events","type":"User","site_admin":false},"comments":1,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":6,"deletions":3,"changed_files":1}},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":1875564,"login":"City-of-Helsinki","gravatar_id":"","url":"https://api.github.com/orgs/City-of-Helsinki","avatar_url":"https://avatars.githubusercontent.com/u/1875564?"}} +{"id":"17244208238","type":"IssuesEvent","actor":{"id":24877853,"login":"Itherma","display_login":"Itherma","gravatar_id":"","url":"https://api.github.com/users/Itherma","avatar_url":"https://avatars.githubusercontent.com/u/24877853?"},"repo":{"id":379208352,"name":"Itherma/blog-gitalk-comment","url":"https://api.github.com/repos/Itherma/blog-gitalk-comment"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/issues/15","repository_url":"https://api.github.com/repos/Itherma/blog-gitalk-comment","labels_url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/issues/15/labels{/name}","comments_url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/issues/15/comments","events_url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/issues/15/events","html_url":"https://github.com/Itherma/blog-gitalk-comment/issues/15","id":949355535,"node_id":"MDU6SXNzdWU5NDkzNTU1MzU=","number":15,"title":"「评论」Git分支的新建与合并-分支操作","user":{"login":"Itherma","id":24877853,"node_id":"MDQ6VXNlcjI0ODc3ODUz","avatar_url":"https://avatars.githubusercontent.com/u/24877853?v=4","gravatar_id":"","url":"https://api.github.com/users/Itherma","html_url":"https://github.com/Itherma","followers_url":"https://api.github.com/users/Itherma/followers","following_url":"https://api.github.com/users/Itherma/following{/other_user}","gists_url":"https://api.github.com/users/Itherma/gists{/gist_id}","starred_url":"https://api.github.com/users/Itherma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Itherma/subscriptions","organizations_url":"https://api.github.com/users/Itherma/orgs","repos_url":"https://api.github.com/users/Itherma/repos","events_url":"https://api.github.com/users/Itherma/events{/privacy}","received_events_url":"https://api.github.com/users/Itherma/received_events","type":"User","site_admin":false},"labels":[{"id":3186781096,"node_id":"MDU6TGFiZWwzMTg2NzgxMDk2","url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/labels//pages/ea5a8c/","name":"/pages/ea5a8c/","color":"ededed","default":false,"description":null},{"id":3107104037,"node_id":"MDU6TGFiZWwzMTA3MTA0MDM3","url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/labels/Comment","name":"Comment","color":"ededed","default":false,"description":null},{"id":3107104036,"node_id":"MDU6TGFiZWwzMTA3MTA0MDM2","url":"https://api.github.com/repos/Itherma/blog-gitalk-comment/labels/Gitalk","name":"Gitalk","color":"ededed","default":false,"description":null}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T06:00:06Z","updated_at":"2021-07-21T06:00:06Z","closed_at":null,"author_association":"OWNER","active_lock_reason":null,"body":"页面:https://itherma.github.io/pages/ea5a8c/","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208242","type":"PullRequestEvent","actor":{"id":11026689,"login":"niranjanshk27","display_login":"niranjanshk27","gravatar_id":"","url":"https://api.github.com/users/niranjanshk27","avatar_url":"https://avatars.githubusercontent.com/u/11026689?"},"repo":{"id":313846192,"name":"Bhoos/react-kit","url":"https://api.github.com/repos/Bhoos/react-kit"},"payload":{"action":"opened","number":20,"pull_request":{"url":"https://api.github.com/repos/Bhoos/react-kit/pulls/20","id":694073985,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDczOTg1","html_url":"https://github.com/Bhoos/react-kit/pull/20","diff_url":"https://github.com/Bhoos/react-kit/pull/20.diff","patch_url":"https://github.com/Bhoos/react-kit/pull/20.patch","issue_url":"https://api.github.com/repos/Bhoos/react-kit/issues/20","number":20,"state":"open","locked":false,"title":"Use of GITHUB_TOKEN to publish","user":{"login":"niranjanshk27","id":11026689,"node_id":"MDQ6VXNlcjExMDI2Njg5","avatar_url":"https://avatars.githubusercontent.com/u/11026689?v=4","gravatar_id":"","url":"https://api.github.com/users/niranjanshk27","html_url":"https://github.com/niranjanshk27","followers_url":"https://api.github.com/users/niranjanshk27/followers","following_url":"https://api.github.com/users/niranjanshk27/following{/other_user}","gists_url":"https://api.github.com/users/niranjanshk27/gists{/gist_id}","starred_url":"https://api.github.com/users/niranjanshk27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/niranjanshk27/subscriptions","organizations_url":"https://api.github.com/users/niranjanshk27/orgs","repos_url":"https://api.github.com/users/niranjanshk27/repos","events_url":"https://api.github.com/users/niranjanshk27/events{/privacy}","received_events_url":"https://api.github.com/users/niranjanshk27/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T06:00:06Z","updated_at":"2021-07-21T06:00:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Bhoos/react-kit/pulls/20/commits","review_comments_url":"https://api.github.com/repos/Bhoos/react-kit/pulls/20/comments","review_comment_url":"https://api.github.com/repos/Bhoos/react-kit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Bhoos/react-kit/issues/20/comments","statuses_url":"https://api.github.com/repos/Bhoos/react-kit/statuses/d6f69e58602534070ef49d1f9044e285ec9251fa","head":{"label":"niranjanshk27:master","ref":"master","sha":"d6f69e58602534070ef49d1f9044e285ec9251fa","user":{"login":"niranjanshk27","id":11026689,"node_id":"MDQ6VXNlcjExMDI2Njg5","avatar_url":"https://avatars.githubusercontent.com/u/11026689?v=4","gravatar_id":"","url":"https://api.github.com/users/niranjanshk27","html_url":"https://github.com/niranjanshk27","followers_url":"https://api.github.com/users/niranjanshk27/followers","following_url":"https://api.github.com/users/niranjanshk27/following{/other_user}","gists_url":"https://api.github.com/users/niranjanshk27/gists{/gist_id}","starred_url":"https://api.github.com/users/niranjanshk27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/niranjanshk27/subscriptions","organizations_url":"https://api.github.com/users/niranjanshk27/orgs","repos_url":"https://api.github.com/users/niranjanshk27/repos","events_url":"https://api.github.com/users/niranjanshk27/events{/privacy}","received_events_url":"https://api.github.com/users/niranjanshk27/received_events","type":"User","site_admin":false},"repo":{"id":385220332,"node_id":"MDEwOlJlcG9zaXRvcnkzODUyMjAzMzI=","name":"react-kit","full_name":"niranjanshk27/react-kit","private":false,"owner":{"login":"niranjanshk27","id":11026689,"node_id":"MDQ6VXNlcjExMDI2Njg5","avatar_url":"https://avatars.githubusercontent.com/u/11026689?v=4","gravatar_id":"","url":"https://api.github.com/users/niranjanshk27","html_url":"https://github.com/niranjanshk27","followers_url":"https://api.github.com/users/niranjanshk27/followers","following_url":"https://api.github.com/users/niranjanshk27/following{/other_user}","gists_url":"https://api.github.com/users/niranjanshk27/gists{/gist_id}","starred_url":"https://api.github.com/users/niranjanshk27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/niranjanshk27/subscriptions","organizations_url":"https://api.github.com/users/niranjanshk27/orgs","repos_url":"https://api.github.com/users/niranjanshk27/repos","events_url":"https://api.github.com/users/niranjanshk27/events{/privacy}","received_events_url":"https://api.github.com/users/niranjanshk27/received_events","type":"User","site_admin":false},"html_url":"https://github.com/niranjanshk27/react-kit","description":"Bhoos React Library","fork":true,"url":"https://api.github.com/repos/niranjanshk27/react-kit","forks_url":"https://api.github.com/repos/niranjanshk27/react-kit/forks","keys_url":"https://api.github.com/repos/niranjanshk27/react-kit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/niranjanshk27/react-kit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/niranjanshk27/react-kit/teams","hooks_url":"https://api.github.com/repos/niranjanshk27/react-kit/hooks","issue_events_url":"https://api.github.com/repos/niranjanshk27/react-kit/issues/events{/number}","events_url":"https://api.github.com/repos/niranjanshk27/react-kit/events","assignees_url":"https://api.github.com/repos/niranjanshk27/react-kit/assignees{/user}","branches_url":"https://api.github.com/repos/niranjanshk27/react-kit/branches{/branch}","tags_url":"https://api.github.com/repos/niranjanshk27/react-kit/tags","blobs_url":"https://api.github.com/repos/niranjanshk27/react-kit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/niranjanshk27/react-kit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/niranjanshk27/react-kit/git/refs{/sha}","trees_url":"https://api.github.com/repos/niranjanshk27/react-kit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/niranjanshk27/react-kit/statuses/{sha}","languages_url":"https://api.github.com/repos/niranjanshk27/react-kit/languages","stargazers_url":"https://api.github.com/repos/niranjanshk27/react-kit/stargazers","contributors_url":"https://api.github.com/repos/niranjanshk27/react-kit/contributors","subscribers_url":"https://api.github.com/repos/niranjanshk27/react-kit/subscribers","subscription_url":"https://api.github.com/repos/niranjanshk27/react-kit/subscription","commits_url":"https://api.github.com/repos/niranjanshk27/react-kit/commits{/sha}","git_commits_url":"https://api.github.com/repos/niranjanshk27/react-kit/git/commits{/sha}","comments_url":"https://api.github.com/repos/niranjanshk27/react-kit/comments{/number}","issue_comment_url":"https://api.github.com/repos/niranjanshk27/react-kit/issues/comments{/number}","contents_url":"https://api.github.com/repos/niranjanshk27/react-kit/contents/{+path}","compare_url":"https://api.github.com/repos/niranjanshk27/react-kit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/niranjanshk27/react-kit/merges","archive_url":"https://api.github.com/repos/niranjanshk27/react-kit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/niranjanshk27/react-kit/downloads","issues_url":"https://api.github.com/repos/niranjanshk27/react-kit/issues{/number}","pulls_url":"https://api.github.com/repos/niranjanshk27/react-kit/pulls{/number}","milestones_url":"https://api.github.com/repos/niranjanshk27/react-kit/milestones{/number}","notifications_url":"https://api.github.com/repos/niranjanshk27/react-kit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/niranjanshk27/react-kit/labels{/name}","releases_url":"https://api.github.com/repos/niranjanshk27/react-kit/releases{/id}","deployments_url":"https://api.github.com/repos/niranjanshk27/react-kit/deployments","created_at":"2021-07-12T11:14:04Z","updated_at":"2021-07-21T05:59:34Z","pushed_at":"2021-07-21T05:59:32Z","git_url":"git://github.com/niranjanshk27/react-kit.git","ssh_url":"git@github.com:niranjanshk27/react-kit.git","clone_url":"https://github.com/niranjanshk27/react-kit.git","svn_url":"https://github.com/niranjanshk27/react-kit","homepage":null,"size":78,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Bhoos:master","ref":"master","sha":"05deaa7b687fd932d79bf828535c77b7c41419ea","user":{"login":"Bhoos","id":24890692,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0ODkwNjky","avatar_url":"https://avatars.githubusercontent.com/u/24890692?v=4","gravatar_id":"","url":"https://api.github.com/users/Bhoos","html_url":"https://github.com/Bhoos","followers_url":"https://api.github.com/users/Bhoos/followers","following_url":"https://api.github.com/users/Bhoos/following{/other_user}","gists_url":"https://api.github.com/users/Bhoos/gists{/gist_id}","starred_url":"https://api.github.com/users/Bhoos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bhoos/subscriptions","organizations_url":"https://api.github.com/users/Bhoos/orgs","repos_url":"https://api.github.com/users/Bhoos/repos","events_url":"https://api.github.com/users/Bhoos/events{/privacy}","received_events_url":"https://api.github.com/users/Bhoos/received_events","type":"Organization","site_admin":false},"repo":{"id":313846192,"node_id":"MDEwOlJlcG9zaXRvcnkzMTM4NDYxOTI=","name":"react-kit","full_name":"Bhoos/react-kit","private":false,"owner":{"login":"Bhoos","id":24890692,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0ODkwNjky","avatar_url":"https://avatars.githubusercontent.com/u/24890692?v=4","gravatar_id":"","url":"https://api.github.com/users/Bhoos","html_url":"https://github.com/Bhoos","followers_url":"https://api.github.com/users/Bhoos/followers","following_url":"https://api.github.com/users/Bhoos/following{/other_user}","gists_url":"https://api.github.com/users/Bhoos/gists{/gist_id}","starred_url":"https://api.github.com/users/Bhoos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bhoos/subscriptions","organizations_url":"https://api.github.com/users/Bhoos/orgs","repos_url":"https://api.github.com/users/Bhoos/repos","events_url":"https://api.github.com/users/Bhoos/events{/privacy}","received_events_url":"https://api.github.com/users/Bhoos/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Bhoos/react-kit","description":"Bhoos React Library","fork":false,"url":"https://api.github.com/repos/Bhoos/react-kit","forks_url":"https://api.github.com/repos/Bhoos/react-kit/forks","keys_url":"https://api.github.com/repos/Bhoos/react-kit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Bhoos/react-kit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Bhoos/react-kit/teams","hooks_url":"https://api.github.com/repos/Bhoos/react-kit/hooks","issue_events_url":"https://api.github.com/repos/Bhoos/react-kit/issues/events{/number}","events_url":"https://api.github.com/repos/Bhoos/react-kit/events","assignees_url":"https://api.github.com/repos/Bhoos/react-kit/assignees{/user}","branches_url":"https://api.github.com/repos/Bhoos/react-kit/branches{/branch}","tags_url":"https://api.github.com/repos/Bhoos/react-kit/tags","blobs_url":"https://api.github.com/repos/Bhoos/react-kit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Bhoos/react-kit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Bhoos/react-kit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Bhoos/react-kit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Bhoos/react-kit/statuses/{sha}","languages_url":"https://api.github.com/repos/Bhoos/react-kit/languages","stargazers_url":"https://api.github.com/repos/Bhoos/react-kit/stargazers","contributors_url":"https://api.github.com/repos/Bhoos/react-kit/contributors","subscribers_url":"https://api.github.com/repos/Bhoos/react-kit/subscribers","subscription_url":"https://api.github.com/repos/Bhoos/react-kit/subscription","commits_url":"https://api.github.com/repos/Bhoos/react-kit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Bhoos/react-kit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Bhoos/react-kit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Bhoos/react-kit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Bhoos/react-kit/contents/{+path}","compare_url":"https://api.github.com/repos/Bhoos/react-kit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Bhoos/react-kit/merges","archive_url":"https://api.github.com/repos/Bhoos/react-kit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Bhoos/react-kit/downloads","issues_url":"https://api.github.com/repos/Bhoos/react-kit/issues{/number}","pulls_url":"https://api.github.com/repos/Bhoos/react-kit/pulls{/number}","milestones_url":"https://api.github.com/repos/Bhoos/react-kit/milestones{/number}","notifications_url":"https://api.github.com/repos/Bhoos/react-kit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Bhoos/react-kit/labels{/name}","releases_url":"https://api.github.com/repos/Bhoos/react-kit/releases{/id}","deployments_url":"https://api.github.com/repos/Bhoos/react-kit/deployments","created_at":"2020-11-18T06:41:31Z","updated_at":"2021-07-20T07:54:59Z","pushed_at":"2021-07-20T07:54:59Z","git_url":"git://github.com/Bhoos/react-kit.git","ssh_url":"git@github.com:Bhoos/react-kit.git","clone_url":"https://github.com/Bhoos/react-kit.git","svn_url":"https://github.com/Bhoos/react-kit","homepage":null,"size":79,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":null,"forks":6,"open_issues":4,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Bhoos/react-kit/pulls/20"},"html":{"href":"https://github.com/Bhoos/react-kit/pull/20"},"issue":{"href":"https://api.github.com/repos/Bhoos/react-kit/issues/20"},"comments":{"href":"https://api.github.com/repos/Bhoos/react-kit/issues/20/comments"},"review_comments":{"href":"https://api.github.com/repos/Bhoos/react-kit/pulls/20/comments"},"review_comment":{"href":"https://api.github.com/repos/Bhoos/react-kit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Bhoos/react-kit/pulls/20/commits"},"statuses":{"href":"https://api.github.com/repos/Bhoos/react-kit/statuses/d6f69e58602534070ef49d1f9044e285ec9251fa"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":true,"commits":1,"additions":2,"deletions":2,"changed_files":2}},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":24890692,"login":"Bhoos","gravatar_id":"","url":"https://api.github.com/orgs/Bhoos","avatar_url":"https://avatars.githubusercontent.com/u/24890692?"}} +{"id":"17244208243","type":"PushEvent","actor":{"id":6334546,"login":"reynaldoeg","display_login":"reynaldoeg","gravatar_id":"","url":"https://api.github.com/users/reynaldoeg","avatar_url":"https://avatars.githubusercontent.com/u/6334546?"},"repo":{"id":165695619,"name":"reynaldoeg/cron-bash","url":"https://api.github.com/repos/reynaldoeg/cron-bash"},"payload":{"push_id":7561415265,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"d3e7a695f4523c1fdc5a61e82718593fc5cf011a","before":"a9fe861554f8e4754f9e9777001ed1b0b5435ab8","commits":[{"sha":"d3e7a695f4523c1fdc5a61e82718593fc5cf011a","author":{"name":"Reynaldo Esparza","email":"95b30e9cdfc69ddd5127248f1edf158cc384728d@hotmail.com"},"message":"2021-07-21 06:00:01","distinct":true,"url":"https://api.github.com/repos/reynaldoeg/cron-bash/commits/d3e7a695f4523c1fdc5a61e82718593fc5cf011a"}]},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208244","type":"CreateEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":206050670,"name":"krynv/feature-rich-graphql-express-server","url":"https://api.github.com/repos/krynv/feature-rich-graphql-express-server"},"payload":{"ref":"dependabot/npm_and_yarn/babel/cli-7.14.8","ref_type":"branch","master_branch":"master","description":"PostgreSQL connectivity, subscriptions, validation, error handling, user registration, token-based authentication, authorisation, custom scalars, cursor pagination, batching, caching and end-to-end testing","pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208251","type":"PushEvent","actor":{"id":32030603,"login":"assafattas","display_login":"assafattas","gravatar_id":"","url":"https://api.github.com/users/assafattas","avatar_url":"https://avatars.githubusercontent.com/u/32030603?"},"repo":{"id":109857546,"name":"SarineTechnologies/sarine.viewer.clarity","url":"https://api.github.com/repos/SarineTechnologies/sarine.viewer.clarity"},"payload":{"push_id":7561415262,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"554a2819beb0e633187326e0d8da6bbc074c1651","before":"1d0e7a5b3b558ca6d84a4d7d434221fb870e183e","commits":[{"sha":"554a2819beb0e633187326e0d8da6bbc074c1651","author":{"name":"assafattas","email":"833d0ff0ebc34b28e0c774b113d1ed012f0c65dc@users.noreply.github.com"},"message":"rollback to clarityDiamondImageDark for 'halo'","distinct":true,"url":"https://api.github.com/repos/SarineTechnologies/sarine.viewer.clarity/commits/554a2819beb0e633187326e0d8da6bbc074c1651"}]},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":10775474,"login":"SarineTechnologies","gravatar_id":"","url":"https://api.github.com/orgs/SarineTechnologies","avatar_url":"https://avatars.githubusercontent.com/u/10775474?"}} +{"id":"17244208257","type":"PullRequestReviewEvent","actor":{"id":57981365,"login":"sowu880","display_login":"sowu880","gravatar_id":"","url":"https://api.github.com/users/sowu880","avatar_url":"https://avatars.githubusercontent.com/u/57981365?"},"repo":{"id":233114737,"name":"microsoft/FHIR-Tools-for-Anonymization","url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization"},"payload":{"action":"created","review":{"id":711282691,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMjgyNjkx","user":{"login":"sowu880","id":57981365,"node_id":"MDQ6VXNlcjU3OTgxMzY1","avatar_url":"https://avatars.githubusercontent.com/u/57981365?v=4","gravatar_id":"","url":"https://api.github.com/users/sowu880","html_url":"https://github.com/sowu880","followers_url":"https://api.github.com/users/sowu880/followers","following_url":"https://api.github.com/users/sowu880/following{/other_user}","gists_url":"https://api.github.com/users/sowu880/gists{/gist_id}","starred_url":"https://api.github.com/users/sowu880/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sowu880/subscriptions","organizations_url":"https://api.github.com/users/sowu880/orgs","repos_url":"https://api.github.com/users/sowu880/repos","events_url":"https://api.github.com/users/sowu880/events{/privacy}","received_events_url":"https://api.github.com/users/sowu880/received_events","type":"User","site_admin":false},"body":null,"commit_id":"c84cbb40b861c6001a7db6817c1178ea0a7c6f03","submitted_at":"2021-07-21T06:00:06Z","state":"commented","html_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization/pull/115#pullrequestreview-711282691","pull_request_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/microsoft/FHIR-Tools-for-Anonymization/pull/115#pullrequestreview-711282691"},"pull_request":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115"}}},"pull_request":{"url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115","id":683146914,"node_id":"MDExOlB1bGxSZXF1ZXN0NjgzMTQ2OTE0","html_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization/pull/115","diff_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization/pull/115.diff","patch_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization/pull/115.patch","issue_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/115","number":115,"state":"open","locked":false,"title":"Update exception handling","user":{"login":"sowu880","id":57981365,"node_id":"MDQ6VXNlcjU3OTgxMzY1","avatar_url":"https://avatars.githubusercontent.com/u/57981365?v=4","gravatar_id":"","url":"https://api.github.com/users/sowu880","html_url":"https://github.com/sowu880","followers_url":"https://api.github.com/users/sowu880/followers","following_url":"https://api.github.com/users/sowu880/following{/other_user}","gists_url":"https://api.github.com/users/sowu880/gists{/gist_id}","starred_url":"https://api.github.com/users/sowu880/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sowu880/subscriptions","organizations_url":"https://api.github.com/users/sowu880/orgs","repos_url":"https://api.github.com/users/sowu880/repos","events_url":"https://api.github.com/users/sowu880/events{/privacy}","received_events_url":"https://api.github.com/users/sowu880/received_events","type":"User","site_admin":false},"body":"Add two kind of exceptions:\r\n1. AnonymizerRuleNotApplicableException (inherit from AnonymizerConfigurationErrorsException)\r\n2. AnonymizationProcessFailedException (Runtime exception)\r\nWe plan to support skipping the second exception in the future.\r\n\r\nGeneralization methods will throw these two exceptions, while other methods will not throw exceptions for now. \r\n","created_at":"2021-07-04T09:59:44Z","updated_at":"2021-07-21T06:00:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e7d93659ea357d683ece768f03341efb175995a0","assignee":null,"assignees":[],"requested_reviewers":[{"login":"tongwu-msft","id":7133968,"node_id":"MDQ6VXNlcjcxMzM5Njg=","avatar_url":"https://avatars.githubusercontent.com/u/7133968?v=4","gravatar_id":"","url":"https://api.github.com/users/tongwu-msft","html_url":"https://github.com/tongwu-msft","followers_url":"https://api.github.com/users/tongwu-msft/followers","following_url":"https://api.github.com/users/tongwu-msft/following{/other_user}","gists_url":"https://api.github.com/users/tongwu-msft/gists{/gist_id}","starred_url":"https://api.github.com/users/tongwu-msft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tongwu-msft/subscriptions","organizations_url":"https://api.github.com/users/tongwu-msft/orgs","repos_url":"https://api.github.com/users/tongwu-msft/repos","events_url":"https://api.github.com/users/tongwu-msft/events{/privacy}","received_events_url":"https://api.github.com/users/tongwu-msft/received_events","type":"User","site_admin":false},{"login":"moria97","id":59813791,"node_id":"MDQ6VXNlcjU5ODEzNzkx","avatar_url":"https://avatars.githubusercontent.com/u/59813791?v=4","gravatar_id":"","url":"https://api.github.com/users/moria97","html_url":"https://github.com/moria97","followers_url":"https://api.github.com/users/moria97/followers","following_url":"https://api.github.com/users/moria97/following{/other_user}","gists_url":"https://api.github.com/users/moria97/gists{/gist_id}","starred_url":"https://api.github.com/users/moria97/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/moria97/subscriptions","organizations_url":"https://api.github.com/users/moria97/orgs","repos_url":"https://api.github.com/users/moria97/repos","events_url":"https://api.github.com/users/moria97/events{/privacy}","received_events_url":"https://api.github.com/users/moria97/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115/commits","review_comments_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115/comments","review_comment_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/comments{/number}","comments_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/115/comments","statuses_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/statuses/c84cbb40b861c6001a7db6817c1178ea0a7c6f03","head":{"label":"microsoft:personal/sowu/refine-exception-handling","ref":"personal/sowu/refine-exception-handling","sha":"c84cbb40b861c6001a7db6817c1178ea0a7c6f03","user":{"login":"microsoft","id":6154722,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=","avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/microsoft","html_url":"https://github.com/microsoft","followers_url":"https://api.github.com/users/microsoft/followers","following_url":"https://api.github.com/users/microsoft/following{/other_user}","gists_url":"https://api.github.com/users/microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/microsoft/subscriptions","organizations_url":"https://api.github.com/users/microsoft/orgs","repos_url":"https://api.github.com/users/microsoft/repos","events_url":"https://api.github.com/users/microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":233114737,"node_id":"MDEwOlJlcG9zaXRvcnkyMzMxMTQ3Mzc=","name":"FHIR-Tools-for-Anonymization","full_name":"microsoft/FHIR-Tools-for-Anonymization","private":false,"owner":{"login":"microsoft","id":6154722,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=","avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/microsoft","html_url":"https://github.com/microsoft","followers_url":"https://api.github.com/users/microsoft/followers","following_url":"https://api.github.com/users/microsoft/following{/other_user}","gists_url":"https://api.github.com/users/microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/microsoft/subscriptions","organizations_url":"https://api.github.com/users/microsoft/orgs","repos_url":"https://api.github.com/users/microsoft/repos","events_url":"https://api.github.com/users/microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/microsoft/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization","description":"Set of tools for helping with data (in FHIR format) anonymization.","fork":false,"url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization","forks_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/forks","keys_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/keys{/key_id}","collaborators_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/teams","hooks_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/hooks","issue_events_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/events{/number}","events_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/events","assignees_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/assignees{/user}","branches_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/branches{/branch}","tags_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/tags","blobs_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/refs{/sha}","trees_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/trees{/sha}","statuses_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/statuses/{sha}","languages_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/languages","stargazers_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/stargazers","contributors_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/contributors","subscribers_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/subscribers","subscription_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/subscription","commits_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/commits{/sha}","git_commits_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/commits{/sha}","comments_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/comments{/number}","issue_comment_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/comments{/number}","contents_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/contents/{+path}","compare_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/compare/{base}...{head}","merges_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/merges","archive_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/downloads","issues_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues{/number}","pulls_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls{/number}","milestones_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/milestones{/number}","notifications_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/labels{/name}","releases_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/releases{/id}","deployments_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/deployments","created_at":"2020-01-10T19:16:48Z","updated_at":"2021-07-19T05:12:02Z","pushed_at":"2021-07-21T05:01:26Z","git_url":"git://github.com/microsoft/FHIR-Tools-for-Anonymization.git","ssh_url":"git@github.com:microsoft/FHIR-Tools-for-Anonymization.git","clone_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization.git","svn_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization","homepage":null,"size":1574,"stargazers_count":85,"watchers_count":85,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":32,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":32,"open_issues":3,"watchers":85,"default_branch":"master"}},"base":{"label":"microsoft:master","ref":"master","sha":"ceb44383e2e3ebc9d2ee8e1497eda893a56991ca","user":{"login":"microsoft","id":6154722,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=","avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/microsoft","html_url":"https://github.com/microsoft","followers_url":"https://api.github.com/users/microsoft/followers","following_url":"https://api.github.com/users/microsoft/following{/other_user}","gists_url":"https://api.github.com/users/microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/microsoft/subscriptions","organizations_url":"https://api.github.com/users/microsoft/orgs","repos_url":"https://api.github.com/users/microsoft/repos","events_url":"https://api.github.com/users/microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":233114737,"node_id":"MDEwOlJlcG9zaXRvcnkyMzMxMTQ3Mzc=","name":"FHIR-Tools-for-Anonymization","full_name":"microsoft/FHIR-Tools-for-Anonymization","private":false,"owner":{"login":"microsoft","id":6154722,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=","avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/microsoft","html_url":"https://github.com/microsoft","followers_url":"https://api.github.com/users/microsoft/followers","following_url":"https://api.github.com/users/microsoft/following{/other_user}","gists_url":"https://api.github.com/users/microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/microsoft/subscriptions","organizations_url":"https://api.github.com/users/microsoft/orgs","repos_url":"https://api.github.com/users/microsoft/repos","events_url":"https://api.github.com/users/microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/microsoft/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization","description":"Set of tools for helping with data (in FHIR format) anonymization.","fork":false,"url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization","forks_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/forks","keys_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/keys{/key_id}","collaborators_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/teams","hooks_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/hooks","issue_events_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/events{/number}","events_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/events","assignees_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/assignees{/user}","branches_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/branches{/branch}","tags_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/tags","blobs_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/refs{/sha}","trees_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/trees{/sha}","statuses_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/statuses/{sha}","languages_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/languages","stargazers_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/stargazers","contributors_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/contributors","subscribers_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/subscribers","subscription_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/subscription","commits_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/commits{/sha}","git_commits_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/git/commits{/sha}","comments_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/comments{/number}","issue_comment_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/comments{/number}","contents_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/contents/{+path}","compare_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/compare/{base}...{head}","merges_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/merges","archive_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/downloads","issues_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues{/number}","pulls_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls{/number}","milestones_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/milestones{/number}","notifications_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/labels{/name}","releases_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/releases{/id}","deployments_url":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/deployments","created_at":"2020-01-10T19:16:48Z","updated_at":"2021-07-19T05:12:02Z","pushed_at":"2021-07-21T05:01:26Z","git_url":"git://github.com/microsoft/FHIR-Tools-for-Anonymization.git","ssh_url":"git@github.com:microsoft/FHIR-Tools-for-Anonymization.git","clone_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization.git","svn_url":"https://github.com/microsoft/FHIR-Tools-for-Anonymization","homepage":null,"size":1574,"stargazers_count":85,"watchers_count":85,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":32,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":32,"open_issues":3,"watchers":85,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115"},"html":{"href":"https://github.com/microsoft/FHIR-Tools-for-Anonymization/pull/115"},"issue":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/115"},"comments":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/issues/115/comments"},"review_comments":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115/comments"},"review_comment":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/pulls/115/commits"},"statuses":{"href":"https://api.github.com/repos/microsoft/FHIR-Tools-for-Anonymization/statuses/c84cbb40b861c6001a7db6817c1178ea0a7c6f03"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":6154722,"login":"microsoft","gravatar_id":"","url":"https://api.github.com/orgs/microsoft","avatar_url":"https://avatars.githubusercontent.com/u/6154722?"}} +{"id":"17244208258","type":"IssuesEvent","actor":{"id":61173114,"login":"BlueWolf3682","display_login":"BlueWolf3682","gravatar_id":"","url":"https://api.github.com/users/BlueWolf3682","avatar_url":"https://avatars.githubusercontent.com/u/61173114?"},"repo":{"id":89822531,"name":"Anuken/Mindustry","url":"https://api.github.com/repos/Anuken/Mindustry"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/Anuken/Mindustry/issues/5627","repository_url":"https://api.github.com/repos/Anuken/Mindustry","labels_url":"https://api.github.com/repos/Anuken/Mindustry/issues/5627/labels{/name}","comments_url":"https://api.github.com/repos/Anuken/Mindustry/issues/5627/comments","events_url":"https://api.github.com/repos/Anuken/Mindustry/issues/5627/events","html_url":"https://github.com/Anuken/Mindustry/issues/5627","id":949355537,"node_id":"MDU6SXNzdWU5NDkzNTU1Mzc=","number":5627,"title":"Can't use drawBlock fields in GenericCrafters right","user":{"login":"BlueWolf3682","id":61173114,"node_id":"MDQ6VXNlcjYxMTczMTE0","avatar_url":"https://avatars.githubusercontent.com/u/61173114?v=4","gravatar_id":"","url":"https://api.github.com/users/BlueWolf3682","html_url":"https://github.com/BlueWolf3682","followers_url":"https://api.github.com/users/BlueWolf3682/followers","following_url":"https://api.github.com/users/BlueWolf3682/following{/other_user}","gists_url":"https://api.github.com/users/BlueWolf3682/gists{/gist_id}","starred_url":"https://api.github.com/users/BlueWolf3682/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BlueWolf3682/subscriptions","organizations_url":"https://api.github.com/users/BlueWolf3682/orgs","repos_url":"https://api.github.com/users/BlueWolf3682/repos","events_url":"https://api.github.com/users/BlueWolf3682/events{/privacy}","received_events_url":"https://api.github.com/users/BlueWolf3682/received_events","type":"User","site_admin":false},"labels":[{"id":594803818,"node_id":"MDU6TGFiZWw1OTQ4MDM4MTg=","url":"https://api.github.com/repos/Anuken/Mindustry/labels/bug","name":"bug","color":"d30051","default":true,"description":""}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T06:00:06Z","updated_at":"2021-07-21T06:00:06Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"body":"**Platform**: *Android*\r\n\r\n**Build**: *128.1.*\r\n\r\n**Issue**: *yeah cool*\r\n![Screenshot_20210720-235031](https://user-images.githubusercontent.com/61173114/126438258-64b65a07-f519-4991-acc8-fb482075f07a.png)\r\n![Screenshot_20210720-234956](https://user-images.githubusercontent.com/61173114/126438268-3818cbf6-5c99-497e-befe-1fa1e0203b52.png)\r\n\r\n**Steps to reproduce**: *Give any JSON GenericCrafter a DrawSmelter/DrawArcSmelter and try to change the drawer's fields.*\r\n\r\n**Link(s) to mod(s) used**: *[unavailable for testing, was done on local files. Blame android 11 for me being unable to rip it out.]*\r\n\r\n**Save file**: *Cannot provide because I cannot export the mod zip i am using for this - if I provided the save, it would remove the block on world load due to invalid size and fields.*\r\n\r\nIf you remove the line above without reading it properly and understanding what it means, I will reap your soul. Even if you're playing on someone's server, you can still save the game to a slot.\r\n\r\nDon't matter this time\r\n\r\n**(Crash) logs**: *none.*\r\n\r\n---\r\n\r\n*Place an X (no spaces) between the brackets to confirm that you have read the line below.* \r\n- [X] **I have not updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**\r\n- [X] **I have searched the closed and open issues to make sure that this problem has not already been reported.**\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208259","type":"CreateEvent","actor":{"id":86597160,"login":"Govindswaroop-astro","display_login":"Govindswaroop-astro","gravatar_id":"","url":"https://api.github.com/users/Govindswaroop-astro","avatar_url":"https://avatars.githubusercontent.com/u/86597160?"},"repo":{"id":387852769,"name":"Govindswaroop-astro/DS-and-ML-Capstone-Project","url":"https://api.github.com/repos/Govindswaroop-astro/DS-and-ML-Capstone-Project"},"payload":{"ref":"Module-2-Exploratory-Data-analysis","ref_type":"branch","master_branch":"main","description":"This repository contains the codes and resources used in edX Data Science and Machine Learning Capstone Project.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T06:00:06Z"} +{"id":"17244208264","type":"PushEvent","actor":{"id":48645689,"login":"linusromland","display_login":"linusromland","gravatar_id":"","url":"https://api.github.com/users/linusromland","avatar_url":"https://avatars.githubusercontent.com/u/48645689?"},"repo":{"id":381823039,"name":"linusromlandArchives/MinecraftServer","url":"https://api.github.com/repos/linusromlandArchives/MinecraftServer"},"payload":{"push_id":7561415260,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"573e90df10c209cd37f792953cc201a3f969e9f5","before":"ac8f6db1daa54f0da545aeaa0fa6697462102fbc","commits":[{"sha":"573e90df10c209cd37f792953cc201a3f969e9f5","author":{"name":"linusromlandBackups","email":"df4abc6316c2aa0cfb1adaa6a44405041f33e9a8@gmail.com"},"message":"Backup from Wed 21 Jul 2021 08:00:01 AM CEST","distinct":true,"url":"https://api.github.com/repos/linusromlandArchives/MinecraftServer/commits/573e90df10c209cd37f792953cc201a3f969e9f5"}]},"public":true,"created_at":"2021-07-21T06:00:06Z","org":{"id":83013944,"login":"linusromlandArchives","gravatar_id":"","url":"https://api.github.com/orgs/linusromlandArchives","avatar_url":"https://avatars.githubusercontent.com/u/83013944?"}} +{"id":"17244791732","type":"PushEvent","actor":{"id":39515279,"login":"DylanJoo","display_login":"DylanJoo","gravatar_id":"","url":"https://api.github.com/users/DylanJoo","avatar_url":"https://avatars.githubusercontent.com/u/39515279?"},"repo":{"id":387684637,"name":"DylanJoo/temp","url":"https://api.github.com/repos/DylanJoo/temp"},"payload":{"push_id":7561706124,"size":1,"distinct_size":1,"ref":"refs/heads/treccast","head":"79767fd0ca111938ac3fc202f22b0fc4c577ce29","before":"e3a951afd59a45cf2ec9bfe6e5cfd0f16643f679","commits":[{"sha":"79767fd0ca111938ac3fc202f22b0fc4c577ce29","author":{"name":"DylanJoo","email":"a3ba619d846b98fedd06454761876fc743c419da@gmail.com"},"message":"fix","distinct":true,"url":"https://api.github.com/repos/DylanJoo/temp/commits/79767fd0ca111938ac3fc202f22b0fc4c577ce29"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791734","type":"CreateEvent","actor":{"id":32995012,"login":"ekrctb","display_login":"ekrctb","gravatar_id":"","url":"https://api.github.com/users/ekrctb","avatar_url":"https://avatars.githubusercontent.com/u/32995012?"},"repo":{"id":134741029,"name":"ekrctb/osu","url":"https://api.github.com/repos/ekrctb/osu"},"payload":{"ref":"selection-box-can-flip","ref_type":"branch","master_branch":"master","description":"rhythm is just a *click* away!","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791735","type":"PushEvent","actor":{"id":12613821,"login":"gregdhill","display_login":"gregdhill","gravatar_id":"","url":"https://api.github.com/users/gregdhill","avatar_url":"https://avatars.githubusercontent.com/u/12613821?"},"repo":{"id":251576007,"name":"interlay/interbtc","url":"https://api.github.com/repos/interlay/interbtc"},"payload":{"push_id":7561706114,"size":2,"distinct_size":2,"ref":"refs/heads/master","head":"0ea0ea26ef9879af4533a05da7ea90f0ca9aecc4","before":"4316a61837300e2d5dc91332e860c95ec1abeb8c","commits":[{"sha":"318509bcec548430a2f4c8930aa0f6799a211732","author":{"name":"Sander Bosma","email":"3d8a9d6e5a12e0b56d8a6110f96ee9c51e9e4542@gmail.com"},"message":"fix: use upstream orml","distinct":true,"url":"https://api.github.com/repos/interlay/interbtc/commits/318509bcec548430a2f4c8930aa0f6799a211732"},{"sha":"0ea0ea26ef9879af4533a05da7ea90f0ca9aecc4","author":{"name":"Greg Hill","email":"e80390f752b9f7611a96fbe9a58a9ad92499ac4c@outlook.com"},"message":"Merge pull request #195 from sander2/fix/broken-dependency\n\nfix: use upstream orml","distinct":true,"url":"https://api.github.com/repos/interlay/interbtc/commits/0ea0ea26ef9879af4533a05da7ea90f0ca9aecc4"}]},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":57096748,"login":"interlay","gravatar_id":"","url":"https://api.github.com/orgs/interlay","avatar_url":"https://avatars.githubusercontent.com/u/57096748?"}} +{"id":"17244791736","type":"PushEvent","actor":{"id":33936852,"login":"m0c0r30n","display_login":"m0c0r30n","gravatar_id":"","url":"https://api.github.com/users/m0c0r30n","avatar_url":"https://avatars.githubusercontent.com/u/33936852?"},"repo":{"id":321111180,"name":"m0c0r30n/kabu-database","url":"https://api.github.com/repos/m0c0r30n/kabu-database"},"payload":{"push_id":7561706132,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"758d4d936b45904d8b062b3f944c93084cfbb61b","before":"3ba86bbd487264f3bae30a286e0417a551a4dce2","commits":[{"sha":"758d4d936b45904d8b062b3f944c93084cfbb61b","author":{"name":"m0c0r30n","email":"770645c6e89108578397b8226b3a39c03f9fcffb@gmail.cm"},"message":"fix bug","distinct":true,"url":"https://api.github.com/repos/m0c0r30n/kabu-database/commits/758d4d936b45904d8b062b3f944c93084cfbb61b"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791737","type":"DeleteEvent","actor":{"id":52546,"login":"hirotaka","display_login":"hirotaka","gravatar_id":"","url":"https://api.github.com/users/hirotaka","avatar_url":"https://avatars.githubusercontent.com/u/52546?"},"repo":{"id":356442765,"name":"stackhackerio/astron","url":"https://api.github.com/repos/stackhackerio/astron"},"payload":{"ref":"dependabot/npm_and_yarn/types/node-15.12.5","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":79205277,"login":"stackhackerio","gravatar_id":"","url":"https://api.github.com/orgs/stackhackerio","avatar_url":"https://avatars.githubusercontent.com/u/79205277?"}} +{"id":"17244791738","type":"PushEvent","actor":{"id":21104054,"login":"Alanscut","display_login":"Alanscut","gravatar_id":"","url":"https://api.github.com/users/Alanscut","avatar_url":"https://avatars.githubusercontent.com/u/21104054?"},"repo":{"id":277434398,"name":"Alanscut/snakeyaml","url":"https://api.github.com/repos/Alanscut/snakeyaml"},"payload":{"push_id":7561706111,"size":108,"distinct_size":1,"ref":"refs/heads/generic-type","head":"8fb780772c8e2fbb1557b9e69e5e2aac68c3ca02","before":"09e85d4b8c02b97bcddd468530fe955468849812","commits":[{"sha":"08d8cf9c222c44e1af7f2f8a29842943aaea3bb7","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Add test","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/08d8cf9c222c44e1af7f2f8a29842943aaea3bb7"},{"sha":"c4abec129bda0f98f4a5548822c29dd96a64c848","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"fix typos","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/c4abec129bda0f98f4a5548822c29dd96a64c848"},{"sha":"82f66083c69d0c90c8127485581b3d7d1fd2f33e","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"add github actions CI","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/82f66083c69d0c90c8127485581b3d7d1fd2f33e"},{"sha":"d6a31ae3a9a8b65e9cbdd5d14640a82fdc3a7e92","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"Fix issue #481: dump anchor not used by alias","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/d6a31ae3a9a8b65e9cbdd5d14640a82fdc3a7e92"},{"sha":"3b8ff94e6976e93f7ba66537caebaf47ef86380e","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"setAnchorGenerator","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/3b8ff94e6976e93f7ba66537caebaf47ef86380e"},{"sha":"bae1eae5ffbe8b17d2b2045a6c329441e19b168e","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Merge pull request #7 from Alanscut/typo\n\nfix typos","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/bae1eae5ffbe8b17d2b2045a6c329441e19b168e"},{"sha":"6bb7dc3ba36f4e218eaee7e0db01395b1895e57e","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Merge branch 'master' of https://github.com/asomov/snakeyaml","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/6bb7dc3ba36f4e218eaee7e0db01395b1895e57e"},{"sha":"187d7e47d1e91777238602470ea3f0ce2d480636","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Add Javadoc","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/187d7e47d1e91777238602470ea3f0ce2d480636"},{"sha":"9013a19afc8a37871f4f40ed0c39c97193ddbc3e","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"add OrderTest","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/9013a19afc8a37871f4f40ed0c39c97193ddbc3e"},{"sha":"f3b9a5856e1fa7cd2c6783084bbc78a7552aee53","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"remove java8 new feature","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/f3b9a5856e1fa7cd2c6783084bbc78a7552aee53"},{"sha":"cfbcde3d10c9605a3ae3ac5b3430d308ea081529","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"rename dump(Node, Writer) and yaml file","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/cfbcde3d10c9605a3ae3ac5b3430d308ea081529"},{"sha":"7e04ea59a6ce0c9b91c097d39559dcdbce4e75b5","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"Merge remote-tracking branch 'origin/master' into actions","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/7e04ea59a6ce0c9b91c097d39559dcdbce4e75b5"},{"sha":"d5cd403d1b508ca6cc9d0ae686960e01d492468e","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Merge pull request #8 from Alanscut/dump-anchor\n\nFix issue #481: Dump anchors that is not used by any alias","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/d5cd403d1b508ca6cc9d0ae686960e01d492468e"},{"sha":"a89fae85fe6001031204e001a3b0719cf2163397","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Update changes.xml","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/a89fae85fe6001031204e001a3b0719cf2163397"},{"sha":"ef90eecf66808cd10c0eedd642b93b201f77a96d","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Improve Javadoc","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/ef90eecf66808cd10c0eedd642b93b201f77a96d"},{"sha":"fbadd3e80bd11bd01e40ab5186c10f90a7f104d2","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Merge pull request #6 from Alanscut/actions\n\nadd github actions CI","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/fbadd3e80bd11bd01e40ab5186c10f90a7f104d2"},{"sha":"e00bad6fa30304b1fd69a8fadc67331357499810","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Merge pull request #10 from Alanscut/order-test\n\nAdd OrderTest","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/e00bad6fa30304b1fd69a8fadc67331357499810"},{"sha":"3bb34a0ab511a0069661bf8cef888097abe8bfc1","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Update maven-resources-plugin to version 3.2.0","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/3bb34a0ab511a0069661bf8cef888097abe8bfc1"},{"sha":"da1f7d84d4f3d25621300f14d1c956dc837fa231","author":{"name":"Alanscut","email":"e08551f5d33a2dec2b66b477babbfb1d58954415@163.com"},"message":"fix CI, specify maven-resources-plugin as 3.0.2","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/da1f7d84d4f3d25621300f14d1c956dc837fa231"},{"sha":"edcc678890a92f1eb2e499db27f879c97d533cb5","author":{"name":"Andrey Somov","email":"873702c7a96d3e6faa6d1afbee8ebd6f9560e35c@gmail.com"},"message":"Merge pull request #11 from Alanscut/ci\n\nfix CI, specify maven-resources-plugin as 3.0.2","distinct":false,"url":"https://api.github.com/repos/Alanscut/snakeyaml/commits/edcc678890a92f1eb2e499db27f879c97d533cb5"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791744","type":"CreateEvent","actor":{"id":68605386,"login":"sarkarnab","display_login":"sarkarnab","gravatar_id":"","url":"https://api.github.com/users/sarkarnab","avatar_url":"https://avatars.githubusercontent.com/u/68605386?"},"repo":{"id":388024717,"name":"sarkarnab/udemy_syscloud","url":"https://api.github.com/repos/sarkarnab/udemy_syscloud"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791752","type":"PushEvent","actor":{"id":8517910,"login":"LombiqBot","display_login":"LombiqBot","gravatar_id":"","url":"https://api.github.com/users/LombiqBot","avatar_url":"https://avatars.githubusercontent.com/u/8517910?"},"repo":{"id":264168728,"name":"Lombiq/OrchardCMS.Poll","url":"https://api.github.com/repos/Lombiq/OrchardCMS.Poll"},"payload":{"push_id":7561706118,"size":0,"distinct_size":0,"ref":"refs/heads/issue/EETT-12","head":"3cf997d062664a70200cd2233dd396dc73ff2eb5","before":"3cf997d062664a70200cd2233dd396dc73ff2eb5","commits":[]},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":8158177,"login":"Lombiq","gravatar_id":"","url":"https://api.github.com/orgs/Lombiq","avatar_url":"https://avatars.githubusercontent.com/u/8158177?"}} +{"id":"17244791760","type":"CreateEvent","actor":{"id":65045755,"login":"Vasile-Hij","display_login":"Vasile-Hij","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","avatar_url":"https://avatars.githubusercontent.com/u/65045755?"},"repo":{"id":387832280,"name":"Vasile-Hij/excels-pandas","url":"https://api.github.com/repos/Vasile-Hij/excels-pandas"},"payload":{"ref":"v0.1","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791788","type":"PushEvent","actor":{"id":52546,"login":"hirotaka","display_login":"hirotaka","gravatar_id":"","url":"https://api.github.com/users/hirotaka","avatar_url":"https://avatars.githubusercontent.com/u/52546?"},"repo":{"id":356442765,"name":"stackhackerio/astron","url":"https://api.github.com/repos/stackhackerio/astron"},"payload":{"push_id":7561706148,"size":2,"distinct_size":2,"ref":"refs/heads/main","head":"7e320b88075f95fca78370b34c8313c7ef3941d6","before":"3ddf5053cc022cdd62c178e22358850315457315","commits":[{"sha":"05999764f584216ebe7f0c0b24e3f7ceda9207c4","author":{"name":"dependabot[bot]","email":"1c358da00a777d4e9898c1280ab801e2df165188@users.noreply.github.com"},"message":"Bump @types/node from 14.14.41 to 15.12.5\n\nBumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.41 to 15.12.5.\n- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)\n- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)\n\n---\nupdated-dependencies:\n- dependency-name: \"@types/node\"\n dependency-type: direct:development\n update-type: version-update:semver-major\n...\n\nSigned-off-by: dependabot[bot] ","distinct":true,"url":"https://api.github.com/repos/stackhackerio/astron/commits/05999764f584216ebe7f0c0b24e3f7ceda9207c4"},{"sha":"7e320b88075f95fca78370b34c8313c7ef3941d6","author":{"name":"Hirotaka Mizutani","email":"d5244bcffa2fcdcd3bdd4de3fa07c2e28eaae737@mizutani.to"},"message":"Merge pull request #7 from stackhackerio/dependabot/npm_and_yarn/types/node-15.12.5\n\nBump @types/node from 14.14.41 to 15.12.5","distinct":true,"url":"https://api.github.com/repos/stackhackerio/astron/commits/7e320b88075f95fca78370b34c8313c7ef3941d6"}]},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":79205277,"login":"stackhackerio","gravatar_id":"","url":"https://api.github.com/orgs/stackhackerio","avatar_url":"https://avatars.githubusercontent.com/u/79205277?"}} +{"id":"17244791795","type":"WatchEvent","actor":{"id":74425534,"login":"sjoveska","display_login":"sjoveska","gravatar_id":"","url":"https://api.github.com/users/sjoveska","avatar_url":"https://avatars.githubusercontent.com/u/74425534?"},"repo":{"id":381091798,"name":"gabrieldim/Accounting-System-Software-Testing","url":"https://api.github.com/repos/gabrieldim/Accounting-System-Software-Testing"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791796","type":"IssueCommentEvent","actor":{"id":11555354,"login":"SilasKenneth","display_login":"SilasKenneth","gravatar_id":"","url":"https://api.github.com/users/SilasKenneth","avatar_url":"https://avatars.githubusercontent.com/u/11555354?"},"repo":{"id":66662684,"name":"microsoftgraph/msgraph-sdk-php","url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/issues/557","repository_url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php","labels_url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/issues/557/labels{/name}","comments_url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/issues/557/comments","events_url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/issues/557/events","html_url":"https://github.com/microsoftgraph/msgraph-sdk-php/issues/557","id":948757874,"node_id":"MDU6SXNzdWU5NDg3NTc4NzQ=","number":557,"title":"object return instead of array when call getMeetingTimeSuggestions under MeetingTimeSuggestionResult","user":{"login":"jp-Telus","id":86217312,"node_id":"MDQ6VXNlcjg2MjE3MzEy","avatar_url":"https://avatars.githubusercontent.com/u/86217312?v=4","gravatar_id":"","url":"https://api.github.com/users/jp-Telus","html_url":"https://github.com/jp-Telus","followers_url":"https://api.github.com/users/jp-Telus/followers","following_url":"https://api.github.com/users/jp-Telus/following{/other_user}","gists_url":"https://api.github.com/users/jp-Telus/gists{/gist_id}","starred_url":"https://api.github.com/users/jp-Telus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jp-Telus/subscriptions","organizations_url":"https://api.github.com/users/jp-Telus/orgs","repos_url":"https://api.github.com/users/jp-Telus/repos","events_url":"https://api.github.com/users/jp-Telus/events{/privacy}","received_events_url":"https://api.github.com/users/jp-Telus/received_events","type":"User","site_admin":false},"labels":[{"id":1242045599,"node_id":"MDU6TGFiZWwxMjQyMDQ1NTk5","url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/labels/ToTriage","name":"ToTriage","color":"a16bdb","default":false,"description":""}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-20T15:14:06Z","updated_at":"2021-07-21T07:00:00Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"I called the FindMeetingTime API ( /me/findMeetingTimes ) and got the list of suggestion result returned, \r\n\r\nHere is the code:\r\n\r\n $findMeetingEventsUrl = '/me/findMeetingTimes';\r\n $results = $graph->createRequest('POST', $findMeetingEventsUrl)\r\n ->addHeaders(['Prefer'=> 'outlook.timezone=\"Pacific Standard Time\"'])\r\n ->attachBody($newEvent)\r\n ->setReturnType(Model\\MeetingTimeSuggestionsResult::class)\r\n ->execute();\r\n\r\nWhen I call the getMeetingTimeSuggestions, I only get get an 'object' (\"MeetingTimeSuggtesions\") instead of array of \"MeetingTimeSuggtesions\". I expect to get the collections from this call. Do I miss something? Thank you.\r\n\r\n Error\r\n Cannot use object of type Microsoft\\Graph\\Model\\MeetingTimeSuggestion as array\r\n\r\n\r\n\r\n\r\nSource: https://github.com/microsoftgraph/msgraph-sdk-php/blob/dev/src/Model/MeetingTimeSuggestionsResult.php\r\n \r\n[AB#10302](https://microsoftgraph.visualstudio.com/0985d294-5762-4bc2-a565-161ef349ca3e/_workitems/edit/10302)","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/issues/comments/883942776","html_url":"https://github.com/microsoftgraph/msgraph-sdk-php/issues/557#issuecomment-883942776","issue_url":"https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/issues/557","id":883942776,"node_id":"IC_kwDOA_kxHM40r-V4","user":{"login":"SilasKenneth","id":11555354,"node_id":"MDQ6VXNlcjExNTU1MzU0","avatar_url":"https://avatars.githubusercontent.com/u/11555354?v=4","gravatar_id":"","url":"https://api.github.com/users/SilasKenneth","html_url":"https://github.com/SilasKenneth","followers_url":"https://api.github.com/users/SilasKenneth/followers","following_url":"https://api.github.com/users/SilasKenneth/following{/other_user}","gists_url":"https://api.github.com/users/SilasKenneth/gists{/gist_id}","starred_url":"https://api.github.com/users/SilasKenneth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SilasKenneth/subscriptions","organizations_url":"https://api.github.com/users/SilasKenneth/orgs","repos_url":"https://api.github.com/users/SilasKenneth/repos","events_url":"https://api.github.com/users/SilasKenneth/events{/privacy}","received_events_url":"https://api.github.com/users/SilasKenneth/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:00Z","updated_at":"2021-07-21T07:00:00Z","author_association":"MEMBER","body":"Related to #76 #97 ","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":17304259,"login":"microsoftgraph","gravatar_id":"","url":"https://api.github.com/orgs/microsoftgraph","avatar_url":"https://avatars.githubusercontent.com/u/17304259?"}} +{"id":"17244791800","type":"PushEvent","actor":{"id":87482730,"login":"selabhust","display_login":"selabhust","gravatar_id":"","url":"https://api.github.com/users/selabhust","avatar_url":"https://avatars.githubusercontent.com/u/87482730?"},"repo":{"id":386300868,"name":"selabhust/home","url":"https://api.github.com/repos/selabhust/home"},"payload":{"push_id":7561706157,"size":2,"distinct_size":2,"ref":"refs/heads/main","head":"c6bb6978c20cc8abe91083d532bd79ba80ac8730","before":"43e696fd1a49c19d0cd0dacdd47dc740bca9191f","commits":[{"sha":"e200f8aa04419cdfcc0bc1e284ff63bb0ffff2ad","author":{"name":"selabhust","email":"13a82a6c3f5000321cbe8c6b7e9c7567b1dbdead@gmail.com"},"message":"update team, album","distinct":true,"url":"https://api.github.com/repos/selabhust/home/commits/e200f8aa04419cdfcc0bc1e284ff63bb0ffff2ad"},{"sha":"c6bb6978c20cc8abe91083d532bd79ba80ac8730","author":{"name":"selabhust","email":"13a82a6c3f5000321cbe8c6b7e9c7567b1dbdead@gmail.com"},"message":"Merge branch 'main' of https://github.com/selabhust/home into main","distinct":true,"url":"https://api.github.com/repos/selabhust/home/commits/c6bb6978c20cc8abe91083d532bd79ba80ac8730"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791804","type":"PushEvent","actor":{"id":72680186,"login":"bg-shorthand","display_login":"bg-shorthand","gravatar_id":"","url":"https://api.github.com/users/bg-shorthand","avatar_url":"https://avatars.githubusercontent.com/u/72680186?"},"repo":{"id":381590294,"name":"bg-shorthand/reserve-meet","url":"https://api.github.com/repos/bg-shorthand/reserve-meet"},"payload":{"push_id":7561706163,"size":9,"distinct_size":9,"ref":"refs/heads/master","head":"789adb7ceff02a1dbfc97f4e02a4e6a354967f67","before":"4492815520667522b4afb809e9ef32b7925fa43e","commits":[{"sha":"39016f2bafe9e359b84dc6f59629ab5045e14839","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[수정] 17시까지만 예약이 가능하도록 타임테이블, 종료시간 수정","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/39016f2bafe9e359b84dc6f59629ab5045e14839"},{"sha":"d1f18f787e4ddded6dfd1f9a6c5313b24d13548f","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[추가] 테이블에서 이전 시간은 예약을 막도록, 테이블을 흐릿하게 가림막","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/d1f18f787e4ddded6dfd1f9a6c5313b24d13548f"},{"sha":"88253282b9e8cc08b23ab0ea4ea4bf7d0a8a8b7b","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[수정] table > tr 에러처리","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/88253282b9e8cc08b23ab0ea4ea4bf7d0a8a8b7b"},{"sha":"792aecc1a149b19b77cb6c91c7afa55aeaaef917","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[수정] 테이블 가림막 높이 수정","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/792aecc1a149b19b77cb6c91c7afa55aeaaef917"},{"sha":"fed59fa5fedf0bba7a6c8ae5f75d508780f9cd82","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[수정] 전날에는 가림막이 전체를 가리도록, 다음날엔 가림막 삭제","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/fed59fa5fedf0bba7a6c8ae5f75d508780f9cd82"},{"sha":"fe54264296df7aa1d61743d62074d43c329b976c","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[추가] 회의 등록할 때 회의 이름 없으면 버튼 안 눌리게","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/fe54264296df7aa1d61743d62074d43c329b976c"},{"sha":"a0652c412068565152a1ab505eae755228cfdfc9","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[수정] 모달 다이얼로그 컨테이너 디렉토리 구분","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/a0652c412068565152a1ab505eae755228cfdfc9"},{"sha":"c6cd572466351c35d02c56fd6138615b05fcc32d","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[추가] 캘린더 추가하기\n\n1. calendarApi에 insertCalendar 추가\n2. 캘린더 리스트 아래 추가 버튼 추가\n3. newCalendar 모달 다이얼로그 추가(다이얼로그를 위한 상태 추가)","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/c6cd572466351c35d02c56fd6138615b05fcc32d"},{"sha":"789adb7ceff02a1dbfc97f4e02a4e6a354967f67","author":{"name":"한병국","email":"745954d696451689c6fbddb4bffa6e5ced9cec1e@gmail.com"},"message":"[추가] 현재 표시 중인 캘린더 fontWeight: 700","distinct":true,"url":"https://api.github.com/repos/bg-shorthand/reserve-meet/commits/789adb7ceff02a1dbfc97f4e02a4e6a354967f67"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791827","type":"PushEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388024715,"name":"thatjohn01/754365578","url":"https://api.github.com/repos/thatjohn01/754365578"},"payload":{"push_id":7561706175,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"5d7638319e0ea19597df177a8a51d73455ee1a7b","before":"2443e9e84518ed2cb7a31c51024e860fb9c3bbc0","commits":[{"sha":"5d7638319e0ea19597df177a8a51d73455ee1a7b","author":{"name":"thatjohn01","email":"72eb81e66410b3da65da4cca287ce0578825ce64@users.noreply.github.com"},"message":"change README.md","distinct":true,"url":"https://api.github.com/repos/thatjohn01/754365578/commits/5d7638319e0ea19597df177a8a51d73455ee1a7b"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791835","type":"PushEvent","actor":{"id":67408315,"login":"jheok318","display_login":"jheok318","gravatar_id":"","url":"https://api.github.com/users/jheok318","avatar_url":"https://avatars.githubusercontent.com/u/67408315?"},"repo":{"id":381617821,"name":"jheok318/mobigen-python-study-2021","url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021"},"payload":{"push_id":7561706176,"size":13,"distinct_size":13,"ref":"refs/heads/master","head":"d663eb4978a82ba5bf775da379da7defdda7eafc","before":"fef51dc263f08a7b1eddd2eb505fa1c4004c8592","commits":[{"sha":"457a39e4e2fdeb15c3ef0a38172b878fe265e88e","author":{"name":"서승환","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"[week3] Add presentation files (String, RE) (#15) (#17)\n\n장혜선 책임님 3주차 발표자료 업로드\r\n\r\nCo-authored-by: hyesunjang1201 \r\nSigned-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/457a39e4e2fdeb15c3ef0a38172b878fe265e88e"},{"sha":"1720a83ad3480bc4550561b2dbf3d1053d59338d","author":{"name":"seungfwani","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"Add week05 스터디_내용.md","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/1720a83ad3480bc4550561b2dbf3d1053d59338d"},{"sha":"85c598123b4b385397886490459e239523165d7c","author":{"name":"seungfwani","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"Update week05 스터디_내용.md","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/85c598123b4b385397886490459e239523165d7c"},{"sha":"9616631a4493e247fa246c86c1786f17662c2e13","author":{"name":"seungfwani","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"Update deque_performace example code","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/9616631a4493e247fa246c86c1786f17662c2e13"},{"sha":"46d7f7e5b5ccb7bd200c78aa82c8d3e4793b4ea8","author":{"name":"seungfwani","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"Add collections.abc example","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/46d7f7e5b5ccb7bd200c78aa82c8d3e4793b4ea8"},{"sha":"4bee73ee2d3adc8ba5abb4fbf89e921e2b850da4","author":{"name":"seungfwani","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"Update deque_performace test result","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/4bee73ee2d3adc8ba5abb4fbf89e921e2b850da4"},{"sha":"cfcab32d801dafac68759dc399034e8b0ec2b913","author":{"name":"JinHee","email":"a7d991dc3130579593e14445b71d1d909f95ff69@users.noreply.github.com"},"message":"[week05] Add presentation files - DataType (#22)\n\nAuthored-by: pojh5090 \r\nSinged-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/cfcab32d801dafac68759dc399034e8b0ec2b913"},{"sha":"b53e6d5c18eb41e898044a2fb2b9466983d1100c","author":{"name":"최현민","email":"bc06dcf85f45629afa3a10f0ff5c050bd910f968@users.noreply.github.com"},"message":"[week03] Add homework - word_count & re (#20)\n\nAuthored-by: choihyuunmin \r\nSinged-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/b53e6d5c18eb41e898044a2fb2b9466983d1100c"},{"sha":"ec4f0080cd3fdbee0d006289ea49dc1ea166940c","author":{"name":"seungfwani","email":"49bb3b1e0393b26cdae4c091e32b32511193655e@gmail.com"},"message":"Add week06 스터디_내용.md\n\nSigned-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/ec4f0080cd3fdbee0d006289ea49dc1ea166940c"},{"sha":"ad37a599cf99f662b812a4c11e6fcb1bb8a4a421","author":{"name":"jheok318","email":"51198ae36c500916a81d2cefd92ff109f7e2808b@gmail.com"},"message":"[week04] Add homework - answer of questions (#16)\n\nAuthored-by: jheok318 \nSinged-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/ad37a599cf99f662b812a4c11e6fcb1bb8a4a421"},{"sha":"f881ba4d8b974a5107a2f20b560ca415794a2044","author":{"name":"gyojun","email":"b448e5c0d6e1314968770a7d2e20853f79be5c30@mobigen.com"},"message":"[week04] Add homework - answer of questions (#18)\n\nAuthored-by: Altera520 \nSinged-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/f881ba4d8b974a5107a2f20b560ca415794a2044"},{"sha":"b51fa33bba76039a38515d268ab0021dd01a21b0","author":{"name":"nayunoh16","email":"6dcea65820184544d371f4f82fe4df5cef303c99@users.noreply.github.com"},"message":"[week04] Add homework - answer of questions (#21)\n\nAuthored-by: nayunoh16 <58417462+nayunoh16@users.noreply.github.com>\nSinged-off-by: seungfwani ","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/b51fa33bba76039a38515d268ab0021dd01a21b0"},{"sha":"d663eb4978a82ba5bf775da379da7defdda7eafc","author":{"name":"오준혁","email":"932857287d536e6ebd609979559b0ec2c07491af@jheok.local"},"message":"Merge branch 'master' of https://github.com/mobigen/mobigen-python-study-2021","distinct":true,"url":"https://api.github.com/repos/jheok318/mobigen-python-study-2021/commits/d663eb4978a82ba5bf775da379da7defdda7eafc"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791842","type":"PushEvent","actor":{"id":69377958,"login":"aikefu002","display_login":"aikefu002","gravatar_id":"","url":"https://api.github.com/users/aikefu002","avatar_url":"https://avatars.githubusercontent.com/u/69377958?"},"repo":{"id":343052750,"name":"aikefu002/book","url":"https://api.github.com/repos/aikefu002/book"},"payload":{"push_id":7561706180,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"4a91e32f13c6b83ed7ef3915a6653466e71c6bb7","before":"e65a16034a1837cdb57a2642a78c77c52c449e2e","commits":[{"sha":"4a91e32f13c6b83ed7ef3915a6653466e71c6bb7","author":{"name":"aikefu002","email":"81afe41e9034909903e17c7c7fb5aa6d9ac3e16c@users.noreply.github.com"},"message":"2021-07-21 14:59:58 upload 4ccb0a07d51045c7929740471e672868_400.jpeg","distinct":true,"url":"https://api.github.com/repos/aikefu002/book/commits/4a91e32f13c6b83ed7ef3915a6653466e71c6bb7"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791855","type":"PullRequestReviewEvent","actor":{"id":50898502,"login":"cothis","display_login":"cothis","gravatar_id":"","url":"https://api.github.com/users/cothis","avatar_url":"https://avatars.githubusercontent.com/u/50898502?"},"repo":{"id":385124567,"name":"woowa-techcamp-2021/deal-17","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17"},"payload":{"action":"created","review":{"id":711317352,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3MzUy","user":{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false},"body":null,"commit_id":"cb1568ae5f146dc08882a53ed030218de604cd3c","submitted_at":"2021-07-21T07:00:00Z","state":"commented","html_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119#pullrequestreview-711317352","pull_request_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119","author_association":"COLLABORATOR","_links":{"html":{"href":"https://github.com/woowa-techcamp-2021/deal-17/pull/119#pullrequestreview-711317352"},"pull_request":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119"}}},"pull_request":{"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119","id":693989579,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzOTg5NTc5","html_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119","diff_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119.diff","patch_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119.patch","issue_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119","number":119,"state":"open","locked":false,"title":"[feature/mypage] 로그인 시 메인페이지에서 회원정보 페이지로 이동","user":{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T02:13:48Z","updated_at":"2021-07-21T07:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0e3370b7911358702d3c4ea59cc923ad68fe2e97","assignee":{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false},"assignees":[{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":3160810249,"node_id":"MDU6TGFiZWwzMTYwODEwMjQ5","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/labels/frontend","name":"frontend","color":"8B0759","default":false,"description":""}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/commits","review_comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/comments","review_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/comments{/number}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119/comments","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/cb1568ae5f146dc08882a53ed030218de604cd3c","head":{"label":"woowa-techcamp-2021:feature/mypage","ref":"feature/mypage","sha":"cb1568ae5f146dc08882a53ed030218de604cd3c","user":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"repo":{"id":385124567,"node_id":"MDEwOlJlcG9zaXRvcnkzODUxMjQ1Njc=","name":"deal-17","full_name":"woowa-techcamp-2021/deal-17","private":false,"owner":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/woowa-techcamp-2021/deal-17","description":"우아마켓 - 17팀 김채은, 윤민호","fork":false,"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17","forks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/forks","keys_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/keys{/key_id}","collaborators_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/teams","hooks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/hooks","issue_events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/events{/number}","events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/events","assignees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/assignees{/user}","branches_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/branches{/branch}","tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/tags","blobs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/refs{/sha}","trees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/trees{/sha}","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/{sha}","languages_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/languages","stargazers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/stargazers","contributors_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contributors","subscribers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscribers","subscription_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscription","commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/commits{/sha}","git_commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/commits{/sha}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/comments{/number}","issue_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/comments{/number}","contents_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contents/{+path}","compare_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/compare/{base}...{head}","merges_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/merges","archive_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/downloads","issues_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues{/number}","pulls_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls{/number}","milestones_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/milestones{/number}","notifications_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/labels{/name}","releases_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/releases{/id}","deployments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/deployments","created_at":"2021-07-12T04:29:47Z","updated_at":"2021-07-20T15:57:20Z","pushed_at":"2021-07-21T06:59:54Z","git_url":"git://github.com/woowa-techcamp-2021/deal-17.git","ssh_url":"git@github.com:woowa-techcamp-2021/deal-17.git","clone_url":"https://github.com/woowa-techcamp-2021/deal-17.git","svn_url":"https://github.com/woowa-techcamp-2021/deal-17","homepage":"","size":488,"stargazers_count":1,"watchers_count":1,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":11,"license":null,"forks":0,"open_issues":11,"watchers":1,"default_branch":"dev"}},"base":{"label":"woowa-techcamp-2021:dev","ref":"dev","sha":"dbb8ee602d263a0cfdd8c7b3890fab0303a73472","user":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"repo":{"id":385124567,"node_id":"MDEwOlJlcG9zaXRvcnkzODUxMjQ1Njc=","name":"deal-17","full_name":"woowa-techcamp-2021/deal-17","private":false,"owner":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/woowa-techcamp-2021/deal-17","description":"우아마켓 - 17팀 김채은, 윤민호","fork":false,"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17","forks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/forks","keys_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/keys{/key_id}","collaborators_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/teams","hooks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/hooks","issue_events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/events{/number}","events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/events","assignees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/assignees{/user}","branches_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/branches{/branch}","tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/tags","blobs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/refs{/sha}","trees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/trees{/sha}","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/{sha}","languages_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/languages","stargazers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/stargazers","contributors_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contributors","subscribers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscribers","subscription_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscription","commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/commits{/sha}","git_commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/commits{/sha}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/comments{/number}","issue_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/comments{/number}","contents_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contents/{+path}","compare_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/compare/{base}...{head}","merges_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/merges","archive_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/downloads","issues_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues{/number}","pulls_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls{/number}","milestones_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/milestones{/number}","notifications_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/labels{/name}","releases_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/releases{/id}","deployments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/deployments","created_at":"2021-07-12T04:29:47Z","updated_at":"2021-07-20T15:57:20Z","pushed_at":"2021-07-21T06:59:54Z","git_url":"git://github.com/woowa-techcamp-2021/deal-17.git","ssh_url":"git@github.com:woowa-techcamp-2021/deal-17.git","clone_url":"https://github.com/woowa-techcamp-2021/deal-17.git","svn_url":"https://github.com/woowa-techcamp-2021/deal-17","homepage":"","size":488,"stargazers_count":1,"watchers_count":1,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":11,"license":null,"forks":0,"open_issues":11,"watchers":1,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119"},"html":{"href":"https://github.com/woowa-techcamp-2021/deal-17/pull/119"},"issue":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119"},"comments":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119/comments"},"review_comments":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/comments"},"review_comment":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/commits"},"statuses":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/cb1568ae5f146dc08882a53ed030218de604cd3c"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":86336942,"login":"woowa-techcamp-2021","gravatar_id":"","url":"https://api.github.com/orgs/woowa-techcamp-2021","avatar_url":"https://avatars.githubusercontent.com/u/86336942?"}} +{"id":"17244791862","type":"PushEvent","actor":{"id":86361200,"login":"mariyaFiliya","display_login":"mariyaFiliya","gravatar_id":"","url":"https://api.github.com/users/mariyaFiliya","avatar_url":"https://avatars.githubusercontent.com/u/86361200?"},"repo":{"id":379609830,"name":"mariyaFiliya/-Python","url":"https://api.github.com/repos/mariyaFiliya/-Python"},"payload":{"push_id":7561706186,"size":1,"distinct_size":1,"ref":"refs/heads/lesson_7","head":"c4272c5a8cc377ef3098630041824fd36d62ea68","before":"c073d2a9257f3840dfc3e73cdcc96db963cdbb44","commits":[{"sha":"c4272c5a8cc377ef3098630041824fd36d62ea68","author":{"name":"cheshir","email":"720f44c8e896248c4aa6eb9c0504944260cfe693@gmail.com"},"message":"урок 7 - задание 1","distinct":true,"url":"https://api.github.com/repos/mariyaFiliya/-Python/commits/c4272c5a8cc377ef3098630041824fd36d62ea68"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791863","type":"CreateEvent","actor":{"id":81615240,"login":"Mehul57","display_login":"Mehul57","gravatar_id":"","url":"https://api.github.com/users/Mehul57","avatar_url":"https://avatars.githubusercontent.com/u/81615240?"},"repo":{"id":388024721,"name":"Mehul57/Project-30","url":"https://api.github.com/repos/Mehul57/Project-30"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791866","type":"PushEvent","actor":{"id":44427720,"login":"user11681","display_login":"user11681","gravatar_id":"","url":"https://api.github.com/users/user11681","avatar_url":"https://avatars.githubusercontent.com/u/44427720?"},"repo":{"id":388023362,"name":"user11681/archloom-forge-test","url":"https://api.github.com/repos/user11681/archloom-forge-test"},"payload":{"push_id":7561706192,"size":1,"distinct_size":1,"ref":"refs/heads/1.16","head":"243bfe67ed7a15033cdebf2f7dde4056146cd220","before":"fee28fd5b30d3f76a842d5db5bf703bf47c28160","commits":[{"sha":"243bfe67ed7a15033cdebf2f7dde4056146cd220","author":{"name":"user11681","email":"32f9b6ae0e0705b5c44297892453c854a8232bdc@gmail.com"},"message":"remove Fabric-specific information","distinct":true,"url":"https://api.github.com/repos/user11681/archloom-forge-test/commits/243bfe67ed7a15033cdebf2f7dde4056146cd220"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791867","type":"PushEvent","actor":{"id":76616434,"login":"oddfar","display_login":"oddfar","gravatar_id":"","url":"https://api.github.com/users/oddfar","avatar_url":"https://avatars.githubusercontent.com/u/76616434?"},"repo":{"id":358109980,"name":"oddfar/static","url":"https://api.github.com/repos/oddfar/static"},"payload":{"push_id":7561706193,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"c2f54368078462d3f26643e880f6d172d9a2ba0f","before":"53032aa8a65db2e824c43fe39f9275ac7f3c6144","commits":[{"sha":"c2f54368078462d3f26643e880f6d172d9a2ba0f","author":{"name":"oddfar","email":"d943519d353020e5f203d841cfec1dd7007febbd@163.com"},"message":"JUC","distinct":true,"url":"https://api.github.com/repos/oddfar/static/commits/c2f54368078462d3f26643e880f6d172d9a2ba0f"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791873","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":377774547,"name":"FrancesMadden/PIDResources","url":"https://api.github.com/repos/FrancesMadden/PIDResources"},"payload":{"push_id":7561706184,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"f81170fc949878c7989b7b52388ce42c6d5fa881","before":"49be7b4f883649baeb1ca334ebadf64eabc543bc","commits":[{"sha":"f81170fc949878c7989b7b52388ce42c6d5fa881","author":{"name":"FrancesMadden","email":"63a5fd3bc5f45a0490e4deca178d288050e26803@fv-az278-174.b3qsmdnmz0tudpq43p2gswzmkf.xx.internal.cloudapp.net"},"message":"Updating the repository GitHub html pages in the docs folder","distinct":true,"url":"https://api.github.com/repos/FrancesMadden/PIDResources/commits/f81170fc949878c7989b7b52388ce42c6d5fa881"}]},"public":true,"created_at":"2021-07-21T07:00:00Z"} +{"id":"17244791876","type":"PullRequestReviewCommentEvent","actor":{"id":50898502,"login":"cothis","display_login":"cothis","gravatar_id":"","url":"https://api.github.com/users/cothis","avatar_url":"https://avatars.githubusercontent.com/u/50898502?"},"repo":{"id":385124567,"name":"woowa-techcamp-2021/deal-17","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/comments/673711119","pull_request_review_id":711317352,"id":673711119,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3MzcxMTExOQ==","diff_hunk":"@@ -28,13 +28,20 @@ export default class MypageView extends View {\n }\n \n render() {\n- this.setTemplateData('username', 'test user');\n+ console.log(this.store.user);\n+ this.setTemplateData('username', this.store.user?.email!);\n this.appendView(AnimateType.RIGHT, AnimateType.RIGHT);\n new HeaderComponent('#mypageView__header', this.store, { title: '내 계정' }).render();\n- new LoginButtonComponent('#myPage', this.store, { title: '로그아웃', id: 'logout', onClick: () => {} }).render();\n-\n- this.container.querySelector('#logout')?.addEventListener('click', (e) => {\n- RouterEvent.dispatchEvent('@back');\n- });\n+ new LoginButtonComponent('#myPage', this.store, {\n+ title: '로그아웃',\n+ id: 'logout',\n+ onClick: () => {\n+ this.store.user = null;","path":"frontend/page/mypage-view.ts","position":null,"original_position":18,"commit_id":"cb1568ae5f146dc08882a53ed030218de604cd3c","original_commit_id":"c4b73b9c481b27e8a3648850c5e458203ca5b94e","user":{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false},"body":"수정했습니다","created_at":"2021-07-21T07:00:00Z","updated_at":"2021-07-21T07:00:00Z","html_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119#discussion_r673711119","pull_request_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/comments/673711119"},"html":{"href":"https://github.com/woowa-techcamp-2021/deal-17/pull/119#discussion_r673711119"},"pull_request":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119"}},"start_line":null,"original_start_line":null,"start_side":null,"line":null,"original_line":39,"side":"RIGHT","in_reply_to_id":673703709},"pull_request":{"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119","id":693989579,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzOTg5NTc5","html_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119","diff_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119.diff","patch_url":"https://github.com/woowa-techcamp-2021/deal-17/pull/119.patch","issue_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119","number":119,"state":"open","locked":false,"title":"[feature/mypage] 로그인 시 메인페이지에서 회원정보 페이지로 이동","user":{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T02:13:48Z","updated_at":"2021-07-21T07:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0e3370b7911358702d3c4ea59cc923ad68fe2e97","assignee":{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false},"assignees":[{"login":"cothis","id":50898502,"node_id":"MDQ6VXNlcjUwODk4NTAy","avatar_url":"https://avatars.githubusercontent.com/u/50898502?v=4","gravatar_id":"","url":"https://api.github.com/users/cothis","html_url":"https://github.com/cothis","followers_url":"https://api.github.com/users/cothis/followers","following_url":"https://api.github.com/users/cothis/following{/other_user}","gists_url":"https://api.github.com/users/cothis/gists{/gist_id}","starred_url":"https://api.github.com/users/cothis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cothis/subscriptions","organizations_url":"https://api.github.com/users/cothis/orgs","repos_url":"https://api.github.com/users/cothis/repos","events_url":"https://api.github.com/users/cothis/events{/privacy}","received_events_url":"https://api.github.com/users/cothis/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":3160810249,"node_id":"MDU6TGFiZWwzMTYwODEwMjQ5","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/labels/frontend","name":"frontend","color":"8B0759","default":false,"description":""}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/commits","review_comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/comments","review_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/comments{/number}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119/comments","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/cb1568ae5f146dc08882a53ed030218de604cd3c","head":{"label":"woowa-techcamp-2021:feature/mypage","ref":"feature/mypage","sha":"cb1568ae5f146dc08882a53ed030218de604cd3c","user":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"repo":{"id":385124567,"node_id":"MDEwOlJlcG9zaXRvcnkzODUxMjQ1Njc=","name":"deal-17","full_name":"woowa-techcamp-2021/deal-17","private":false,"owner":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/woowa-techcamp-2021/deal-17","description":"우아마켓 - 17팀 김채은, 윤민호","fork":false,"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17","forks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/forks","keys_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/keys{/key_id}","collaborators_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/teams","hooks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/hooks","issue_events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/events{/number}","events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/events","assignees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/assignees{/user}","branches_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/branches{/branch}","tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/tags","blobs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/refs{/sha}","trees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/trees{/sha}","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/{sha}","languages_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/languages","stargazers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/stargazers","contributors_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contributors","subscribers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscribers","subscription_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscription","commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/commits{/sha}","git_commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/commits{/sha}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/comments{/number}","issue_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/comments{/number}","contents_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contents/{+path}","compare_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/compare/{base}...{head}","merges_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/merges","archive_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/downloads","issues_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues{/number}","pulls_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls{/number}","milestones_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/milestones{/number}","notifications_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/labels{/name}","releases_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/releases{/id}","deployments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/deployments","created_at":"2021-07-12T04:29:47Z","updated_at":"2021-07-20T15:57:20Z","pushed_at":"2021-07-21T06:59:54Z","git_url":"git://github.com/woowa-techcamp-2021/deal-17.git","ssh_url":"git@github.com:woowa-techcamp-2021/deal-17.git","clone_url":"https://github.com/woowa-techcamp-2021/deal-17.git","svn_url":"https://github.com/woowa-techcamp-2021/deal-17","homepage":"","size":488,"stargazers_count":1,"watchers_count":1,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":11,"license":null,"forks":0,"open_issues":11,"watchers":1,"default_branch":"dev"}},"base":{"label":"woowa-techcamp-2021:dev","ref":"dev","sha":"dbb8ee602d263a0cfdd8c7b3890fab0303a73472","user":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"repo":{"id":385124567,"node_id":"MDEwOlJlcG9zaXRvcnkzODUxMjQ1Njc=","name":"deal-17","full_name":"woowa-techcamp-2021/deal-17","private":false,"owner":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/woowa-techcamp-2021/deal-17","description":"우아마켓 - 17팀 김채은, 윤민호","fork":false,"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17","forks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/forks","keys_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/keys{/key_id}","collaborators_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/teams","hooks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/hooks","issue_events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/events{/number}","events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/events","assignees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/assignees{/user}","branches_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/branches{/branch}","tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/tags","blobs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/refs{/sha}","trees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/trees{/sha}","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/{sha}","languages_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/languages","stargazers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/stargazers","contributors_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contributors","subscribers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscribers","subscription_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/subscription","commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/commits{/sha}","git_commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/git/commits{/sha}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/comments{/number}","issue_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/comments{/number}","contents_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/contents/{+path}","compare_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/compare/{base}...{head}","merges_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/merges","archive_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/downloads","issues_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues{/number}","pulls_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls{/number}","milestones_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/milestones{/number}","notifications_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/labels{/name}","releases_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/releases{/id}","deployments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/deployments","created_at":"2021-07-12T04:29:47Z","updated_at":"2021-07-20T15:57:20Z","pushed_at":"2021-07-21T06:59:54Z","git_url":"git://github.com/woowa-techcamp-2021/deal-17.git","ssh_url":"git@github.com:woowa-techcamp-2021/deal-17.git","clone_url":"https://github.com/woowa-techcamp-2021/deal-17.git","svn_url":"https://github.com/woowa-techcamp-2021/deal-17","homepage":"","size":488,"stargazers_count":1,"watchers_count":1,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":11,"license":null,"forks":0,"open_issues":11,"watchers":1,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119"},"html":{"href":"https://github.com/woowa-techcamp-2021/deal-17/pull/119"},"issue":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119"},"comments":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/issues/119/comments"},"review_comments":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/comments"},"review_comment":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/pulls/119/commits"},"statuses":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-17/statuses/cb1568ae5f146dc08882a53ed030218de604cd3c"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:00Z","org":{"id":86336942,"login":"woowa-techcamp-2021","gravatar_id":"","url":"https://api.github.com/orgs/woowa-techcamp-2021","avatar_url":"https://avatars.githubusercontent.com/u/86336942?"}} +{"id":"17244791881","type":"IssueCommentEvent","actor":{"id":1096355,"login":"gvissers","display_login":"gvissers","gravatar_id":"","url":"https://api.github.com/users/gvissers","avatar_url":"https://avatars.githubusercontent.com/u/1096355?"},"repo":{"id":2496724,"name":"raduprv/Eternal-Lands","url":"https://api.github.com/repos/raduprv/Eternal-Lands"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/raduprv/Eternal-Lands/issues/137","repository_url":"https://api.github.com/repos/raduprv/Eternal-Lands","labels_url":"https://api.github.com/repos/raduprv/Eternal-Lands/issues/137/labels{/name}","comments_url":"https://api.github.com/repos/raduprv/Eternal-Lands/issues/137/comments","events_url":"https://api.github.com/repos/raduprv/Eternal-Lands/issues/137/events","html_url":"https://github.com/raduprv/Eternal-Lands/issues/137","id":941448779,"node_id":"MDU6SXNzdWU5NDE0NDg3Nzk=","number":137,"title":"Coordinating the next release","user":{"login":"pjbroad","id":1093822,"node_id":"MDQ6VXNlcjEwOTM4MjI=","avatar_url":"https://avatars.githubusercontent.com/u/1093822?v=4","gravatar_id":"","url":"https://api.github.com/users/pjbroad","html_url":"https://github.com/pjbroad","followers_url":"https://api.github.com/users/pjbroad/followers","following_url":"https://api.github.com/users/pjbroad/following{/other_user}","gists_url":"https://api.github.com/users/pjbroad/gists{/gist_id}","starred_url":"https://api.github.com/users/pjbroad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pjbroad/subscriptions","organizations_url":"https://api.github.com/users/pjbroad/orgs","repos_url":"https://api.github.com/users/pjbroad/repos","events_url":"https://api.github.com/users/pjbroad/events{/privacy}","received_events_url":"https://api.github.com/users/pjbroad/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":12,"created_at":"2021-07-11T13:04:06Z","updated_at":"2021-07-21T07:00:00Z","closed_at":null,"author_association":"COLLABORATOR","active_lock_reason":null,"body":"We have a full client update coming that includes maps updates (from @feeltheburn) and a server update (from @raduprv). This will be the next major version bump. We've been holding off major changes to the client code so but have the exciting prospect of #132 (from @gvissers) so we need to decide whether to merge that so we can test fully before the release. I'm in favour of merging #132 but what do others think? Are there other changes should we consider?","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/raduprv/Eternal-Lands/issues/comments/883942781","html_url":"https://github.com/raduprv/Eternal-Lands/issues/137#issuecomment-883942781","issue_url":"https://api.github.com/repos/raduprv/Eternal-Lands/issues/137","id":883942781,"node_id":"IC_kwDOACYY1M40r-V9","user":{"login":"gvissers","id":1096355,"node_id":"MDQ6VXNlcjEwOTYzNTU=","avatar_url":"https://avatars.githubusercontent.com/u/1096355?v=4","gravatar_id":"","url":"https://api.github.com/users/gvissers","html_url":"https://github.com/gvissers","followers_url":"https://api.github.com/users/gvissers/followers","following_url":"https://api.github.com/users/gvissers/following{/other_user}","gists_url":"https://api.github.com/users/gvissers/gists{/gist_id}","starred_url":"https://api.github.com/users/gvissers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gvissers/subscriptions","organizations_url":"https://api.github.com/users/gvissers/orgs","repos_url":"https://api.github.com/users/gvissers/repos","events_url":"https://api.github.com/users/gvissers/events{/privacy}","received_events_url":"https://api.github.com/users/gvissers/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:00Z","updated_at":"2021-07-21T07:00:00Z","author_association":"COLLABORATOR","body":"I have merged #132 and enabled USE_SSL by default so that the code can get wider testing. Things should work as they were on unencrypted connections. Encrypted connections can be tested on Learner's proxies, just add the following lines to servers.lst:\r\n```\r\nlrnr-ssl-main main proxy1.other-life.com 3000 encrypted Encrypted proxy to the main game server\r\nlrnr-ssl-test test proxy1.other-life.com 3001 encrypted Encrypted proxy to the test server\r\n```\r\nUsing one of these will pop up a warning that the certificate could not be verified because these aren't distributed with the client yet. For now, just ignore the warning and click \"Continue\".\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791886","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":385301722,"name":"alyssafrndz/automatic-scraper","url":"https://api.github.com/repos/alyssafrndz/automatic-scraper"},"payload":{"push_id":7561706191,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"794fa4f28993792071408e2776e7780ed6987a5c","before":"9dae3aea34a4919e4ac04e4e66395715ab0f0395","commits":[{"sha":"794fa4f28993792071408e2776e7780ed6987a5c","author":{"name":"Automated","email":"326b426f9ac7a96ed6baf62f8838565416d27df8@users.noreply.github.com"},"message":"Latest data: Wed Jul 21 06:59:58 UTC 2021","distinct":true,"url":"https://api.github.com/repos/alyssafrndz/automatic-scraper/commits/794fa4f28993792071408e2776e7780ed6987a5c"}]},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791890","type":"CreateEvent","actor":{"id":8135172,"login":"LeonellS","display_login":"LeonellS","gravatar_id":"","url":"https://api.github.com/users/LeonellS","avatar_url":"https://avatars.githubusercontent.com/u/8135172?"},"repo":{"id":388024722,"name":"LeonellS/leonells.github.io","url":"https://api.github.com/repos/LeonellS/leonells.github.io"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791895","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":373733367,"name":"jakubm/phx-data","url":"https://api.github.com/repos/jakubm/phx-data"},"payload":{"push_id":7561706203,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"79e4de7dc1b756e701b1ad9f9f6f0ba6dd23e120","before":"6c2192fdb4b3fd81ba21720e68ac172a8be40dbd","commits":[{"sha":"79e4de7dc1b756e701b1ad9f9f6f0ba6dd23e120","author":{"name":"Automated","email":"326b426f9ac7a96ed6baf62f8838565416d27df8@users.noreply.github.com"},"message":"Latest data: Tue Jul 20 23:59:58 MST 2021","distinct":true,"url":"https://api.github.com/repos/jakubm/phx-data/commits/79e4de7dc1b756e701b1ad9f9f6f0ba6dd23e120"}]},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791906","type":"DeleteEvent","actor":{"id":19972370,"login":"goutamp","display_login":"goutamp","gravatar_id":"","url":"https://api.github.com/users/goutamp","avatar_url":"https://avatars.githubusercontent.com/u/19972370?"},"repo":{"id":368109195,"name":"goutamp/sample3","url":"https://api.github.com/repos/goutamp/sample3"},"payload":{"ref":"release-v1.0.6","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791913","type":"WatchEvent","actor":{"id":57927650,"login":"accaplan","display_login":"accaplan","gravatar_id":"","url":"https://api.github.com/users/accaplan","avatar_url":"https://avatars.githubusercontent.com/u/57927650?"},"repo":{"id":261786851,"name":"sparkbox/vue-storybook-starter","url":"https://api.github.com/repos/sparkbox/vue-storybook-starter"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":524508,"login":"sparkbox","gravatar_id":"","url":"https://api.github.com/orgs/sparkbox","avatar_url":"https://avatars.githubusercontent.com/u/524508?"}} +{"id":"17244791914","type":"ForkEvent","actor":{"id":43455153,"login":"HantangFXW","display_login":"HantangFXW","gravatar_id":"","url":"https://api.github.com/users/HantangFXW","avatar_url":"https://avatars.githubusercontent.com/u/43455153?"},"repo":{"id":194904677,"name":"BorealisAI/de-simple","url":"https://api.github.com/repos/BorealisAI/de-simple"},"payload":{"forkee":{"id":388024723,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMjQ3MjM=","name":"de-simple","full_name":"HantangFXW/de-simple","private":false,"owner":{"login":"HantangFXW","id":43455153,"node_id":"MDQ6VXNlcjQzNDU1MTUz","avatar_url":"https://avatars.githubusercontent.com/u/43455153?v=4","gravatar_id":"","url":"https://api.github.com/users/HantangFXW","html_url":"https://github.com/HantangFXW","followers_url":"https://api.github.com/users/HantangFXW/followers","following_url":"https://api.github.com/users/HantangFXW/following{/other_user}","gists_url":"https://api.github.com/users/HantangFXW/gists{/gist_id}","starred_url":"https://api.github.com/users/HantangFXW/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/HantangFXW/subscriptions","organizations_url":"https://api.github.com/users/HantangFXW/orgs","repos_url":"https://api.github.com/users/HantangFXW/repos","events_url":"https://api.github.com/users/HantangFXW/events{/privacy}","received_events_url":"https://api.github.com/users/HantangFXW/received_events","type":"User","site_admin":false},"html_url":"https://github.com/HantangFXW/de-simple","description":"Diachronic Embedding for Temporal Knowledge Graph Completion","fork":true,"url":"https://api.github.com/repos/HantangFXW/de-simple","forks_url":"https://api.github.com/repos/HantangFXW/de-simple/forks","keys_url":"https://api.github.com/repos/HantangFXW/de-simple/keys{/key_id}","collaborators_url":"https://api.github.com/repos/HantangFXW/de-simple/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/HantangFXW/de-simple/teams","hooks_url":"https://api.github.com/repos/HantangFXW/de-simple/hooks","issue_events_url":"https://api.github.com/repos/HantangFXW/de-simple/issues/events{/number}","events_url":"https://api.github.com/repos/HantangFXW/de-simple/events","assignees_url":"https://api.github.com/repos/HantangFXW/de-simple/assignees{/user}","branches_url":"https://api.github.com/repos/HantangFXW/de-simple/branches{/branch}","tags_url":"https://api.github.com/repos/HantangFXW/de-simple/tags","blobs_url":"https://api.github.com/repos/HantangFXW/de-simple/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/HantangFXW/de-simple/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/HantangFXW/de-simple/git/refs{/sha}","trees_url":"https://api.github.com/repos/HantangFXW/de-simple/git/trees{/sha}","statuses_url":"https://api.github.com/repos/HantangFXW/de-simple/statuses/{sha}","languages_url":"https://api.github.com/repos/HantangFXW/de-simple/languages","stargazers_url":"https://api.github.com/repos/HantangFXW/de-simple/stargazers","contributors_url":"https://api.github.com/repos/HantangFXW/de-simple/contributors","subscribers_url":"https://api.github.com/repos/HantangFXW/de-simple/subscribers","subscription_url":"https://api.github.com/repos/HantangFXW/de-simple/subscription","commits_url":"https://api.github.com/repos/HantangFXW/de-simple/commits{/sha}","git_commits_url":"https://api.github.com/repos/HantangFXW/de-simple/git/commits{/sha}","comments_url":"https://api.github.com/repos/HantangFXW/de-simple/comments{/number}","issue_comment_url":"https://api.github.com/repos/HantangFXW/de-simple/issues/comments{/number}","contents_url":"https://api.github.com/repos/HantangFXW/de-simple/contents/{+path}","compare_url":"https://api.github.com/repos/HantangFXW/de-simple/compare/{base}...{head}","merges_url":"https://api.github.com/repos/HantangFXW/de-simple/merges","archive_url":"https://api.github.com/repos/HantangFXW/de-simple/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/HantangFXW/de-simple/downloads","issues_url":"https://api.github.com/repos/HantangFXW/de-simple/issues{/number}","pulls_url":"https://api.github.com/repos/HantangFXW/de-simple/pulls{/number}","milestones_url":"https://api.github.com/repos/HantangFXW/de-simple/milestones{/number}","notifications_url":"https://api.github.com/repos/HantangFXW/de-simple/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/HantangFXW/de-simple/labels{/name}","releases_url":"https://api.github.com/repos/HantangFXW/de-simple/releases{/id}","deployments_url":"https://api.github.com/repos/HantangFXW/de-simple/deployments","created_at":"2021-07-21T07:00:00Z","updated_at":"2021-07-14T06:55:46Z","pushed_at":"2019-12-19T20:46:21Z","git_url":"git://github.com/HantangFXW/de-simple.git","ssh_url":"git@github.com:HantangFXW/de-simple.git","clone_url":"https://github.com/HantangFXW/de-simple.git","svn_url":"https://github.com/HantangFXW/de-simple","homepage":"","size":28533,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":38730800,"login":"BorealisAI","gravatar_id":"","url":"https://api.github.com/orgs/BorealisAI","avatar_url":"https://avatars.githubusercontent.com/u/38730800?"}} +{"id":"17244791921","type":"PullRequestEvent","actor":{"id":76930969,"login":"agenius-mohammed-ali","display_login":"agenius-mohammed-ali","gravatar_id":"","url":"https://api.github.com/users/agenius-mohammed-ali","avatar_url":"https://avatars.githubusercontent.com/u/76930969?"},"repo":{"id":341858207,"name":"saranya-ag/DGEobj.plots","url":"https://api.github.com/repos/saranya-ag/DGEobj.plots"},"payload":{"action":"opened","number":13,"pull_request":{"url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/13","id":694104861,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODYx","html_url":"https://github.com/saranya-ag/DGEobj.plots/pull/13","diff_url":"https://github.com/saranya-ag/DGEobj.plots/pull/13.diff","patch_url":"https://github.com/saranya-ag/DGEobj.plots/pull/13.patch","issue_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues/13","number":13,"state":"open","locked":false,"title":"Develop","user":{"login":"agenius-mohammed-ali","id":76930969,"node_id":"MDQ6VXNlcjc2OTMwOTY5","avatar_url":"https://avatars.githubusercontent.com/u/76930969?v=4","gravatar_id":"","url":"https://api.github.com/users/agenius-mohammed-ali","html_url":"https://github.com/agenius-mohammed-ali","followers_url":"https://api.github.com/users/agenius-mohammed-ali/followers","following_url":"https://api.github.com/users/agenius-mohammed-ali/following{/other_user}","gists_url":"https://api.github.com/users/agenius-mohammed-ali/gists{/gist_id}","starred_url":"https://api.github.com/users/agenius-mohammed-ali/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/agenius-mohammed-ali/subscriptions","organizations_url":"https://api.github.com/users/agenius-mohammed-ali/orgs","repos_url":"https://api.github.com/users/agenius-mohammed-ali/repos","events_url":"https://api.github.com/users/agenius-mohammed-ali/events{/privacy}","received_events_url":"https://api.github.com/users/agenius-mohammed-ali/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:00:00Z","updated_at":"2021-07-21T07:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/13/commits","review_comments_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/13/comments","review_comment_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/comments{/number}","comments_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues/13/comments","statuses_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/statuses/4bbd2e9bbafc54bafc6d8f7a502d6d93e6afe485","head":{"label":"agenius-mohammed-ali:develop","ref":"develop","sha":"4bbd2e9bbafc54bafc6d8f7a502d6d93e6afe485","user":{"login":"agenius-mohammed-ali","id":76930969,"node_id":"MDQ6VXNlcjc2OTMwOTY5","avatar_url":"https://avatars.githubusercontent.com/u/76930969?v=4","gravatar_id":"","url":"https://api.github.com/users/agenius-mohammed-ali","html_url":"https://github.com/agenius-mohammed-ali","followers_url":"https://api.github.com/users/agenius-mohammed-ali/followers","following_url":"https://api.github.com/users/agenius-mohammed-ali/following{/other_user}","gists_url":"https://api.github.com/users/agenius-mohammed-ali/gists{/gist_id}","starred_url":"https://api.github.com/users/agenius-mohammed-ali/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/agenius-mohammed-ali/subscriptions","organizations_url":"https://api.github.com/users/agenius-mohammed-ali/orgs","repos_url":"https://api.github.com/users/agenius-mohammed-ali/repos","events_url":"https://api.github.com/users/agenius-mohammed-ali/events{/privacy}","received_events_url":"https://api.github.com/users/agenius-mohammed-ali/received_events","type":"User","site_admin":false},"repo":{"id":328005118,"node_id":"MDEwOlJlcG9zaXRvcnkzMjgwMDUxMTg=","name":"DGEobj.plots","full_name":"agenius-mohammed-ali/DGEobj.plots","private":false,"owner":{"login":"agenius-mohammed-ali","id":76930969,"node_id":"MDQ6VXNlcjc2OTMwOTY5","avatar_url":"https://avatars.githubusercontent.com/u/76930969?v=4","gravatar_id":"","url":"https://api.github.com/users/agenius-mohammed-ali","html_url":"https://github.com/agenius-mohammed-ali","followers_url":"https://api.github.com/users/agenius-mohammed-ali/followers","following_url":"https://api.github.com/users/agenius-mohammed-ali/following{/other_user}","gists_url":"https://api.github.com/users/agenius-mohammed-ali/gists{/gist_id}","starred_url":"https://api.github.com/users/agenius-mohammed-ali/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/agenius-mohammed-ali/subscriptions","organizations_url":"https://api.github.com/users/agenius-mohammed-ali/orgs","repos_url":"https://api.github.com/users/agenius-mohammed-ali/repos","events_url":"https://api.github.com/users/agenius-mohammed-ali/events{/privacy}","received_events_url":"https://api.github.com/users/agenius-mohammed-ali/received_events","type":"User","site_admin":false},"html_url":"https://github.com/agenius-mohammed-ali/DGEobj.plots","description":"DGE Plotting tools designed to work with the DGEobj data structure.","fork":true,"url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots","forks_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/forks","keys_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/keys{/key_id}","collaborators_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/teams","hooks_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/hooks","issue_events_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/issues/events{/number}","events_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/events","assignees_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/assignees{/user}","branches_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/branches{/branch}","tags_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/tags","blobs_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/git/refs{/sha}","trees_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/git/trees{/sha}","statuses_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/statuses/{sha}","languages_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/languages","stargazers_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/stargazers","contributors_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/contributors","subscribers_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/subscribers","subscription_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/subscription","commits_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/commits{/sha}","git_commits_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/git/commits{/sha}","comments_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/comments{/number}","issue_comment_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/issues/comments{/number}","contents_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/contents/{+path}","compare_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/compare/{base}...{head}","merges_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/merges","archive_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/downloads","issues_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/issues{/number}","pulls_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/pulls{/number}","milestones_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/milestones{/number}","notifications_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/labels{/name}","releases_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/releases{/id}","deployments_url":"https://api.github.com/repos/agenius-mohammed-ali/DGEobj.plots/deployments","created_at":"2021-01-08T20:26:49Z","updated_at":"2021-02-15T05:25:39Z","pushed_at":"2021-07-20T14:49:07Z","git_url":"git://github.com/agenius-mohammed-ali/DGEobj.plots.git","ssh_url":"git@github.com:agenius-mohammed-ali/DGEobj.plots.git","clone_url":"https://github.com/agenius-mohammed-ali/DGEobj.plots.git","svn_url":"https://github.com/agenius-mohammed-ali/DGEobj.plots","homepage":null,"size":416,"stargazers_count":0,"watchers_count":0,"language":"R","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"base":{"label":"saranya-ag:develop","ref":"develop","sha":"9e0b7d697dab6972b70f3139b5a04dccb14562c4","user":{"login":"saranya-ag","id":51917619,"node_id":"MDQ6VXNlcjUxOTE3NjE5","avatar_url":"https://avatars.githubusercontent.com/u/51917619?v=4","gravatar_id":"","url":"https://api.github.com/users/saranya-ag","html_url":"https://github.com/saranya-ag","followers_url":"https://api.github.com/users/saranya-ag/followers","following_url":"https://api.github.com/users/saranya-ag/following{/other_user}","gists_url":"https://api.github.com/users/saranya-ag/gists{/gist_id}","starred_url":"https://api.github.com/users/saranya-ag/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/saranya-ag/subscriptions","organizations_url":"https://api.github.com/users/saranya-ag/orgs","repos_url":"https://api.github.com/users/saranya-ag/repos","events_url":"https://api.github.com/users/saranya-ag/events{/privacy}","received_events_url":"https://api.github.com/users/saranya-ag/received_events","type":"User","site_admin":false},"repo":{"id":341858207,"node_id":"MDEwOlJlcG9zaXRvcnkzNDE4NTgyMDc=","name":"DGEobj.plots","full_name":"saranya-ag/DGEobj.plots","private":false,"owner":{"login":"saranya-ag","id":51917619,"node_id":"MDQ6VXNlcjUxOTE3NjE5","avatar_url":"https://avatars.githubusercontent.com/u/51917619?v=4","gravatar_id":"","url":"https://api.github.com/users/saranya-ag","html_url":"https://github.com/saranya-ag","followers_url":"https://api.github.com/users/saranya-ag/followers","following_url":"https://api.github.com/users/saranya-ag/following{/other_user}","gists_url":"https://api.github.com/users/saranya-ag/gists{/gist_id}","starred_url":"https://api.github.com/users/saranya-ag/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/saranya-ag/subscriptions","organizations_url":"https://api.github.com/users/saranya-ag/orgs","repos_url":"https://api.github.com/users/saranya-ag/repos","events_url":"https://api.github.com/users/saranya-ag/events{/privacy}","received_events_url":"https://api.github.com/users/saranya-ag/received_events","type":"User","site_admin":false},"html_url":"https://github.com/saranya-ag/DGEobj.plots","description":"DGE Plotting tools designed to work with the DGEobj data structure.","fork":true,"url":"https://api.github.com/repos/saranya-ag/DGEobj.plots","forks_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/forks","keys_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/keys{/key_id}","collaborators_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/teams","hooks_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/hooks","issue_events_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues/events{/number}","events_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/events","assignees_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/assignees{/user}","branches_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/branches{/branch}","tags_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/tags","blobs_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/git/refs{/sha}","trees_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/git/trees{/sha}","statuses_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/statuses/{sha}","languages_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/languages","stargazers_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/stargazers","contributors_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/contributors","subscribers_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/subscribers","subscription_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/subscription","commits_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/commits{/sha}","git_commits_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/git/commits{/sha}","comments_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/comments{/number}","issue_comment_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues/comments{/number}","contents_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/contents/{+path}","compare_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/compare/{base}...{head}","merges_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/merges","archive_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/downloads","issues_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues{/number}","pulls_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls{/number}","milestones_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/milestones{/number}","notifications_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/labels{/name}","releases_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/releases{/id}","deployments_url":"https://api.github.com/repos/saranya-ag/DGEobj.plots/deployments","created_at":"2021-02-24T10:10:55Z","updated_at":"2021-02-24T10:10:56Z","pushed_at":"2021-07-20T14:49:09Z","git_url":"git://github.com/saranya-ag/DGEobj.plots.git","ssh_url":"git@github.com:saranya-ag/DGEobj.plots.git","clone_url":"https://github.com/saranya-ag/DGEobj.plots.git","svn_url":"https://github.com/saranya-ag/DGEobj.plots","homepage":null,"size":344,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":null,"forks":0,"open_issues":5,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/13"},"html":{"href":"https://github.com/saranya-ag/DGEobj.plots/pull/13"},"issue":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues/13"},"comments":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/issues/13/comments"},"review_comments":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/13/comments"},"review_comment":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/pulls/13/commits"},"statuses":{"href":"https://api.github.com/repos/saranya-ag/DGEobj.plots/statuses/4bbd2e9bbafc54bafc6d8f7a502d6d93e6afe485"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":true,"commits":87,"additions":7129,"deletions":2640,"changed_files":37}},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791922","type":"PushEvent","actor":{"id":4085151,"login":"umbros","display_login":"umbros","gravatar_id":"","url":"https://api.github.com/users/umbros","avatar_url":"https://avatars.githubusercontent.com/u/4085151?"},"repo":{"id":357874499,"name":"pcm-dpc/DPC-Bollettini-Criticita-Idrogeologica-Idraulica","url":"https://api.github.com/repos/pcm-dpc/DPC-Bollettini-Criticita-Idrogeologica-Idraulica"},"payload":{"push_id":7561706207,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8f1aeffcb0e28ad03cb027dee932ad497704437e","before":"f185084404afc6ed8f87fded2765b0df87b5788c","commits":[{"sha":"8f1aeffcb0e28ad03cb027dee932ad497704437e","author":{"name":"pcm-dpc","email":"b0251f6150d8b49ab6136a836e55d225b09599de@protezionecivile.it"},"message":"Published by DPC Github Pipeline","distinct":true,"url":"https://api.github.com/repos/pcm-dpc/DPC-Bollettini-Criticita-Idrogeologica-Idraulica/commits/8f1aeffcb0e28ad03cb027dee932ad497704437e"}]},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":61385480,"login":"pcm-dpc","gravatar_id":"","url":"https://api.github.com/orgs/pcm-dpc","avatar_url":"https://avatars.githubusercontent.com/u/61385480?"}} +{"id":"17244791940","type":"PushEvent","actor":{"id":4061040,"login":"irJERAD","display_login":"irJERAD","gravatar_id":"","url":"https://api.github.com/users/irJERAD","avatar_url":"https://avatars.githubusercontent.com/u/4061040?"},"repo":{"id":359328023,"name":"irJERAD/jerad","url":"https://api.github.com/repos/irJERAD/jerad"},"payload":{"push_id":7561706222,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"369bcda906d4e4887653f9492279f4a62e5ea1f9","before":"04d2463b34049d5fb7685dc31f20cab93ab5fd61","commits":[{"sha":"369bcda906d4e4887653f9492279f4a62e5ea1f9","author":{"name":"Jerad","email":"07180e29522c63a29aa310901dd2038f90e083ee@gmail.com"},"message":"this should clear things up","distinct":true,"url":"https://api.github.com/repos/irJERAD/jerad/commits/369bcda906d4e4887653f9492279f4a62e5ea1f9"}]},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791942","type":"PushEvent","actor":{"id":34388810,"login":"NickGeramanis","display_login":"NickGeramanis","gravatar_id":"","url":"https://api.github.com/users/NickGeramanis","avatar_url":"https://avatars.githubusercontent.com/u/34388810?"},"repo":{"id":382729875,"name":"NickGeramanis/denoising-inpainting-lbp","url":"https://api.github.com/repos/NickGeramanis/denoising-inpainting-lbp"},"payload":{"push_id":7561706213,"size":1,"distinct_size":1,"ref":"refs/heads/gh-pages","head":"37e4ed0691cf3eb94446f8806298c736fe2deee9","before":"ebd4f735508e9fb9087ffc11288b4516f16d5a3c","commits":[{"sha":"37e4ed0691cf3eb94446f8806298c736fe2deee9","author":{"name":"NickGer","email":"0441ee8f7690af8f679f359d20d088dabb70e20c@gmail.com"},"message":"changed to html, not yet completed","distinct":true,"url":"https://api.github.com/repos/NickGeramanis/denoising-inpainting-lbp/commits/37e4ed0691cf3eb94446f8806298c736fe2deee9"}]},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791943","type":"PushEvent","actor":{"id":56509042,"login":"shakhawat07","display_login":"shakhawat07","gravatar_id":"","url":"https://api.github.com/users/shakhawat07","avatar_url":"https://avatars.githubusercontent.com/u/56509042?"},"repo":{"id":387504787,"name":"shakhawat07/getting-started-bootstrap","url":"https://api.github.com/repos/shakhawat07/getting-started-bootstrap"},"payload":{"push_id":7561706218,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"8fdf0fed8cd4696808bad2f786eeb05631aab1f9","before":"c09b29903566b6b195e6b09d3e46090edec19b26","commits":[{"sha":"8fdf0fed8cd4696808bad2f786eeb05631aab1f9","author":{"name":"shakhawat07","email":"2d5003e191ce0b0270b024f1e58da60326159b48@gmail.com"},"message":"carousel.html added","distinct":true,"url":"https://api.github.com/repos/shakhawat07/getting-started-bootstrap/commits/8fdf0fed8cd4696808bad2f786eeb05631aab1f9"}]},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791952","type":"IssueCommentEvent","actor":{"id":29044647,"login":"NicolaZee","display_login":"NicolaZee","gravatar_id":"","url":"https://api.github.com/users/NicolaZee","avatar_url":"https://avatars.githubusercontent.com/u/29044647?"},"repo":{"id":65782263,"name":"NPBruce/valkyrie","url":"https://api.github.com/repos/NPBruce/valkyrie"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/NPBruce/valkyrie/issues/1451","repository_url":"https://api.github.com/repos/NPBruce/valkyrie","labels_url":"https://api.github.com/repos/NPBruce/valkyrie/issues/1451/labels{/name}","comments_url":"https://api.github.com/repos/NPBruce/valkyrie/issues/1451/comments","events_url":"https://api.github.com/repos/NPBruce/valkyrie/issues/1451/events","html_url":"https://github.com/NPBruce/valkyrie/issues/1451","id":802713736,"node_id":"MDU6SXNzdWU4MDI3MTM3MzY=","number":1451,"title":"[Feature] Add event visibility indicator ","user":{"login":"antontimmermans","id":42072372,"node_id":"MDQ6VXNlcjQyMDcyMzcy","avatar_url":"https://avatars.githubusercontent.com/u/42072372?v=4","gravatar_id":"","url":"https://api.github.com/users/antontimmermans","html_url":"https://github.com/antontimmermans","followers_url":"https://api.github.com/users/antontimmermans/followers","following_url":"https://api.github.com/users/antontimmermans/following{/other_user}","gists_url":"https://api.github.com/users/antontimmermans/gists{/gist_id}","starred_url":"https://api.github.com/users/antontimmermans/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/antontimmermans/subscriptions","organizations_url":"https://api.github.com/users/antontimmermans/orgs","repos_url":"https://api.github.com/users/antontimmermans/repos","events_url":"https://api.github.com/users/antontimmermans/events{/privacy}","received_events_url":"https://api.github.com/users/antontimmermans/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-02-06T14:29:52Z","updated_at":"2021-07-21T07:00:01Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"\r\n\r\n## Is your feature request related to a problem? Please describe.**\r\nWhen you move events around (for example to add variation to a scenario), it happens that an event that once had text and an active button becomes a 'silent event'. (and sometimes the other way around). \r\nThat event must become invisible (or silent).\r\nThis does not always happen, and it is difficult to spot.\r\n\r\n## Describe the solution you'd like\r\nA clear and concise description of what you want to happen.\r\nAdd a field + tickbox in the editor for **events** and **tokens** that shows if they are processed silently or not. In principle, it should who if the event or token contains `display=false` in its code'. With that tickbox, you should also be able to force it to be visible or not. \r\n `display=false` : Box is empty\r\n\r\n![image](https://user-images.githubusercontent.com/42072372/107120897-cdf61280-688f-11eb-8e73-1a119cb0c6d3.png)\r\n\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/NPBruce/valkyrie/issues/comments/883942784","html_url":"https://github.com/NPBruce/valkyrie/issues/1451#issuecomment-883942784","issue_url":"https://api.github.com/repos/NPBruce/valkyrie/issues/1451","id":883942784,"node_id":"IC_kwDOA-vB9840r-WA","user":{"login":"NicolaZee","id":29044647,"node_id":"MDQ6VXNlcjI5MDQ0NjQ3","avatar_url":"https://avatars.githubusercontent.com/u/29044647?v=4","gravatar_id":"","url":"https://api.github.com/users/NicolaZee","html_url":"https://github.com/NicolaZee","followers_url":"https://api.github.com/users/NicolaZee/followers","following_url":"https://api.github.com/users/NicolaZee/following{/other_user}","gists_url":"https://api.github.com/users/NicolaZee/gists{/gist_id}","starred_url":"https://api.github.com/users/NicolaZee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NicolaZee/subscriptions","organizations_url":"https://api.github.com/users/NicolaZee/orgs","repos_url":"https://api.github.com/users/NicolaZee/repos","events_url":"https://api.github.com/users/NicolaZee/events{/privacy}","received_events_url":"https://api.github.com/users/NicolaZee/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:01Z","updated_at":"2021-07-21T07:00:01Z","author_association":"NONE","body":"Good idea to show the Display Tick box. That way it is easier to spot the bug has occurred. But the underlying issue is a bug and sometime (hopefully) the bug will be fixed. So, I suggest not having the tick box enabled and instead raise an issue to request the bug to be fixed.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791956","type":"WatchEvent","actor":{"id":11945133,"login":"huangshichao","display_login":"huangshichao","gravatar_id":"","url":"https://api.github.com/users/huangshichao","avatar_url":"https://avatars.githubusercontent.com/u/11945133?"},"repo":{"id":138895417,"name":"bash-c/pin-in-CTF","url":"https://api.github.com/repos/bash-c/pin-in-CTF"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791962","type":"PushEvent","actor":{"id":69785449,"login":"koichimatz","display_login":"koichimatz","gravatar_id":"","url":"https://api.github.com/users/koichimatz","avatar_url":"https://avatars.githubusercontent.com/u/69785449?"},"repo":{"id":386122519,"name":"dwc-mimm/work-github","url":"https://api.github.com/repos/dwc-mimm/work-github"},"payload":{"push_id":7561706232,"size":1,"distinct_size":1,"ref":"refs/heads/make-header","head":"ee64ccf8ee50fca46c99e2b164f8eecee043f294","before":"c7d62be981e1f63054297a16a913017d9ebfd127","commits":[{"sha":"ee64ccf8ee50fca46c99e2b164f8eecee043f294","author":{"name":"koichimatz","email":"31cb1c31bfb4e160dd3b3f96703f086589adf6e6@gmail.com"},"message":"[Add]Header&Footer&Siderbar&Searchbox","distinct":true,"url":"https://api.github.com/repos/dwc-mimm/work-github/commits/ee64ccf8ee50fca46c99e2b164f8eecee043f294"}]},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":87454251,"login":"dwc-mimm","gravatar_id":"","url":"https://api.github.com/orgs/dwc-mimm","avatar_url":"https://avatars.githubusercontent.com/u/87454251?"}} +{"id":"17244791971","type":"PullRequestReviewEvent","actor":{"id":1019641,"login":"rvl","display_login":"rvl","gravatar_id":"","url":"https://api.github.com/users/rvl","avatar_url":"https://avatars.githubusercontent.com/u/1019641?"},"repo":{"id":173286031,"name":"input-output-hk/cardano-wallet","url":"https://api.github.com/repos/input-output-hk/cardano-wallet"},"payload":{"action":"created","review":{"id":711317243,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3MjQz","user":{"login":"rvl","id":1019641,"node_id":"MDQ6VXNlcjEwMTk2NDE=","avatar_url":"https://avatars.githubusercontent.com/u/1019641?v=4","gravatar_id":"","url":"https://api.github.com/users/rvl","html_url":"https://github.com/rvl","followers_url":"https://api.github.com/users/rvl/followers","following_url":"https://api.github.com/users/rvl/following{/other_user}","gists_url":"https://api.github.com/users/rvl/gists{/gist_id}","starred_url":"https://api.github.com/users/rvl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rvl/subscriptions","organizations_url":"https://api.github.com/users/rvl/orgs","repos_url":"https://api.github.com/users/rvl/repos","events_url":"https://api.github.com/users/rvl/events{/privacy}","received_events_url":"https://api.github.com/users/rvl/received_events","type":"User","site_admin":false},"body":":+1:","commit_id":"6331d91891bd1639873947dd9e0854534e9a4207","submitted_at":"2021-07-21T07:00:01Z","state":"approved","html_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770#pullrequestreview-711317243","pull_request_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/input-output-hk/cardano-wallet/pull/2770#pullrequestreview-711317243"},"pull_request":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770"}}},"pull_request":{"url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770","id":693500926,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNTAwOTI2","html_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770","diff_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770.diff","patch_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770.patch","issue_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770","number":2770,"state":"open","locked":false,"title":"switch to newer node in e2e docker workflow","user":{"login":"piotr-iohk","id":42900201,"node_id":"MDQ6VXNlcjQyOTAwMjAx","avatar_url":"https://avatars.githubusercontent.com/u/42900201?v=4","gravatar_id":"","url":"https://api.github.com/users/piotr-iohk","html_url":"https://github.com/piotr-iohk","followers_url":"https://api.github.com/users/piotr-iohk/followers","following_url":"https://api.github.com/users/piotr-iohk/following{/other_user}","gists_url":"https://api.github.com/users/piotr-iohk/gists{/gist_id}","starred_url":"https://api.github.com/users/piotr-iohk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotr-iohk/subscriptions","organizations_url":"https://api.github.com/users/piotr-iohk/orgs","repos_url":"https://api.github.com/users/piotr-iohk/repos","events_url":"https://api.github.com/users/piotr-iohk/events{/privacy}","received_events_url":"https://api.github.com/users/piotr-iohk/received_events","type":"User","site_admin":false},"body":"# Issue Number\r\n\r\n\r\n\r\n\r\n# Overview\r\n\r\n\r\n\r\n- [ ] switch to newer node in e2e docker workflow\r\n\r\n\r\n# Comments\r\n\r\nSince cardano-wallet master doesn't work with cardano-node `1.27.0` switching temporarily to `alonzo-white-1.1`.","created_at":"2021-07-20T14:10:28Z","updated_at":"2021-07-21T07:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ca5f44152d091000a4b646768b32571ddeeb26e8","assignee":{"login":"piotr-iohk","id":42900201,"node_id":"MDQ6VXNlcjQyOTAwMjAx","avatar_url":"https://avatars.githubusercontent.com/u/42900201?v=4","gravatar_id":"","url":"https://api.github.com/users/piotr-iohk","html_url":"https://github.com/piotr-iohk","followers_url":"https://api.github.com/users/piotr-iohk/followers","following_url":"https://api.github.com/users/piotr-iohk/following{/other_user}","gists_url":"https://api.github.com/users/piotr-iohk/gists{/gist_id}","starred_url":"https://api.github.com/users/piotr-iohk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotr-iohk/subscriptions","organizations_url":"https://api.github.com/users/piotr-iohk/orgs","repos_url":"https://api.github.com/users/piotr-iohk/repos","events_url":"https://api.github.com/users/piotr-iohk/events{/privacy}","received_events_url":"https://api.github.com/users/piotr-iohk/received_events","type":"User","site_admin":false},"assignees":[{"login":"piotr-iohk","id":42900201,"node_id":"MDQ6VXNlcjQyOTAwMjAx","avatar_url":"https://avatars.githubusercontent.com/u/42900201?v=4","gravatar_id":"","url":"https://api.github.com/users/piotr-iohk","html_url":"https://github.com/piotr-iohk","followers_url":"https://api.github.com/users/piotr-iohk/followers","following_url":"https://api.github.com/users/piotr-iohk/following{/other_user}","gists_url":"https://api.github.com/users/piotr-iohk/gists{/gist_id}","starred_url":"https://api.github.com/users/piotr-iohk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotr-iohk/subscriptions","organizations_url":"https://api.github.com/users/piotr-iohk/orgs","repos_url":"https://api.github.com/users/piotr-iohk/repos","events_url":"https://api.github.com/users/piotr-iohk/events{/privacy}","received_events_url":"https://api.github.com/users/piotr-iohk/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/commits","review_comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/comments","review_comment_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/comments{/number}","comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770/comments","statuses_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/6331d91891bd1639873947dd9e0854534e9a4207","head":{"label":"input-output-hk:piotr/e2e-tests-maintenance","ref":"piotr/e2e-tests-maintenance","sha":"6331d91891bd1639873947dd9e0854534e9a4207","user":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"repo":{"id":173286031,"node_id":"MDEwOlJlcG9zaXRvcnkxNzMyODYwMzE=","name":"cardano-wallet","full_name":"input-output-hk/cardano-wallet","private":false,"owner":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/input-output-hk/cardano-wallet","description":"HTTP server & command-line for managing UTxOs and HD wallets in Cardano.","fork":false,"url":"https://api.github.com/repos/input-output-hk/cardano-wallet","forks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/forks","keys_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/teams","hooks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/hooks","issue_events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/events{/number}","events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/events","assignees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/assignees{/user}","branches_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/branches{/branch}","tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/tags","blobs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/refs{/sha}","trees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/{sha}","languages_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/languages","stargazers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/stargazers","contributors_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contributors","subscribers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscribers","subscription_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscription","commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/commits{/sha}","git_commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/commits{/sha}","comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/comments{/number}","issue_comment_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/comments{/number}","contents_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contents/{+path}","compare_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/merges","archive_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/downloads","issues_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues{/number}","pulls_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls{/number}","milestones_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/milestones{/number}","notifications_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/labels{/name}","releases_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/releases{/id}","deployments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/deployments","created_at":"2019-03-01T10:50:58Z","updated_at":"2021-07-20T16:38:07Z","pushed_at":"2021-07-21T06:29:48Z","git_url":"git://github.com/input-output-hk/cardano-wallet.git","ssh_url":"git@github.com:input-output-hk/cardano-wallet.git","clone_url":"https://github.com/input-output-hk/cardano-wallet.git","svn_url":"https://github.com/input-output-hk/cardano-wallet","homepage":"","size":162328,"stargazers_count":426,"watchers_count":426,"language":"Haskell","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":86,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":61,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":86,"open_issues":61,"watchers":426,"default_branch":"master"}},"base":{"label":"input-output-hk:master","ref":"master","sha":"66d0d8d67b88710f275ce6dc374c3babf4e506dd","user":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"repo":{"id":173286031,"node_id":"MDEwOlJlcG9zaXRvcnkxNzMyODYwMzE=","name":"cardano-wallet","full_name":"input-output-hk/cardano-wallet","private":false,"owner":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/input-output-hk/cardano-wallet","description":"HTTP server & command-line for managing UTxOs and HD wallets in Cardano.","fork":false,"url":"https://api.github.com/repos/input-output-hk/cardano-wallet","forks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/forks","keys_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/teams","hooks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/hooks","issue_events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/events{/number}","events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/events","assignees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/assignees{/user}","branches_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/branches{/branch}","tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/tags","blobs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/refs{/sha}","trees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/{sha}","languages_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/languages","stargazers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/stargazers","contributors_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contributors","subscribers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscribers","subscription_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscription","commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/commits{/sha}","git_commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/commits{/sha}","comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/comments{/number}","issue_comment_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/comments{/number}","contents_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contents/{+path}","compare_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/merges","archive_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/downloads","issues_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues{/number}","pulls_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls{/number}","milestones_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/milestones{/number}","notifications_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/labels{/name}","releases_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/releases{/id}","deployments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/deployments","created_at":"2019-03-01T10:50:58Z","updated_at":"2021-07-20T16:38:07Z","pushed_at":"2021-07-21T06:29:48Z","git_url":"git://github.com/input-output-hk/cardano-wallet.git","ssh_url":"git@github.com:input-output-hk/cardano-wallet.git","clone_url":"https://github.com/input-output-hk/cardano-wallet.git","svn_url":"https://github.com/input-output-hk/cardano-wallet","homepage":"","size":162328,"stargazers_count":426,"watchers_count":426,"language":"Haskell","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":86,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":61,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":86,"open_issues":61,"watchers":426,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770"},"html":{"href":"https://github.com/input-output-hk/cardano-wallet/pull/2770"},"issue":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770"},"comments":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770/comments"},"review_comments":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/comments"},"review_comment":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/commits"},"statuses":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/6331d91891bd1639873947dd9e0854534e9a4207"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":12909177,"login":"input-output-hk","gravatar_id":"","url":"https://api.github.com/orgs/input-output-hk","avatar_url":"https://avatars.githubusercontent.com/u/12909177?"}} +{"id":"17244791972","type":"PullRequestReviewEvent","actor":{"id":1019641,"login":"rvl","display_login":"rvl","gravatar_id":"","url":"https://api.github.com/users/rvl","avatar_url":"https://avatars.githubusercontent.com/u/1019641?"},"repo":{"id":173286031,"name":"input-output-hk/cardano-wallet","url":"https://api.github.com/repos/input-output-hk/cardano-wallet"},"payload":{"action":"created","review":{"id":711317243,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3MjQz","user":{"login":"rvl","id":1019641,"node_id":"MDQ6VXNlcjEwMTk2NDE=","avatar_url":"https://avatars.githubusercontent.com/u/1019641?v=4","gravatar_id":"","url":"https://api.github.com/users/rvl","html_url":"https://github.com/rvl","followers_url":"https://api.github.com/users/rvl/followers","following_url":"https://api.github.com/users/rvl/following{/other_user}","gists_url":"https://api.github.com/users/rvl/gists{/gist_id}","starred_url":"https://api.github.com/users/rvl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rvl/subscriptions","organizations_url":"https://api.github.com/users/rvl/orgs","repos_url":"https://api.github.com/users/rvl/repos","events_url":"https://api.github.com/users/rvl/events{/privacy}","received_events_url":"https://api.github.com/users/rvl/received_events","type":"User","site_admin":false},"body":":+1:","commit_id":"6331d91891bd1639873947dd9e0854534e9a4207","submitted_at":"2021-07-21T07:00:01Z","state":"approved","html_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770#pullrequestreview-711317243","pull_request_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/input-output-hk/cardano-wallet/pull/2770#pullrequestreview-711317243"},"pull_request":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770"}}},"pull_request":{"url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770","id":693500926,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNTAwOTI2","html_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770","diff_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770.diff","patch_url":"https://github.com/input-output-hk/cardano-wallet/pull/2770.patch","issue_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770","number":2770,"state":"open","locked":false,"title":"switch to newer node in e2e docker workflow","user":{"login":"piotr-iohk","id":42900201,"node_id":"MDQ6VXNlcjQyOTAwMjAx","avatar_url":"https://avatars.githubusercontent.com/u/42900201?v=4","gravatar_id":"","url":"https://api.github.com/users/piotr-iohk","html_url":"https://github.com/piotr-iohk","followers_url":"https://api.github.com/users/piotr-iohk/followers","following_url":"https://api.github.com/users/piotr-iohk/following{/other_user}","gists_url":"https://api.github.com/users/piotr-iohk/gists{/gist_id}","starred_url":"https://api.github.com/users/piotr-iohk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotr-iohk/subscriptions","organizations_url":"https://api.github.com/users/piotr-iohk/orgs","repos_url":"https://api.github.com/users/piotr-iohk/repos","events_url":"https://api.github.com/users/piotr-iohk/events{/privacy}","received_events_url":"https://api.github.com/users/piotr-iohk/received_events","type":"User","site_admin":false},"body":"# Issue Number\r\n\r\n\r\n\r\n\r\n# Overview\r\n\r\n\r\n\r\n- [ ] switch to newer node in e2e docker workflow\r\n\r\n\r\n# Comments\r\n\r\nSince cardano-wallet master doesn't work with cardano-node `1.27.0` switching temporarily to `alonzo-white-1.1`.","created_at":"2021-07-20T14:10:28Z","updated_at":"2021-07-21T07:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ca5f44152d091000a4b646768b32571ddeeb26e8","assignee":{"login":"piotr-iohk","id":42900201,"node_id":"MDQ6VXNlcjQyOTAwMjAx","avatar_url":"https://avatars.githubusercontent.com/u/42900201?v=4","gravatar_id":"","url":"https://api.github.com/users/piotr-iohk","html_url":"https://github.com/piotr-iohk","followers_url":"https://api.github.com/users/piotr-iohk/followers","following_url":"https://api.github.com/users/piotr-iohk/following{/other_user}","gists_url":"https://api.github.com/users/piotr-iohk/gists{/gist_id}","starred_url":"https://api.github.com/users/piotr-iohk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotr-iohk/subscriptions","organizations_url":"https://api.github.com/users/piotr-iohk/orgs","repos_url":"https://api.github.com/users/piotr-iohk/repos","events_url":"https://api.github.com/users/piotr-iohk/events{/privacy}","received_events_url":"https://api.github.com/users/piotr-iohk/received_events","type":"User","site_admin":false},"assignees":[{"login":"piotr-iohk","id":42900201,"node_id":"MDQ6VXNlcjQyOTAwMjAx","avatar_url":"https://avatars.githubusercontent.com/u/42900201?v=4","gravatar_id":"","url":"https://api.github.com/users/piotr-iohk","html_url":"https://github.com/piotr-iohk","followers_url":"https://api.github.com/users/piotr-iohk/followers","following_url":"https://api.github.com/users/piotr-iohk/following{/other_user}","gists_url":"https://api.github.com/users/piotr-iohk/gists{/gist_id}","starred_url":"https://api.github.com/users/piotr-iohk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotr-iohk/subscriptions","organizations_url":"https://api.github.com/users/piotr-iohk/orgs","repos_url":"https://api.github.com/users/piotr-iohk/repos","events_url":"https://api.github.com/users/piotr-iohk/events{/privacy}","received_events_url":"https://api.github.com/users/piotr-iohk/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/commits","review_comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/comments","review_comment_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/comments{/number}","comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770/comments","statuses_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/6331d91891bd1639873947dd9e0854534e9a4207","head":{"label":"input-output-hk:piotr/e2e-tests-maintenance","ref":"piotr/e2e-tests-maintenance","sha":"6331d91891bd1639873947dd9e0854534e9a4207","user":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"repo":{"id":173286031,"node_id":"MDEwOlJlcG9zaXRvcnkxNzMyODYwMzE=","name":"cardano-wallet","full_name":"input-output-hk/cardano-wallet","private":false,"owner":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/input-output-hk/cardano-wallet","description":"HTTP server & command-line for managing UTxOs and HD wallets in Cardano.","fork":false,"url":"https://api.github.com/repos/input-output-hk/cardano-wallet","forks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/forks","keys_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/teams","hooks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/hooks","issue_events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/events{/number}","events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/events","assignees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/assignees{/user}","branches_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/branches{/branch}","tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/tags","blobs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/refs{/sha}","trees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/{sha}","languages_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/languages","stargazers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/stargazers","contributors_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contributors","subscribers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscribers","subscription_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscription","commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/commits{/sha}","git_commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/commits{/sha}","comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/comments{/number}","issue_comment_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/comments{/number}","contents_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contents/{+path}","compare_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/merges","archive_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/downloads","issues_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues{/number}","pulls_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls{/number}","milestones_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/milestones{/number}","notifications_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/labels{/name}","releases_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/releases{/id}","deployments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/deployments","created_at":"2019-03-01T10:50:58Z","updated_at":"2021-07-20T16:38:07Z","pushed_at":"2021-07-21T06:29:48Z","git_url":"git://github.com/input-output-hk/cardano-wallet.git","ssh_url":"git@github.com:input-output-hk/cardano-wallet.git","clone_url":"https://github.com/input-output-hk/cardano-wallet.git","svn_url":"https://github.com/input-output-hk/cardano-wallet","homepage":"","size":162328,"stargazers_count":426,"watchers_count":426,"language":"Haskell","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":86,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":61,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":86,"open_issues":61,"watchers":426,"default_branch":"master"}},"base":{"label":"input-output-hk:master","ref":"master","sha":"66d0d8d67b88710f275ce6dc374c3babf4e506dd","user":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"repo":{"id":173286031,"node_id":"MDEwOlJlcG9zaXRvcnkxNzMyODYwMzE=","name":"cardano-wallet","full_name":"input-output-hk/cardano-wallet","private":false,"owner":{"login":"input-output-hk","id":12909177,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTA5MTc3","avatar_url":"https://avatars.githubusercontent.com/u/12909177?v=4","gravatar_id":"","url":"https://api.github.com/users/input-output-hk","html_url":"https://github.com/input-output-hk","followers_url":"https://api.github.com/users/input-output-hk/followers","following_url":"https://api.github.com/users/input-output-hk/following{/other_user}","gists_url":"https://api.github.com/users/input-output-hk/gists{/gist_id}","starred_url":"https://api.github.com/users/input-output-hk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/input-output-hk/subscriptions","organizations_url":"https://api.github.com/users/input-output-hk/orgs","repos_url":"https://api.github.com/users/input-output-hk/repos","events_url":"https://api.github.com/users/input-output-hk/events{/privacy}","received_events_url":"https://api.github.com/users/input-output-hk/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/input-output-hk/cardano-wallet","description":"HTTP server & command-line for managing UTxOs and HD wallets in Cardano.","fork":false,"url":"https://api.github.com/repos/input-output-hk/cardano-wallet","forks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/forks","keys_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/teams","hooks_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/hooks","issue_events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/events{/number}","events_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/events","assignees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/assignees{/user}","branches_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/branches{/branch}","tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/tags","blobs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/refs{/sha}","trees_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/{sha}","languages_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/languages","stargazers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/stargazers","contributors_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contributors","subscribers_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscribers","subscription_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/subscription","commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/commits{/sha}","git_commits_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/git/commits{/sha}","comments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/comments{/number}","issue_comment_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/comments{/number}","contents_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/contents/{+path}","compare_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/merges","archive_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/downloads","issues_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues{/number}","pulls_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls{/number}","milestones_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/milestones{/number}","notifications_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/labels{/name}","releases_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/releases{/id}","deployments_url":"https://api.github.com/repos/input-output-hk/cardano-wallet/deployments","created_at":"2019-03-01T10:50:58Z","updated_at":"2021-07-20T16:38:07Z","pushed_at":"2021-07-21T06:29:48Z","git_url":"git://github.com/input-output-hk/cardano-wallet.git","ssh_url":"git@github.com:input-output-hk/cardano-wallet.git","clone_url":"https://github.com/input-output-hk/cardano-wallet.git","svn_url":"https://github.com/input-output-hk/cardano-wallet","homepage":"","size":162328,"stargazers_count":426,"watchers_count":426,"language":"Haskell","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":86,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":61,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":86,"open_issues":61,"watchers":426,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770"},"html":{"href":"https://github.com/input-output-hk/cardano-wallet/pull/2770"},"issue":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770"},"comments":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/issues/2770/comments"},"review_comments":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/comments"},"review_comment":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/pulls/2770/commits"},"statuses":{"href":"https://api.github.com/repos/input-output-hk/cardano-wallet/statuses/6331d91891bd1639873947dd9e0854534e9a4207"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":12909177,"login":"input-output-hk","gravatar_id":"","url":"https://api.github.com/orgs/input-output-hk","avatar_url":"https://avatars.githubusercontent.com/u/12909177?"}} +{"id":"17244791973","type":"WatchEvent","actor":{"id":3261505,"login":"cakefile","display_login":"cakefile","gravatar_id":"","url":"https://api.github.com/users/cakefile","avatar_url":"https://avatars.githubusercontent.com/u/3261505?"},"repo":{"id":189273689,"name":"agiletortoise/drafts-script-reference","url":"https://api.github.com/repos/agiletortoise/drafts-script-reference"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791974","type":"WatchEvent","actor":{"id":25742988,"login":"Geekhyt","display_login":"Geekhyt","gravatar_id":"","url":"https://api.github.com/users/Geekhyt","avatar_url":"https://avatars.githubusercontent.com/u/25742988?"},"repo":{"id":98029592,"name":"ziishaned/learn-regex","url":"https://api.github.com/repos/ziishaned/learn-regex"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791975","type":"WatchEvent","actor":{"id":74425534,"login":"sjoveska","display_login":"sjoveska","gravatar_id":"","url":"https://api.github.com/users/sjoveska","avatar_url":"https://avatars.githubusercontent.com/u/74425534?"},"repo":{"id":380509015,"name":"gabrieldim/Markdown-Crash-Course","url":"https://api.github.com/repos/gabrieldim/Markdown-Crash-Course"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791977","type":"WatchEvent","actor":{"id":32324169,"login":"cuteshuai","display_login":"cuteshuai","gravatar_id":"","url":"https://api.github.com/users/cuteshuai","avatar_url":"https://avatars.githubusercontent.com/u/32324169?"},"repo":{"id":11171548,"name":"nlohmann/json","url":"https://api.github.com/repos/nlohmann/json"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244791979","type":"PushEvent","actor":{"id":49210012,"login":"challenger009","display_login":"challenger009","gravatar_id":"","url":"https://api.github.com/users/challenger009","avatar_url":"https://avatars.githubusercontent.com/u/49210012?"},"repo":{"id":214436347,"name":"Ressource-Allocation/Cache-Allocation-Project","url":"https://api.github.com/repos/Ressource-Allocation/Cache-Allocation-Project"},"payload":{"push_id":7561706237,"size":1,"distinct_size":1,"ref":"refs/heads/temp","head":"056284ce8ad23a2ee5f5a7dd4acda53e1c1bef42","before":"7ee0ed7d9009cb753c8381090f8ab4800dc21aa3","commits":[{"sha":"056284ce8ad23a2ee5f5a7dd4acda53e1c1bef42","author":{"name":"challenger009","email":"7fade1806ceb0c6ee86c8831b6b2f2835ff2d6f5@supcom.tn"},"message":"update code","distinct":true,"url":"https://api.github.com/repos/Ressource-Allocation/Cache-Allocation-Project/commits/056284ce8ad23a2ee5f5a7dd4acda53e1c1bef42"}]},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":56404846,"login":"Ressource-Allocation","gravatar_id":"","url":"https://api.github.com/orgs/Ressource-Allocation","avatar_url":"https://avatars.githubusercontent.com/u/56404846?"}} +{"id":"17244791980","type":"PushEvent","actor":{"id":34688948,"login":"cozy-bot","display_login":"cozy-bot","gravatar_id":"","url":"https://api.github.com/users/cozy-bot","avatar_url":"https://avatars.githubusercontent.com/u/34688948?"},"repo":{"id":343777796,"name":"konnectors/tracemob","url":"https://api.github.com/repos/konnectors/tracemob"},"payload":{"push_id":7561706241,"size":1,"distinct_size":1,"ref":"refs/heads/build","head":"762b132fcb848c7bf911bb41a7f1cb6155256f09","before":"a916f7befdba7c476679d2eb88d18cb5a6be2ca1","commits":[{"sha":"762b132fcb848c7bf911bb41a7f1cb6155256f09","author":{"name":"Travis CI User","email":"e6cc0fb2b8dad4110ef62e9a33e5a8aa4e0f86d7@example.org"},"message":"publish: chore(deps): update dependency jest to v27\n\ngenerated from commit 7b39b2dc2cf4d739bceb2ebc90e06b43c2eb0b79","distinct":true,"url":"https://api.github.com/repos/konnectors/tracemob/commits/762b132fcb848c7bf911bb41a7f1cb6155256f09"}]},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":35696401,"login":"konnectors","gravatar_id":"","url":"https://api.github.com/orgs/konnectors","avatar_url":"https://avatars.githubusercontent.com/u/35696401?"}} +{"id":"17244791985","type":"WatchEvent","actor":{"id":18587417,"login":"micro-step","display_login":"micro-step","gravatar_id":"","url":"https://api.github.com/users/micro-step","avatar_url":"https://avatars.githubusercontent.com/u/18587417?"},"repo":{"id":130252887,"name":"amirgholami/SqueezeNext","url":"https://api.github.com/repos/amirgholami/SqueezeNext"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244792000","type":"DeleteEvent","actor":{"id":73186203,"login":"enthr","display_login":"enthr","gravatar_id":"","url":"https://api.github.com/users/enthr","avatar_url":"https://avatars.githubusercontent.com/u/73186203?"},"repo":{"id":386189669,"name":"enthr/first-contributions","url":"https://api.github.com/repos/enthr/first-contributions"},"payload":{"ref":"add-Jaimin-Prajapati","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244792007","type":"CreateEvent","actor":{"id":49699333,"login":"dependabot[bot]","display_login":"dependabot","gravatar_id":"","url":"https://api.github.com/users/dependabot[bot]","avatar_url":"https://avatars.githubusercontent.com/u/49699333?"},"repo":{"id":262461195,"name":"crowdbotics-apps/aviannasavatarmaker-16781","url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781"},"payload":{"ref":"dependabot/npm_and_yarn/react-native-0.62.3","ref_type":"branch","master_branch":"master","description":"This react_native application was built with Crowdbotics www.crowdbotics.com","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244792002","type":"PushEvent","actor":{"id":524596,"login":"Varriount","display_login":"Varriount","gravatar_id":"","url":"https://api.github.com/users/Varriount","avatar_url":"https://avatars.githubusercontent.com/u/524596?"},"repo":{"id":378265592,"name":"Varriount/Yoyo","url":"https://api.github.com/repos/Varriount/Yoyo"},"payload":{"push_id":7561706246,"size":0,"distinct_size":0,"ref":"refs/heads/remove-python-slugify","head":"67470dcf23d23503bcd12bb368c04faed05f26ff","before":"67470dcf23d23503bcd12bb368c04faed05f26ff","commits":[]},"public":true,"created_at":"2021-07-21T07:00:01Z"} +{"id":"17244792016","type":"PushEvent","actor":{"id":10910590,"login":"michelescarlato","display_login":"michelescarlato","gravatar_id":"","url":"https://api.github.com/users/michelescarlato","avatar_url":"https://avatars.githubusercontent.com/u/10910590?"},"repo":{"id":372401824,"name":"fasten-project/LCV-CM","url":"https://api.github.com/repos/fasten-project/LCV-CM"},"payload":{"push_id":7561706258,"size":1,"distinct_size":1,"ref":"refs/heads/maven-use-case","head":"bad489245aff2321d3815c2e973439458d70728e","before":"bc12e228e44814e1aebd23fd323ad85f7b173214","commits":[{"sha":"bad489245aff2321d3815c2e973439458d70728e","author":{"name":"Michele Scarlato","email":"7e6432671aece48a2c6d283607e7c6e3ccc8c2f2@gmail.com"},"message":"Added sleepycat (not supported), to insert `Berkeley` as possible keyword for bsd.","distinct":true,"url":"https://api.github.com/repos/fasten-project/LCV-CM/commits/bad489245aff2321d3815c2e973439458d70728e"}]},"public":true,"created_at":"2021-07-21T07:00:01Z","org":{"id":47090554,"login":"fasten-project","gravatar_id":"","url":"https://api.github.com/orgs/fasten-project","avatar_url":"https://avatars.githubusercontent.com/u/47090554?"}} +{"id":"17244792027","type":"PushEvent","actor":{"id":5637090,"login":"ruscur","display_login":"ruscur","gravatar_id":"","url":"https://api.github.com/users/ruscur","avatar_url":"https://avatars.githubusercontent.com/u/5637090?"},"repo":{"id":52412231,"name":"ruscur/skiboot","url":"https://api.github.com/repos/ruscur/skiboot"},"payload":{"push_id":7561706261,"size":61,"distinct_size":61,"ref":"refs/heads/_61_61__hw_chiptod__Abort_if_core_frequency_is_not_set_mbox_master","head":"caaf0e0bd72df87fd24c6d6d9ebde98ed56ad89a","before":"b47b6418b5f825648e20ecf0f9c835b8ed5d4ba8","commits":[{"sha":"cd9f63136278b16d5b6eaf664f312c75070815ef","author":{"name":"Nicholas Piggin","email":"24829078df5003d9dd1dd871ca2aeb0b1cf95ef4@gmail.com"},"message":"external/mambo: skiboot.tcl add POWER10 config\n\nCo-authored-by: Nicholas Piggin \nSigned-off-by: Nicholas Piggin \nCo-authored-by: Madhavan Srinivasan \nSigned-off-by: Madhavan Srinivasan \nCo-authored-by: Ravi Bangoria \nSigned-off-by: Ravi Bangoria \n[Folded Maddy's IMC changes and Ravi's DAWR changes - Vasant]\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/cd9f63136278b16d5b6eaf664f312c75070815ef"},{"sha":"88ee1f5255ee3ab4ce1826a5ba2a4ddc2a4e6c83","author":{"name":"Nicholas Piggin","email":"24829078df5003d9dd1dd871ca2aeb0b1cf95ef4@gmail.com"},"message":"Initial POWER10 enablement\n\nCo-authored-by: Nicholas Piggin \nSigned-off-by: Nicholas Piggin \nCo-authored-by: Vaidyanathan Srinivasan \nSigned-off-by: Vaidyanathan Srinivasan \nCo-authored-by: Michael Neuling \nSigned-off-by: Michael Neuling \nCo-authored-by: Vasant Hegde \nSigned-off-by: Vasant Hegde \nCo-authored-by: Mahesh Salgaonkar \nSigned-off-by: Mahesh Salgaonkar \nCo-authored-by: Cédric Le Goater \nSigned-off-by: Cédric Le Goater \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/88ee1f5255ee3ab4ce1826a5ba2a4ddc2a4e6c83"},{"sha":"01284fa003bfa730ac20335972dc2388cd9a5d54","author":{"name":"Oliver O'Halloran","email":"3dfaff8fa6ae977f042064a112a6aac576279b9b@gmail.com"},"message":"hw/p8-i2c: Add POWER10 support\n\nEarly P8s didn't have the I2C interrupt, but all the subsequent chips\nhave one. Flip the interrupt support checking so the old chips are the\nspecial case rather than having to add a new entry for every new chip.\n\nP10 added several additional flag registers and moved the existing\nflag register. The actual data bits have not changed so the existing\nhandshake protocol between the OCC and OPAL works just fine.\n\nSigned-off-by: Oliver O'Halloran \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/01284fa003bfa730ac20335972dc2388cd9a5d54"},{"sha":"ac105b395cbdbbf649de3c49929b93d3a6a80ef3","author":{"name":"Michael Neuling","email":"7b0ab45a730e48a69239010b5b8fe5fa4e8eaac6@neuling.org"},"message":"p10: Workaround core recovery issue\n\nThis works around a core recovery issue in P10. The workaround involves\nthe CME polling for a core recovery and performing the recovery\nprocedure itself.\n\nFor this to happen, the host leaves core recovery off (HID[5]) and\nthen masks the PC system checkstop. This patch does this.\n\nFirmware starts skiboot with recovery already off, so we just leave it\noff for longer and then mask the PC system checkstop. This makes the\nwindow longer where a core recovery can cause an xstop but this\nwindow is still small and can still only happens on boot.\n\nSigned-off-by: Michael Neuling \n[Added mambo check - Vasant]\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/ac105b395cbdbbf649de3c49929b93d3a6a80ef3"},{"sha":"33a66d56295521963b81f447f6bf05ab7f9796bd","author":{"name":"Nicholas Piggin","email":"24829078df5003d9dd1dd871ca2aeb0b1cf95ef4@gmail.com"},"message":"cpufeatures: Add POWER10 support\n\nSigned-off-by: Nicholas Piggin \nSigned-off-by: Ravi Bangoria \n[Folded Ravi's DAWR patch - Vasant]\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/33a66d56295521963b81f447f6bf05ab7f9796bd"},{"sha":"412dfc8f633b814b4cfc63e019b8c80f4204cc27","author":{"name":"Nicholas Piggin","email":"24829078df5003d9dd1dd871ca2aeb0b1cf95ef4@gmail.com"},"message":"chiptod: Add POWER10 support\n\nPOWER10 changes to use the SCOM addressing mode, as it was found to\nbe more robust than the core ID addressing mode.\n\nSigned-off-by: Nicholas Piggin \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/412dfc8f633b814b4cfc63e019b8c80f4204cc27"},{"sha":"6bac77d2e986fd8c1bf080a8972562d320ffd082","author":{"name":"Vaidyanathan Srinivasan","email":"c012decff9dffda4c10860ab2ba9eb53320d90bc@linux.ibm.com"},"message":"Basic P10 stop state support\n\nAdds support for STOP0 lite, STOP2 and STOP3 for Power10 with the\nfollowing latencies, residency requirements:\n\n latency residency\nstop0lite 1us 10us\nstop0 10us 100us\nstop2 20us 200us\nstop3 45us 450us\n\nSigned-off-by: Vaidyanathan Srinivasan \nSigned-off-by: Pratik R. Sampat \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/6bac77d2e986fd8c1bf080a8972562d320ffd082"},{"sha":"60f458dd03bfed6168723c41c3b75d1f182207de","author":{"name":"Cédric Le Goater","email":"2d8cace2254d4e43578d21933db015d84bcc468a@kaod.org"},"message":"plat/qemu/p10: add a POWER10 platform\n\nBMC is still defined as ast2500 but it should change to ast2600 when\navailable.\n\nSigned-off-by: Cédric Le Goater \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/60f458dd03bfed6168723c41c3b75d1f182207de"},{"sha":"bd3500d89b1225e843c5e9adb39840722e416823","author":{"name":"Cédric Le Goater","email":"2d8cace2254d4e43578d21933db015d84bcc468a@kaod.org"},"message":"psi/p10: Activate P10 interrupts\n\nBehave as P9 for now until we know more on P10. Interface should be\nthe same, apart from the size of the ESB pages.\n\nSigned-off-by: Cédric Le Goater \n[Fixed suprious interrupt issue - Vasant]\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/bd3500d89b1225e843c5e9adb39840722e416823"},{"sha":"b86f50172e82fde19defdc2ea249fe320a96edb4","author":{"name":"Klaus Heinrich Kiwi","email":"d568b8cd7ef64877891488bb3328757d2d21c847@linux.vnet.ibm.com"},"message":"external/gard: Enable Power10\n\nAdd Power10 support for opal-gard utility.\n\nSigned-off-by: Klaus Heinrich Kiwi \n[Folded test case fix and updated commit message - Vasant]\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/b86f50172e82fde19defdc2ea249fe320a96edb4"},{"sha":"5d08ac410914e6908b6b29f290c40fa2b8ff1b89","author":{"name":"Vasant Hegde","email":"a6d753843573f7bfed5075c1e875b0be8007a0ab@linux.vnet.ibm.com"},"message":"external/xscom-utils: Add P10 chip info\n\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/5d08ac410914e6908b6b29f290c40fa2b8ff1b89"},{"sha":"d30741730491f015560a1eee49d51f3127f6b914","author":{"name":"Vasant Hegde","email":"a6d753843573f7bfed5075c1e875b0be8007a0ab@linux.vnet.ibm.com"},"message":"external/opal-prd: Fix occ, homer node label search\n\nStarting P10, hostboot/HDAT will provide consistent reserved node name. It will\njust provide node name without starting string \"ibm,\". That will cause\n`pm-complex <*>` operation to fails.\n\nThis patch fixes above issue. For backward compatability purpose I have\nkept support for old variant of node name as well.\n\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/d30741730491f015560a1eee49d51f3127f6b914"},{"sha":"d85f42cbcf312f6fe4ef47f311124590a8f27b33","author":{"name":"Vaidyanathan Srinivasan","email":"c012decff9dffda4c10860ab2ba9eb53320d90bc@linux.ibm.com"},"message":"occ: Add POWER10 support\n\nAdd support for parsing OCC on Power10 to populate the pstate\ninformation. Also enables OCC on P10 Denali system.\n\nCo-authored-by: Pratik R. Sampat \nCo-authored-by: Vaidyanathan Srinivasan \nSigned-off-by: Pratik R. Sampat \nSigned-off-by: Vaidyanathan Srinivasan \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/d85f42cbcf312f6fe4ef47f311124590a8f27b33"},{"sha":"1320d894e9641c3cae78034046fb332c3cfe8936","author":{"name":"Ravi Bangoria","email":"c3c2a342b6d0b20c2fffcb500dabb9f78ad28a56@linux.ibm.com"},"message":"hdata: Add POWER10 support\n\nInitial P10 support\n - LPC : This contains two useful information:\n LPC MCTP Memory Window Base Address\n Second vUART console details\n - Enable memory-buffer mmio\n - Fix ipmi sensors\n IPMI sensors are deprecated in P10. Hence do not parse ipmi sensors.\n - I2C support\n - Detect PHB5\n - Create p10 xscom, xive, chiptod nodes\n - Set pa-features bit for 2nd DAWR\n Availability of 2nd DAWR depends on 0th bit of 64th byte of\n ibm,pa-features property. Set it for p10.\n\nCo-authored-by: Vasant Hegde \nSigned-off-by: Vasant Hegde \nCo-authored-by: Nicholas Piggin \nSigned-off-by: Nicholas Piggin \nCo-authored-by: Reza Arbab \nSigned-off-by: Reza Arbab \nCo-authored-by: Ravi Bangoria \nSigned-off-by: Ravi Bangoria \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/1320d894e9641c3cae78034046fb332c3cfe8936"},{"sha":"3a47c281ec3f05bff90e7a17782a5cf5c09e6e08","author":{"name":"Haren Myneni","email":"b227902ebd12300369f7adca5621d6a4c3524817@linux.ibm.com"},"message":"hdat/spira: Define ibm, primary-topology-index property per chip\n\nHDAT provides Topology ID table and the primary topology location on\nP10. This primary location points to primary topology entry in ID table\nwhich contains the primary topology index and this index is used to\ndefine the paste base address per chip.\n\nThis patch reads Topology ID table and the primary topology location\nfrom hdata and retrieves the primary topology index in the ID table.\n\nMake this primaty topology index value available with\nibm,primary-topology-index property per chip. VAS reads this property\nto setup paste base address for each chip.\n\nSigned-off-by: Haren Myneni \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/3a47c281ec3f05bff90e7a17782a5cf5c09e6e08"},{"sha":"8e73dfa2d5d098d19c595c3db98dfe83b418988f","author":{"name":"Haren Myneni","email":"b227902ebd12300369f7adca5621d6a4c3524817@linux.ibm.com"},"message":"hdat/spira: Add ibm, power10-vas-x string to VAS compatible property\n\nVAS SCOM base address and paste address format are changed on P10.\nThis patch adds ibm,power10-vas-x string to compatible property per\neach VAS node. This compatible string is used to define the paste\nbase address later during VAS initialization.\n\nAlso enables NX on P10 without adding any compatible string since\nthe NX SCOM base address is not changed.\n\nSigned-off-by: Haren Myneni \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/8e73dfa2d5d098d19c595c3db98dfe83b418988f"},{"sha":"92e4b3d365466f7a80abba9fcace72af8856c35b","author":{"name":"Vasant Hegde","email":"a6d753843573f7bfed5075c1e875b0be8007a0ab@linux.vnet.ibm.com"},"message":"hdata/P10: Fix xscom address and ibm, chip-id property\n\n`xscom_id` is deprecated in P10. Instead we should use topology ID's\n(\"Primary topology table index\") to calculate xscom address. Also\nuse (\"Processor fabric topology id\") for \"ibm,chip-id\" property.\n\nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/92e4b3d365466f7a80abba9fcace72af8856c35b"},{"sha":"e7a5c607d9ab90000435403ecff682bc72d3cc57","author":{"name":"Vasant Hegde","email":"a6d753843573f7bfed5075c1e875b0be8007a0ab@linux.vnet.ibm.com"},"message":"phys/P10: Use topology index to get phys mapping\n\nThis fixes multipchip rainier boot issue.\n\nfor Rainer:\n\nchip0: ibm,primary-topology-index = < 0x0>;\nchip1: ibm,primary-topology-index = < 0x4>;\nchip2: ibm,primary-topology-index = < 0x8>;\nchip3: ibm,primary-topology-index = < 0xc>;\n\nfor Denali:\n\nnode0:\nchip0: ibm,primary-topology-index = < 0x0>;\nchip1: ibm,primary-topology-index = < 0x1>;\nchip2: ibm,primary-topology-index = < 0x2>;\nchip3: ibm,primary-topology-index = < 0x3>;\n\nnode1:\nchip0: ibm,primary-topology-index = < 0x4>;\nchip1: ibm,primary-topology-index = < 0x5>;\nchip2: ibm,primary-topology-index = < 0x6>;\nchip3: ibm,primary-topology-index = < 0x7>;\n\nNote that bmc_create_node() gets called very early in the boot process.\nHence we have to traverse through HDAT ntuple to get right topology index.\n\nMay be we can optimize pcid_to_topology_idx() function as its pretty\nmuch duplicate of pcid_to_chip_id(). But for now lets keep it as\nseparate function.\n\nSigned-off-by: Vasant Hegde \nSigned-off-by: Ryan Grimm \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/e7a5c607d9ab90000435403ecff682bc72d3cc57"},{"sha":"a1a09b26a633865a135eaed969d5a1f0078cc77d","author":{"name":"Frederic Barrat","email":"c90240987c583ac0802fbeb88cb1684edc683e18@linux.ibm.com"},"message":"hdata/iohub: Read PCI Gen5 equalization settings for P10\n\nHDAT spec added fields to define the equalization settings for PCI\nGen5 link. Format is the same as PCI Gen4, so we just need to add\nextra fields in the \"ibm,lane-eq\" in the device tree.\n\nSigned-off-by: Frederic Barrat \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/a1a09b26a633865a135eaed969d5a1f0078cc77d"},{"sha":"a1be7faf3af01e388865db81248f0f3fcf8d59f9","author":{"name":"Oliver O'Halloran","email":"3dfaff8fa6ae977f042064a112a6aac576279b9b@gmail.com"},"message":"prd: Add base P10 support\n\nSigned-off-by: Oliver O'Halloran \nSigned-off-by: Vasant Hegde ","distinct":true,"url":"https://api.github.com/repos/ruscur/skiboot/commits/a1be7faf3af01e388865db81248f0f3fcf8d59f9"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792035","type":"PushEvent","actor":{"id":66377779,"login":"georgesunnyt","display_login":"georgesunnyt","gravatar_id":"","url":"https://api.github.com/users/georgesunnyt","avatar_url":"https://avatars.githubusercontent.com/u/66377779?"},"repo":{"id":387549810,"name":"georgesunnyt/portfolio","url":"https://api.github.com/repos/georgesunnyt/portfolio"},"payload":{"push_id":7561706272,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"76f5451eaf8ec6d24cb8367853af96c614c21dad","before":"eb100fe4a60c33aaec7a1c9f17ec9c649f433551","commits":[{"sha":"76f5451eaf8ec6d24cb8367853af96c614c21dad","author":{"name":"georgesunnyt","email":"37bc2366be960e25f54540d7d6812cecc994ebd5@gmail.com"},"message":"update navigation link for portfolio","distinct":true,"url":"https://api.github.com/repos/georgesunnyt/portfolio/commits/76f5451eaf8ec6d24cb8367853af96c614c21dad"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792058","type":"CreateEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388024725,"name":"thatjohn01/781116602","url":"https://api.github.com/repos/thatjohn01/781116602"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":"瞬间人生--一个聪明的傻子的故事_PDF下载_徐光炜","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792061","type":"PushEvent","actor":{"id":8517910,"login":"LombiqBot","display_login":"LombiqBot","gravatar_id":"","url":"https://api.github.com/users/LombiqBot","avatar_url":"https://avatars.githubusercontent.com/u/8517910?"},"repo":{"id":264168728,"name":"Lombiq/OrchardCMS.Poll","url":"https://api.github.com/repos/Lombiq/OrchardCMS.Poll"},"payload":{"push_id":7561706271,"size":0,"distinct_size":0,"ref":"refs/heads/master","head":"bfe0fdff97231d1d169486262e8d0548f594a1b7","before":"bfe0fdff97231d1d169486262e8d0548f594a1b7","commits":[]},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":8158177,"login":"Lombiq","gravatar_id":"","url":"https://api.github.com/orgs/Lombiq","avatar_url":"https://avatars.githubusercontent.com/u/8158177?"}} +{"id":"17244792072","type":"PullRequestEvent","actor":{"id":19240202,"login":"jonghopark95","display_login":"jonghopark95","gravatar_id":"","url":"https://api.github.com/users/jonghopark95","avatar_url":"https://avatars.githubusercontent.com/u/19240202?"},"repo":{"id":385130973,"name":"woowa-techcamp-2021/deal-7","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7"},"payload":{"action":"opened","number":48,"pull_request":{"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/48","id":694104865,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODY1","html_url":"https://github.com/woowa-techcamp-2021/deal-7/pull/48","diff_url":"https://github.com/woowa-techcamp-2021/deal-7/pull/48.diff","patch_url":"https://github.com/woowa-techcamp-2021/deal-7/pull/48.patch","issue_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/48","number":48,"state":"open","locked":false,"title":"[Feature/#13] 메인 페이지-카테고리 슬라이드 API 연결","user":{"login":"jonghopark95","id":19240202,"node_id":"MDQ6VXNlcjE5MjQwMjAy","avatar_url":"https://avatars.githubusercontent.com/u/19240202?v=4","gravatar_id":"","url":"https://api.github.com/users/jonghopark95","html_url":"https://github.com/jonghopark95","followers_url":"https://api.github.com/users/jonghopark95/followers","following_url":"https://api.github.com/users/jonghopark95/following{/other_user}","gists_url":"https://api.github.com/users/jonghopark95/gists{/gist_id}","starred_url":"https://api.github.com/users/jonghopark95/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonghopark95/subscriptions","organizations_url":"https://api.github.com/users/jonghopark95/orgs","repos_url":"https://api.github.com/users/jonghopark95/repos","events_url":"https://api.github.com/users/jonghopark95/events{/privacy}","received_events_url":"https://api.github.com/users/jonghopark95/received_events","type":"User","site_admin":false},"body":"## :bookmark_tabs: 제목\r\n\r\n### 카테고리 필터에 맞게 상품들 렌더링 (반환값이 없을 경우 텅~~~~~)\r\n\r\nhttps://user-images.githubusercontent.com/19240202/126444993-f388e801-01a0-4dc4-b529-37eefd293b96.mov\r\n\r\n\r\n### 로그인 했을 경우, 사용자가 이전에 좋아요 했던 상품 하트 \r\n> 로그인, 로그아웃 시에도 바로 바로 반영되도록 (영상 참고) \r\n\r\nhttps://user-images.githubusercontent.com/19240202/126445093-f1f71d55-a5b1-4d2e-9c65-2a2daf69793b.mov\r\n\r\n## :heavy_check_mark: 셀프 체크리스트\r\n\r\n> 최소 1명 이상의 assign을 받아야만 Merge 가능합니다.\r\n\r\n- [x] Warning Message가 발생하지 않았나요?\r\n- [x] Coding Convention을 준수했나요?\r\n\r\n## :speech_balloon: 작업 내용\r\n\r\n> 구현 내용 및 작업 했던 내역\r\n\r\n- [x] Close #13\r\n\r\n## :construction: PR 특이 사항\r\n\r\n> PR을 볼 때 주의깊게 봐야하거나 말하고 싶은 점\r\n\r\n- 로딩 스피너는 고민중입니다... 근데 후순위로 미뤄두려고요...\r\n- 코드가 슬슬 많이 더러워집니다 ㅜㅜ 시간 때문에 기능 구현과 코드의 리팩토링을 저울질 했는데 일단 기능구현이 우선이라 생각했습니다.","created_at":"2021-07-21T07:00:01Z","updated_at":"2021-07-21T07:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":{"login":"jonghopark95","id":19240202,"node_id":"MDQ6VXNlcjE5MjQwMjAy","avatar_url":"https://avatars.githubusercontent.com/u/19240202?v=4","gravatar_id":"","url":"https://api.github.com/users/jonghopark95","html_url":"https://github.com/jonghopark95","followers_url":"https://api.github.com/users/jonghopark95/followers","following_url":"https://api.github.com/users/jonghopark95/following{/other_user}","gists_url":"https://api.github.com/users/jonghopark95/gists{/gist_id}","starred_url":"https://api.github.com/users/jonghopark95/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonghopark95/subscriptions","organizations_url":"https://api.github.com/users/jonghopark95/orgs","repos_url":"https://api.github.com/users/jonghopark95/repos","events_url":"https://api.github.com/users/jonghopark95/events{/privacy}","received_events_url":"https://api.github.com/users/jonghopark95/received_events","type":"User","site_admin":false},"assignees":[{"login":"jonghopark95","id":19240202,"node_id":"MDQ6VXNlcjE5MjQwMjAy","avatar_url":"https://avatars.githubusercontent.com/u/19240202?v=4","gravatar_id":"","url":"https://api.github.com/users/jonghopark95","html_url":"https://github.com/jonghopark95","followers_url":"https://api.github.com/users/jonghopark95/followers","following_url":"https://api.github.com/users/jonghopark95/following{/other_user}","gists_url":"https://api.github.com/users/jonghopark95/gists{/gist_id}","starred_url":"https://api.github.com/users/jonghopark95/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonghopark95/subscriptions","organizations_url":"https://api.github.com/users/jonghopark95/orgs","repos_url":"https://api.github.com/users/jonghopark95/repos","events_url":"https://api.github.com/users/jonghopark95/events{/privacy}","received_events_url":"https://api.github.com/users/jonghopark95/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"Kwongiseok","id":18898526,"node_id":"MDQ6VXNlcjE4ODk4NTI2","avatar_url":"https://avatars.githubusercontent.com/u/18898526?v=4","gravatar_id":"","url":"https://api.github.com/users/Kwongiseok","html_url":"https://github.com/Kwongiseok","followers_url":"https://api.github.com/users/Kwongiseok/followers","following_url":"https://api.github.com/users/Kwongiseok/following{/other_user}","gists_url":"https://api.github.com/users/Kwongiseok/gists{/gist_id}","starred_url":"https://api.github.com/users/Kwongiseok/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kwongiseok/subscriptions","organizations_url":"https://api.github.com/users/Kwongiseok/orgs","repos_url":"https://api.github.com/users/Kwongiseok/repos","events_url":"https://api.github.com/users/Kwongiseok/events{/privacy}","received_events_url":"https://api.github.com/users/Kwongiseok/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":3163702595,"node_id":"MDU6TGFiZWwzMTYzNzAyNTk1","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/labels/BE","name":"BE","color":"674395","default":false,"description":"백엔드 개발"},{"id":3163702051,"node_id":"MDU6TGFiZWwzMTYzNzAyMDUx","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/labels/FE","name":"FE","color":"0E1A42","default":false,"description":"프론트엔드 개발"},{"id":3163705958,"node_id":"MDU6TGFiZWwzMTYzNzA1OTU4","url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/labels/%EC%9E%91%EC%97%85%20%EC%99%84%EB%A3%8C","name":"작업 완료","color":"9FBA8B","default":false,"description":""}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/48/commits","review_comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/48/comments","review_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/comments{/number}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/48/comments","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/statuses/637da1a12fab1b1186b042361cc951bb094e3e76","head":{"label":"woowa-techcamp-2021:feature/#13","ref":"feature/#13","sha":"637da1a12fab1b1186b042361cc951bb094e3e76","user":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"repo":{"id":385130973,"node_id":"MDEwOlJlcG9zaXRvcnkzODUxMzA5NzM=","name":"deal-7","full_name":"woowa-techcamp-2021/deal-7","private":false,"owner":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/woowa-techcamp-2021/deal-7","description":null,"fork":false,"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7","forks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/forks","keys_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/keys{/key_id}","collaborators_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/teams","hooks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/hooks","issue_events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/events{/number}","events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/events","assignees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/assignees{/user}","branches_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/branches{/branch}","tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/tags","blobs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/refs{/sha}","trees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/trees{/sha}","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/statuses/{sha}","languages_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/languages","stargazers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/stargazers","contributors_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/contributors","subscribers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/subscribers","subscription_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/subscription","commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/commits{/sha}","git_commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/commits{/sha}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/comments{/number}","issue_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/comments{/number}","contents_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/contents/{+path}","compare_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/compare/{base}...{head}","merges_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/merges","archive_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/downloads","issues_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues{/number}","pulls_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls{/number}","milestones_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/milestones{/number}","notifications_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/labels{/name}","releases_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/releases{/id}","deployments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/deployments","created_at":"2021-07-12T05:08:04Z","updated_at":"2021-07-21T01:18:56Z","pushed_at":"2021-07-21T07:00:01Z","git_url":"git://github.com/woowa-techcamp-2021/deal-7.git","ssh_url":"git@github.com:woowa-techcamp-2021/deal-7.git","clone_url":"https://github.com/woowa-techcamp-2021/deal-7.git","svn_url":"https://github.com/woowa-techcamp-2021/deal-7","homepage":null,"size":1747,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":6,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":1,"open_issues":6,"watchers":2,"default_branch":"dev"}},"base":{"label":"woowa-techcamp-2021:dev","ref":"dev","sha":"503ab8f0ec1137905ee8901d33a90b9a0274e397","user":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"repo":{"id":385130973,"node_id":"MDEwOlJlcG9zaXRvcnkzODUxMzA5NzM=","name":"deal-7","full_name":"woowa-techcamp-2021/deal-7","private":false,"owner":{"login":"woowa-techcamp-2021","id":86336942,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg2MzM2OTQy","avatar_url":"https://avatars.githubusercontent.com/u/86336942?v=4","gravatar_id":"","url":"https://api.github.com/users/woowa-techcamp-2021","html_url":"https://github.com/woowa-techcamp-2021","followers_url":"https://api.github.com/users/woowa-techcamp-2021/followers","following_url":"https://api.github.com/users/woowa-techcamp-2021/following{/other_user}","gists_url":"https://api.github.com/users/woowa-techcamp-2021/gists{/gist_id}","starred_url":"https://api.github.com/users/woowa-techcamp-2021/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/woowa-techcamp-2021/subscriptions","organizations_url":"https://api.github.com/users/woowa-techcamp-2021/orgs","repos_url":"https://api.github.com/users/woowa-techcamp-2021/repos","events_url":"https://api.github.com/users/woowa-techcamp-2021/events{/privacy}","received_events_url":"https://api.github.com/users/woowa-techcamp-2021/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/woowa-techcamp-2021/deal-7","description":null,"fork":false,"url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7","forks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/forks","keys_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/keys{/key_id}","collaborators_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/teams","hooks_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/hooks","issue_events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/events{/number}","events_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/events","assignees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/assignees{/user}","branches_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/branches{/branch}","tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/tags","blobs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/refs{/sha}","trees_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/trees{/sha}","statuses_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/statuses/{sha}","languages_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/languages","stargazers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/stargazers","contributors_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/contributors","subscribers_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/subscribers","subscription_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/subscription","commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/commits{/sha}","git_commits_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/git/commits{/sha}","comments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/comments{/number}","issue_comment_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/comments{/number}","contents_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/contents/{+path}","compare_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/compare/{base}...{head}","merges_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/merges","archive_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/downloads","issues_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues{/number}","pulls_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls{/number}","milestones_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/milestones{/number}","notifications_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/labels{/name}","releases_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/releases{/id}","deployments_url":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/deployments","created_at":"2021-07-12T05:08:04Z","updated_at":"2021-07-21T01:18:56Z","pushed_at":"2021-07-21T07:00:01Z","git_url":"git://github.com/woowa-techcamp-2021/deal-7.git","ssh_url":"git@github.com:woowa-techcamp-2021/deal-7.git","clone_url":"https://github.com/woowa-techcamp-2021/deal-7.git","svn_url":"https://github.com/woowa-techcamp-2021/deal-7","homepage":null,"size":1747,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":6,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":1,"open_issues":6,"watchers":2,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/48"},"html":{"href":"https://github.com/woowa-techcamp-2021/deal-7/pull/48"},"issue":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/48"},"comments":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/issues/48/comments"},"review_comments":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/48/comments"},"review_comment":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/pulls/48/commits"},"statuses":{"href":"https://api.github.com/repos/woowa-techcamp-2021/deal-7/statuses/637da1a12fab1b1186b042361cc951bb094e3e76"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":8,"additions":150,"deletions":43,"changed_files":8}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":86336942,"login":"woowa-techcamp-2021","gravatar_id":"","url":"https://api.github.com/orgs/woowa-techcamp-2021","avatar_url":"https://avatars.githubusercontent.com/u/86336942?"}} +{"id":"17244792075","type":"PushEvent","actor":{"id":73381050,"login":"Kunal0007","display_login":"Kunal0007","gravatar_id":"","url":"https://api.github.com/users/Kunal0007","avatar_url":"https://avatars.githubusercontent.com/u/73381050?"},"repo":{"id":383790190,"name":"Kunal0007/Shopping-cart","url":"https://api.github.com/repos/Kunal0007/Shopping-cart"},"payload":{"push_id":7561706290,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"3bf84f4457abbd012e9e32cc4597ccda37830d05","before":"19fdb24ed3c5d328eb9e68b9a0c032c83c6c1996","commits":[{"sha":"3bf84f4457abbd012e9e32cc4597ccda37830d05","author":{"name":"Kunal Patil","email":"9626b594ec2184340966475c58ae4ba89b546a58@users.noreply.github.com"},"message":"Delete index.css","distinct":true,"url":"https://api.github.com/repos/Kunal0007/Shopping-cart/commits/3bf84f4457abbd012e9e32cc4597ccda37830d05"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792079","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":208147593,"name":"influxdata/influxdb2-sample-data","url":"https://api.github.com/repos/influxdata/influxdb2-sample-data"},"payload":{"push_id":7561706299,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"6bb3fe1b85296d7476b43363399c89e541f5a31a","before":"bc7ea8502c0ff3594a360201df63c770a7b77a6f","commits":[{"sha":"6bb3fe1b85296d7476b43363399c89e541f5a31a","author":{"name":"github-actions","email":"17973dcf2e10b67f63417c88ca4c460b2c9439f2@users.noreply.github.com"},"message":"Adding LP and annotated CSV file","distinct":true,"url":"https://api.github.com/repos/influxdata/influxdb2-sample-data/commits/6bb3fe1b85296d7476b43363399c89e541f5a31a"}]},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":5713248,"login":"influxdata","gravatar_id":"","url":"https://api.github.com/orgs/influxdata","avatar_url":"https://avatars.githubusercontent.com/u/5713248?"}} +{"id":"17244792084","type":"CreateEvent","actor":{"id":8135172,"login":"LeonellS","display_login":"LeonellS","gravatar_id":"","url":"https://api.github.com/users/LeonellS","avatar_url":"https://avatars.githubusercontent.com/u/8135172?"},"repo":{"id":388024722,"name":"LeonellS/leonells.github.io","url":"https://api.github.com/repos/LeonellS/leonells.github.io"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792085","type":"PushEvent","actor":{"id":85987457,"login":"Nirmalya61","display_login":"Nirmalya61","gravatar_id":"","url":"https://api.github.com/users/Nirmalya61","avatar_url":"https://avatars.githubusercontent.com/u/85987457?"},"repo":{"id":388018240,"name":"Nirmalya61/New-folder","url":"https://api.github.com/repos/Nirmalya61/New-folder"},"payload":{"push_id":7561706304,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"456b2d0987d2d79d5f16702742124573b7f32008","before":"7827ff52436d8dee36467746da5d51d30e06d0db","commits":[{"sha":"456b2d0987d2d79d5f16702742124573b7f32008","author":{"name":"Nirmalya","email":"3a2789cf63cb1531afedba7ecb498a043ebe784b@users.noreply.github.com"},"message":"Create style.css","distinct":true,"url":"https://api.github.com/repos/Nirmalya61/New-folder/commits/456b2d0987d2d79d5f16702742124573b7f32008"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792086","type":"PushEvent","actor":{"id":9397845,"login":"ford442","display_login":"ford442","gravatar_id":"","url":"https://api.github.com/users/ford442","avatar_url":"https://avatars.githubusercontent.com/u/9397845?"},"repo":{"id":387942769,"name":"ford442/r3g","url":"https://api.github.com/repos/ford442/r3g"},"payload":{"push_id":7561706296,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"e3203e93ee89a6d222bcec006eeab6b9994dd250","before":"981a4a009b15de8b39a720f0451354cb8bf7762a","commits":[{"sha":"e3203e93ee89a6d222bcec006eeab6b9994dd250","author":{"name":"Noah Cohn","email":"28c606380e2d43e0ea2b0c4890117e1a35650978@gmail.com"},"message":"Delete array.h","distinct":true,"url":"https://api.github.com/repos/ford442/r3g/commits/e3203e93ee89a6d222bcec006eeab6b9994dd250"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792087","type":"CreateEvent","actor":{"id":86947924,"login":"JustInPosition","display_login":"JustInPosition","gravatar_id":"","url":"https://api.github.com/users/JustInPosition","avatar_url":"https://avatars.githubusercontent.com/u/86947924?"},"repo":{"id":388022686,"name":"PositionExchange/position-token","url":"https://api.github.com/repos/PositionExchange/position-token"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":86948262,"login":"PositionExchange","gravatar_id":"","url":"https://api.github.com/orgs/PositionExchange","avatar_url":"https://avatars.githubusercontent.com/u/86948262?"}} +{"id":"17244792092","type":"PushEvent","actor":{"id":67720330,"login":"Labruhtoory","display_login":"Labruhtoory","gravatar_id":"","url":"https://api.github.com/users/Labruhtoory","avatar_url":"https://avatars.githubusercontent.com/u/67720330?"},"repo":{"id":330309164,"name":"Labruhtoory/os_setups","url":"https://api.github.com/repos/Labruhtoory/os_setups"},"payload":{"push_id":7561706306,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"6604fc4b5b89c86fadef14cae2a41b8ec70f67de","before":"c6863e592b9be8d93819dd37ba67463a0eeb19f5","commits":[{"sha":"6604fc4b5b89c86fadef14cae2a41b8ec70f67de","author":{"name":"Labruhtoory","email":"ae98245dcac678d585d9d98787531f027ec0afcb@users.noreply.github.com"},"message":"Update deb_os.sh","distinct":true,"url":"https://api.github.com/repos/Labruhtoory/os_setups/commits/6604fc4b5b89c86fadef14cae2a41b8ec70f67de"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792098","type":"IssueCommentEvent","actor":{"id":25307165,"login":"JNKPercona","display_login":"JNKPercona","gravatar_id":"","url":"https://api.github.com/users/JNKPercona","avatar_url":"https://avatars.githubusercontent.com/u/25307165?"},"repo":{"id":114283030,"name":"Percona-Lab/pmm-submodules","url":"https://api.github.com/repos/Percona-Lab/pmm-submodules"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/issues/1919","repository_url":"https://api.github.com/repos/Percona-Lab/pmm-submodules","labels_url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/issues/1919/labels{/name}","comments_url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/issues/1919/comments","events_url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/issues/1919/events","html_url":"https://github.com/Percona-Lab/pmm-submodules/pull/1919","id":948481409,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzMzE4Mzc0","number":1919,"title":"PMM-8408 DBaaS: use correct pmm-client image based on environment","user":{"login":"jprukner","id":13995559,"node_id":"MDQ6VXNlcjEzOTk1NTU5","avatar_url":"https://avatars.githubusercontent.com/u/13995559?v=4","gravatar_id":"","url":"https://api.github.com/users/jprukner","html_url":"https://github.com/jprukner","followers_url":"https://api.github.com/users/jprukner/followers","following_url":"https://api.github.com/users/jprukner/following{/other_user}","gists_url":"https://api.github.com/users/jprukner/gists{/gist_id}","starred_url":"https://api.github.com/users/jprukner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jprukner/subscriptions","organizations_url":"https://api.github.com/users/jprukner/orgs","repos_url":"https://api.github.com/users/jprukner/repos","events_url":"https://api.github.com/users/jprukner/events{/privacy}","received_events_url":"https://api.github.com/users/jprukner/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":5,"created_at":"2021-07-20T09:41:45Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/pulls/1919","html_url":"https://github.com/Percona-Lab/pmm-submodules/pull/1919","diff_url":"https://github.com/Percona-Lab/pmm-submodules/pull/1919.diff","patch_url":"https://github.com/Percona-Lab/pmm-submodules/pull/1919.patch"},"body":"","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/issues/comments/883942790","html_url":"https://github.com/Percona-Lab/pmm-submodules/pull/1919#issuecomment-883942790","issue_url":"https://api.github.com/repos/Percona-Lab/pmm-submodules/issues/1919","id":883942790,"node_id":"IC_kwDOBs_SFs40r-WG","user":{"login":"JNKPercona","id":25307165,"node_id":"MDQ6VXNlcjI1MzA3MTY1","avatar_url":"https://avatars.githubusercontent.com/u/25307165?v=4","gravatar_id":"","url":"https://api.github.com/users/JNKPercona","html_url":"https://github.com/JNKPercona","followers_url":"https://api.github.com/users/JNKPercona/followers","following_url":"https://api.github.com/users/JNKPercona/following{/other_user}","gists_url":"https://api.github.com/users/JNKPercona/gists{/gist_id}","starred_url":"https://api.github.com/users/JNKPercona/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JNKPercona/subscriptions","organizations_url":"https://api.github.com/users/JNKPercona/orgs","repos_url":"https://api.github.com/users/JNKPercona/repos","events_url":"https://api.github.com/users/JNKPercona/events{/privacy}","received_events_url":"https://api.github.com/users/JNKPercona/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","author_association":"COLLABORATOR","body":"server docker - perconalab/pmm-server-fb:PR-1919-643b5aa\nclient docker - perconalab/pmm-client-fb:PR-1919-643b5aa\nclient - https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-PR-1919-643b5aa.tar.gz\nCreate Staging Instance: https://pmm.cd.percona.com/job/aws-staging-start/parambuild/?DOCKER_VERSION=perconalab/pmm-server-fb:PR-1919-643b5aa&CLIENT_VERSION=https://s3.us-east-2.amazonaws.com/pmm-build-cache/PR-BUILDS/pmm2-client/pmm2-client-PR-1919-643b5aa.tar.gz","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":12824839,"login":"Percona-Lab","gravatar_id":"","url":"https://api.github.com/orgs/Percona-Lab","avatar_url":"https://avatars.githubusercontent.com/u/12824839?"}} +{"id":"17244792119","type":"WatchEvent","actor":{"id":44917879,"login":"Root970103","display_login":"Root970103","gravatar_id":"","url":"https://api.github.com/users/Root970103","avatar_url":"https://avatars.githubusercontent.com/u/44917879?"},"repo":{"id":177778222,"name":"luopeixiang/im2latex","url":"https://api.github.com/repos/luopeixiang/im2latex"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792120","type":"PushEvent","actor":{"id":87436052,"login":"ZahiCohen","display_login":"ZahiCohen","gravatar_id":"","url":"https://api.github.com/users/ZahiCohen","avatar_url":"https://avatars.githubusercontent.com/u/87436052?"},"repo":{"id":385995015,"name":"ZahiCohen/JB","url":"https://api.github.com/repos/ZahiCohen/JB"},"payload":{"push_id":7561706316,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"914e757e71c4c3b32aefbb754f21a61eecec3ff5","before":"85f2a24b54106c0b49a66752163ebcc3b7579078","commits":[{"sha":"914e757e71c4c3b32aefbb754f21a61eecec3ff5","author":{"name":"ZahiCohen","email":"80573d27bfaac40c91d00e13e2e520afd7037825@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/ZahiCohen/JB/commits/914e757e71c4c3b32aefbb754f21a61eecec3ff5"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792121","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":275871713,"name":"NethServer/dns-community-blacklist","url":"https://api.github.com/repos/NethServer/dns-community-blacklist"},"payload":{"push_id":7561706324,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"53e920bdbfb5ebffc1bcf5640fa673d1e082ca7f","before":"21cc010de57c1e15d5626be49aa71102031a1aaa","commits":[{"sha":"53e920bdbfb5ebffc1bcf5640fa673d1e082ca7f","author":{"name":"dnutan","email":"759532faf728bc7a97f912a9df9d6c70aa6f0244@mailbox.org"},"message":"[Bot] Update DNS Blacklists (2021-07-21T06:59:58Z)","distinct":true,"url":"https://api.github.com/repos/NethServer/dns-community-blacklist/commits/53e920bdbfb5ebffc1bcf5640fa673d1e082ca7f"}]},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":6469208,"login":"NethServer","gravatar_id":"","url":"https://api.github.com/orgs/NethServer","avatar_url":"https://avatars.githubusercontent.com/u/6469208?"}} +{"id":"17244792123","type":"PushEvent","actor":{"id":69618456,"login":"adityachaphekar","display_login":"adityachaphekar","gravatar_id":"","url":"https://api.github.com/users/adityachaphekar","avatar_url":"https://avatars.githubusercontent.com/u/69618456?"},"repo":{"id":387539318,"name":"adityachaphekar/dynamicpipeline","url":"https://api.github.com/repos/adityachaphekar/dynamicpipeline"},"payload":{"push_id":7561706321,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"a2eed8358f53552df18daafe3c90d5078a9b1475","before":"c5449b591bdf0b5bbd461821cbc32d31df04fc0d","commits":[{"sha":"a2eed8358f53552df18daafe3c90d5078a9b1475","author":{"name":"adityachaphekar","email":"609587ff81c32b9d2b093aeccc69c53a301a1e30@users.noreply.github.com"},"message":"Update samplereactjob.yaml","distinct":true,"url":"https://api.github.com/repos/adityachaphekar/dynamicpipeline/commits/a2eed8358f53552df18daafe3c90d5078a9b1475"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792127","type":"CreateEvent","actor":{"id":1413417,"login":"nutgaard","display_login":"nutgaard","gravatar_id":"","url":"https://api.github.com/users/nutgaard","avatar_url":"https://avatars.githubusercontent.com/u/1413417?"},"repo":{"id":122311682,"name":"navikt/modiapersonoversikt","url":"https://api.github.com/repos/navikt/modiapersonoversikt"},"payload":{"ref":"fix/fagsystem-177153","ref_type":"branch","master_branch":"dev","description":" Modiapersonoversikt er en intern arbeidsflate som gir veiledere og saksbehandlere oversikt over brukeres forhold til NAV.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":11848947,"login":"navikt","gravatar_id":"","url":"https://api.github.com/orgs/navikt","avatar_url":"https://avatars.githubusercontent.com/u/11848947?"}} +{"id":"17244792134","type":"IssueCommentEvent","actor":{"id":11410715,"login":"AsierGonzalez","display_login":"AsierGonzalez","gravatar_id":"","url":"https://api.github.com/users/AsierGonzalez","avatar_url":"https://avatars.githubusercontent.com/u/11410715?"},"repo":{"id":362871994,"name":"iRNA-COSI/APAeval","url":"https://api.github.com/repos/iRNA-COSI/APAeval"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/iRNA-COSI/APAeval/issues/152","repository_url":"https://api.github.com/repos/iRNA-COSI/APAeval","labels_url":"https://api.github.com/repos/iRNA-COSI/APAeval/issues/152/labels{/name}","comments_url":"https://api.github.com/repos/iRNA-COSI/APAeval/issues/152/comments","events_url":"https://api.github.com/repos/iRNA-COSI/APAeval/issues/152/events","html_url":"https://github.com/iRNA-COSI/APAeval/issues/152","id":948488020,"node_id":"MDU6SXNzdWU5NDg0ODgwMjA=","number":152,"title":"OpenEBench summary workflow: rename output consolidation file","user":{"login":"AsierGonzalez","id":11410715,"node_id":"MDQ6VXNlcjExNDEwNzE1","avatar_url":"https://avatars.githubusercontent.com/u/11410715?v=4","gravatar_id":"","url":"https://api.github.com/users/AsierGonzalez","html_url":"https://github.com/AsierGonzalez","followers_url":"https://api.github.com/users/AsierGonzalez/followers","following_url":"https://api.github.com/users/AsierGonzalez/following{/other_user}","gists_url":"https://api.github.com/users/AsierGonzalez/gists{/gist_id}","starred_url":"https://api.github.com/users/AsierGonzalez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AsierGonzalez/subscriptions","organizations_url":"https://api.github.com/users/AsierGonzalez/orgs","repos_url":"https://api.github.com/users/AsierGonzalez/repos","events_url":"https://api.github.com/users/AsierGonzalez/events{/privacy}","received_events_url":"https://api.github.com/users/AsierGonzalez/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":{"login":"yuukiiwa","id":41866052,"node_id":"MDQ6VXNlcjQxODY2MDUy","avatar_url":"https://avatars.githubusercontent.com/u/41866052?v=4","gravatar_id":"","url":"https://api.github.com/users/yuukiiwa","html_url":"https://github.com/yuukiiwa","followers_url":"https://api.github.com/users/yuukiiwa/followers","following_url":"https://api.github.com/users/yuukiiwa/following{/other_user}","gists_url":"https://api.github.com/users/yuukiiwa/gists{/gist_id}","starred_url":"https://api.github.com/users/yuukiiwa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yuukiiwa/subscriptions","organizations_url":"https://api.github.com/users/yuukiiwa/orgs","repos_url":"https://api.github.com/users/yuukiiwa/repos","events_url":"https://api.github.com/users/yuukiiwa/events{/privacy}","received_events_url":"https://api.github.com/users/yuukiiwa/received_events","type":"User","site_admin":false},"assignees":[{"login":"yuukiiwa","id":41866052,"node_id":"MDQ6VXNlcjQxODY2MDUy","avatar_url":"https://avatars.githubusercontent.com/u/41866052?v=4","gravatar_id":"","url":"https://api.github.com/users/yuukiiwa","html_url":"https://github.com/yuukiiwa","followers_url":"https://api.github.com/users/yuukiiwa/followers","following_url":"https://api.github.com/users/yuukiiwa/following{/other_user}","gists_url":"https://api.github.com/users/yuukiiwa/gists{/gist_id}","starred_url":"https://api.github.com/users/yuukiiwa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yuukiiwa/subscriptions","organizations_url":"https://api.github.com/users/yuukiiwa/orgs","repos_url":"https://api.github.com/users/yuukiiwa/repos","events_url":"https://api.github.com/users/yuukiiwa/events{/privacy}","received_events_url":"https://api.github.com/users/yuukiiwa/received_events","type":"User","site_admin":false}],"milestone":null,"comments":3,"created_at":"2021-07-20T09:50:24Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"COLLABORATOR","active_lock_reason":null,"body":"At the moment the output file is called `{challenge_name}_summary.json` (e.g. `Q2_summary.json`), which causes issue with the visualisation as the js code used expects the file to be called `{challenge_name}.json` (e.g. `Q2.json`). Could you please change the code in [line 138 of manage_assessment_data.py](https://github.com/iRNA-COSI/APAeval/blob/main/summary_workflows/quantification/Q2/benchmarking_dockers/apaeval_consolidation/manage_assessment_data.py#L138) to remove the \"_summary\" bit? \r\n\r\n\r\n`summary_dir = os.path.join(challenge_dir,challenge + `~~\"_summary.json\"~~ `\".json\")`\r\n\r\nI believe this is the only place in the code this filename is reference but I haven't checked thoroughly","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/iRNA-COSI/APAeval/issues/comments/883942792","html_url":"https://github.com/iRNA-COSI/APAeval/issues/152#issuecomment-883942792","issue_url":"https://api.github.com/repos/iRNA-COSI/APAeval/issues/152","id":883942792,"node_id":"IC_kwDOFaD8us40r-WI","user":{"login":"AsierGonzalez","id":11410715,"node_id":"MDQ6VXNlcjExNDEwNzE1","avatar_url":"https://avatars.githubusercontent.com/u/11410715?v=4","gravatar_id":"","url":"https://api.github.com/users/AsierGonzalez","html_url":"https://github.com/AsierGonzalez","followers_url":"https://api.github.com/users/AsierGonzalez/followers","following_url":"https://api.github.com/users/AsierGonzalez/following{/other_user}","gists_url":"https://api.github.com/users/AsierGonzalez/gists{/gist_id}","starred_url":"https://api.github.com/users/AsierGonzalez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AsierGonzalez/subscriptions","organizations_url":"https://api.github.com/users/AsierGonzalez/orgs","repos_url":"https://api.github.com/users/AsierGonzalez/repos","events_url":"https://api.github.com/users/AsierGonzalez/events{/privacy}","received_events_url":"https://api.github.com/users/AsierGonzalez/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","author_association":"COLLABORATOR","body":"Thank you @yuukiiwa. I see you have changed `manage_assessment_data.py` but not `merge_data_model_files.py`, do you think the latter is fine as it?","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":63477102,"login":"iRNA-COSI","gravatar_id":"","url":"https://api.github.com/orgs/iRNA-COSI","avatar_url":"https://avatars.githubusercontent.com/u/63477102?"}} +{"id":"17244792139","type":"CreateEvent","actor":{"id":45584744,"login":"kuangzhilu","display_login":"kuangzhilu","gravatar_id":"","url":"https://api.github.com/users/kuangzhilu","avatar_url":"https://avatars.githubusercontent.com/u/45584744?"},"repo":{"id":388021305,"name":"kuangzhilu/init","url":"https://api.github.com/repos/kuangzhilu/init"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792144","type":"PushEvent","actor":{"id":37845953,"login":"openapi-sdkautomation[bot]","display_login":"openapi-sdkautomation","gravatar_id":"","url":"https://api.github.com/users/openapi-sdkautomation[bot]","avatar_url":"https://avatars.githubusercontent.com/u/37845953?"},"repo":{"id":379510750,"name":"AzureSDKAutomation/azure-sdk-for-go","url":"https://api.github.com/repos/AzureSDKAutomation/azure-sdk-for-go"},"payload":{"push_id":7561706300,"size":0,"distinct_size":0,"ref":"refs/heads/main","head":"063ba36b88e88a618ebb1372ca84f2ab26d74927","before":"063ba36b88e88a618ebb1372ca84f2ab26d74927","commits":[]},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":50844873,"login":"AzureSDKAutomation","gravatar_id":"","url":"https://api.github.com/orgs/AzureSDKAutomation","avatar_url":"https://avatars.githubusercontent.com/u/50844873?"}} +{"id":"17244792146","type":"PushEvent","actor":{"id":85284788,"login":"kokowhen","display_login":"kokowhen","gravatar_id":"","url":"https://api.github.com/users/kokowhen","avatar_url":"https://avatars.githubusercontent.com/u/85284788?"},"repo":{"id":387367469,"name":"kokowhen/my_Learning_Notes","url":"https://api.github.com/repos/kokowhen/my_Learning_Notes"},"payload":{"push_id":7561706323,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"1ee00fca38eac0ecc17bc7ba89f0030d9928247c","before":"9c6e938ef13bec9d2bc62af47b9c9950d6993e26","commits":[{"sha":"1ee00fca38eac0ecc17bc7ba89f0030d9928247c","author":{"name":"Jeffery","email":"4e13c45a9d1913391d03bd93e4bf29dd2c8c0672@gmail.com"},"message":"Delete Project1 directory","distinct":true,"url":"https://api.github.com/repos/kokowhen/my_Learning_Notes/commits/1ee00fca38eac0ecc17bc7ba89f0030d9928247c"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792152","type":"PullRequestEvent","actor":{"id":49699333,"login":"dependabot[bot]","display_login":"dependabot","gravatar_id":"","url":"https://api.github.com/users/dependabot[bot]","avatar_url":"https://avatars.githubusercontent.com/u/49699333?"},"repo":{"id":262461195,"name":"crowdbotics-apps/aviannasavatarmaker-16781","url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781"},"payload":{"action":"opened","number":16,"pull_request":{"url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/16","id":694104871,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODcx","html_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781/pull/16","diff_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781/pull/16.diff","patch_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781/pull/16.patch","issue_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/16","number":16,"state":"open","locked":false,"title":"Bump react-native from 0.60.5 to 0.62.3","user":{"login":"dependabot[bot]","id":49699333,"node_id":"MDM6Qm90NDk2OTkzMzM=","avatar_url":"https://avatars.githubusercontent.com/in/29110?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot%5Bbot%5D","html_url":"https://github.com/apps/dependabot","followers_url":"https://api.github.com/users/dependabot%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bumps [react-native](https://github.com/facebook/react-native) from 0.60.5 to 0.62.3.\n
    \nRelease notes\n

    Sourced from react-native's releases.

    \n
    \n

    v0.62.3

    \n

    This patch release is specifically targetted towards Xcode 12.5. The changes done are tailored to unblock developers still relying on v0.62 of RN.

    \n

    Aside from bumping your version from 0.62.2 to 0.62.3, please make sure to add this line to your podfile (or modify it if you already had it):

    \n
    use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')\n
    \n

    After which, do all the classic necessary cleans (node_modules, caches, pod folders, etc)(react-native-clean-project is your ally) then do yarn install and a pod install --repo-update (if pod install fails on an error about a Flipper package, just remove the relevant lines from the podfile.lock and run the pod install again).

    \n

    The only other commit picked & released along the Xcode 12.5 fixes is:

    \n
      \n
    • Update validateBaseUrl to use latest regex (commit) which fixes CVE-2020-1920, GHSL-2020-293.
    • \n
    \n
    \n

    To help you upgrade to this version, you can use the upgrade helper ⚛️

    \n
    \n

    You can find the whole changelog history over at react-native-releases.

    \n

    v0.62.2

    \n

    This release fixes a few minor issues that were reported by the community. You can view the complete changelog here.

    \n

    You can participate in the conversation for the next patch release in the dedicated issue.

    \n
    \n

    To help you upgrade to this version, you can use the new upgrade helper ⚛️

    \n
    \n

    You can find the whole changelog history over at react-native-releases.

    \n

    v0.62.1

    \n

    This release fixes a YellowBox regression in v0.62.0 where the Flipper network inspector causes YellowBox to crash the app due to using base64 images.

    \n

    You can view the complete changelog here.

    \n

    You can participate in the conversation for the next patch release in the dedicated issue.

    \n
    \n

    To help you upgrade to this version, you can use the new upgrade helper ⚛️

    \n
    \n

    You can find the whole changelog history over at react-native-releases.

    \n\n
    \n

    ... (truncated)

    \n
    \n
    \nCommits\n
      \n
    • 83425fa [0.62.3] Bump version numbers
    • \n
    • c6f4611 [local] change autolink to match requirements for FlipperFolly working with X...
    • \n
    • c4ea556 [local] change podfile to rely on the autolink-ios rb file
    • \n
    • ca09ae8 Update validateBaseUrl to use latest regex
    • \n
    • 166a5dd Get ReactiveNative compiled with Clang 10 (#28362)
    • \n
    • 158b558 [local] update detox to work on Xcode 12
    • \n
    • b9944e5 [0.62.2] Bump version numbers
    • \n
    • f89c509 Make Vibration.vibrate compatible with TurboModules (#27951)
    • \n
    • 8858d87 Exclude all FlipperKit transitive dependencies from iOS Release builds (#28504)
    • \n
    • 4fd9c9d Fix Appearance module when using Chrome Debugger
    • \n
    • Additional commits viewable in compare view
    • \n
    \n
    \n
    \n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=react-native&package-manager=npm_and_yarn&previous-version=0.60.5&new-version=0.62.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language\n- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language\n- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language\n- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language\n\nYou can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/crowdbotics-apps/aviannasavatarmaker-16781/network/alerts).\n\n
    ","created_at":"2021-07-21T07:00:01Z","updated_at":"2021-07-21T07:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/16/commits","review_comments_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/16/comments","review_comment_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/comments{/number}","comments_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/16/comments","statuses_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/statuses/296e1658337a372871cb5528c85bdaa81ef7bc3f","head":{"label":"crowdbotics-apps:dependabot/npm_and_yarn/react-native-0.62.3","ref":"dependabot/npm_and_yarn/react-native-0.62.3","sha":"296e1658337a372871cb5528c85bdaa81ef7bc3f","user":{"login":"crowdbotics-apps","id":46582385,"node_id":"MDQ6VXNlcjQ2NTgyMzg1","avatar_url":"https://avatars.githubusercontent.com/u/46582385?v=4","gravatar_id":"","url":"https://api.github.com/users/crowdbotics-apps","html_url":"https://github.com/crowdbotics-apps","followers_url":"https://api.github.com/users/crowdbotics-apps/followers","following_url":"https://api.github.com/users/crowdbotics-apps/following{/other_user}","gists_url":"https://api.github.com/users/crowdbotics-apps/gists{/gist_id}","starred_url":"https://api.github.com/users/crowdbotics-apps/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowdbotics-apps/subscriptions","organizations_url":"https://api.github.com/users/crowdbotics-apps/orgs","repos_url":"https://api.github.com/users/crowdbotics-apps/repos","events_url":"https://api.github.com/users/crowdbotics-apps/events{/privacy}","received_events_url":"https://api.github.com/users/crowdbotics-apps/received_events","type":"User","site_admin":false},"repo":{"id":262461195,"node_id":"MDEwOlJlcG9zaXRvcnkyNjI0NjExOTU=","name":"aviannasavatarmaker-16781","full_name":"crowdbotics-apps/aviannasavatarmaker-16781","private":false,"owner":{"login":"crowdbotics-apps","id":46582385,"node_id":"MDQ6VXNlcjQ2NTgyMzg1","avatar_url":"https://avatars.githubusercontent.com/u/46582385?v=4","gravatar_id":"","url":"https://api.github.com/users/crowdbotics-apps","html_url":"https://github.com/crowdbotics-apps","followers_url":"https://api.github.com/users/crowdbotics-apps/followers","following_url":"https://api.github.com/users/crowdbotics-apps/following{/other_user}","gists_url":"https://api.github.com/users/crowdbotics-apps/gists{/gist_id}","starred_url":"https://api.github.com/users/crowdbotics-apps/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowdbotics-apps/subscriptions","organizations_url":"https://api.github.com/users/crowdbotics-apps/orgs","repos_url":"https://api.github.com/users/crowdbotics-apps/repos","events_url":"https://api.github.com/users/crowdbotics-apps/events{/privacy}","received_events_url":"https://api.github.com/users/crowdbotics-apps/received_events","type":"User","site_admin":false},"html_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781","description":"This react_native application was built with Crowdbotics www.crowdbotics.com","fork":false,"url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781","forks_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/forks","keys_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/keys{/key_id}","collaborators_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/teams","hooks_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/hooks","issue_events_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/events{/number}","events_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/events","assignees_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/assignees{/user}","branches_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/branches{/branch}","tags_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/tags","blobs_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/refs{/sha}","trees_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/trees{/sha}","statuses_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/statuses/{sha}","languages_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/languages","stargazers_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/stargazers","contributors_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/contributors","subscribers_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/subscribers","subscription_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/subscription","commits_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/commits{/sha}","git_commits_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/commits{/sha}","comments_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/comments{/number}","issue_comment_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/comments{/number}","contents_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/contents/{+path}","compare_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/compare/{base}...{head}","merges_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/merges","archive_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/downloads","issues_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues{/number}","pulls_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls{/number}","milestones_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/milestones{/number}","notifications_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/labels{/name}","releases_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/releases{/id}","deployments_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/deployments","created_at":"2020-05-09T01:10:54Z","updated_at":"2020-05-09T01:11:50Z","pushed_at":"2021-07-21T07:00:02Z","git_url":"git://github.com/crowdbotics-apps/aviannasavatarmaker-16781.git","ssh_url":"git@github.com:crowdbotics-apps/aviannasavatarmaker-16781.git","clone_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781.git","svn_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781","homepage":null,"size":36052,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":14,"license":null,"forks":0,"open_issues":14,"watchers":0,"default_branch":"master"}},"base":{"label":"crowdbotics-apps:master","ref":"master","sha":"0d6f23c1cea953c1fbc0a8a8144e7c3040a5e4a1","user":{"login":"crowdbotics-apps","id":46582385,"node_id":"MDQ6VXNlcjQ2NTgyMzg1","avatar_url":"https://avatars.githubusercontent.com/u/46582385?v=4","gravatar_id":"","url":"https://api.github.com/users/crowdbotics-apps","html_url":"https://github.com/crowdbotics-apps","followers_url":"https://api.github.com/users/crowdbotics-apps/followers","following_url":"https://api.github.com/users/crowdbotics-apps/following{/other_user}","gists_url":"https://api.github.com/users/crowdbotics-apps/gists{/gist_id}","starred_url":"https://api.github.com/users/crowdbotics-apps/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowdbotics-apps/subscriptions","organizations_url":"https://api.github.com/users/crowdbotics-apps/orgs","repos_url":"https://api.github.com/users/crowdbotics-apps/repos","events_url":"https://api.github.com/users/crowdbotics-apps/events{/privacy}","received_events_url":"https://api.github.com/users/crowdbotics-apps/received_events","type":"User","site_admin":false},"repo":{"id":262461195,"node_id":"MDEwOlJlcG9zaXRvcnkyNjI0NjExOTU=","name":"aviannasavatarmaker-16781","full_name":"crowdbotics-apps/aviannasavatarmaker-16781","private":false,"owner":{"login":"crowdbotics-apps","id":46582385,"node_id":"MDQ6VXNlcjQ2NTgyMzg1","avatar_url":"https://avatars.githubusercontent.com/u/46582385?v=4","gravatar_id":"","url":"https://api.github.com/users/crowdbotics-apps","html_url":"https://github.com/crowdbotics-apps","followers_url":"https://api.github.com/users/crowdbotics-apps/followers","following_url":"https://api.github.com/users/crowdbotics-apps/following{/other_user}","gists_url":"https://api.github.com/users/crowdbotics-apps/gists{/gist_id}","starred_url":"https://api.github.com/users/crowdbotics-apps/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowdbotics-apps/subscriptions","organizations_url":"https://api.github.com/users/crowdbotics-apps/orgs","repos_url":"https://api.github.com/users/crowdbotics-apps/repos","events_url":"https://api.github.com/users/crowdbotics-apps/events{/privacy}","received_events_url":"https://api.github.com/users/crowdbotics-apps/received_events","type":"User","site_admin":false},"html_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781","description":"This react_native application was built with Crowdbotics www.crowdbotics.com","fork":false,"url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781","forks_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/forks","keys_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/keys{/key_id}","collaborators_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/teams","hooks_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/hooks","issue_events_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/events{/number}","events_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/events","assignees_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/assignees{/user}","branches_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/branches{/branch}","tags_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/tags","blobs_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/refs{/sha}","trees_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/trees{/sha}","statuses_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/statuses/{sha}","languages_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/languages","stargazers_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/stargazers","contributors_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/contributors","subscribers_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/subscribers","subscription_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/subscription","commits_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/commits{/sha}","git_commits_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/git/commits{/sha}","comments_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/comments{/number}","issue_comment_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/comments{/number}","contents_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/contents/{+path}","compare_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/compare/{base}...{head}","merges_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/merges","archive_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/downloads","issues_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues{/number}","pulls_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls{/number}","milestones_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/milestones{/number}","notifications_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/labels{/name}","releases_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/releases{/id}","deployments_url":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/deployments","created_at":"2020-05-09T01:10:54Z","updated_at":"2020-05-09T01:11:50Z","pushed_at":"2021-07-21T07:00:02Z","git_url":"git://github.com/crowdbotics-apps/aviannasavatarmaker-16781.git","ssh_url":"git@github.com:crowdbotics-apps/aviannasavatarmaker-16781.git","clone_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781.git","svn_url":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781","homepage":null,"size":36052,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":14,"license":null,"forks":0,"open_issues":14,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/16"},"html":{"href":"https://github.com/crowdbotics-apps/aviannasavatarmaker-16781/pull/16"},"issue":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/16"},"comments":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/issues/16/comments"},"review_comments":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/16/comments"},"review_comment":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/pulls/16/commits"},"statuses":{"href":"https://api.github.com/repos/crowdbotics-apps/aviannasavatarmaker-16781/statuses/296e1658337a372871cb5528c85bdaa81ef7bc3f"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":860,"deletions":593,"changed_files":2}},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792153","type":"ForkEvent","actor":{"id":68951627,"login":"kawata-yuya","display_login":"kawata-yuya","gravatar_id":"","url":"https://api.github.com/users/kawata-yuya","avatar_url":"https://avatars.githubusercontent.com/u/68951627?"},"repo":{"id":81598961,"name":"python/cpython","url":"https://api.github.com/repos/python/cpython"},"payload":{"forkee":{"id":388024727,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMjQ3Mjc=","name":"cpython","full_name":"kawata-yuya/cpython","private":false,"owner":{"login":"kawata-yuya","id":68951627,"node_id":"MDQ6VXNlcjY4OTUxNjI3","avatar_url":"https://avatars.githubusercontent.com/u/68951627?v=4","gravatar_id":"","url":"https://api.github.com/users/kawata-yuya","html_url":"https://github.com/kawata-yuya","followers_url":"https://api.github.com/users/kawata-yuya/followers","following_url":"https://api.github.com/users/kawata-yuya/following{/other_user}","gists_url":"https://api.github.com/users/kawata-yuya/gists{/gist_id}","starred_url":"https://api.github.com/users/kawata-yuya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kawata-yuya/subscriptions","organizations_url":"https://api.github.com/users/kawata-yuya/orgs","repos_url":"https://api.github.com/users/kawata-yuya/repos","events_url":"https://api.github.com/users/kawata-yuya/events{/privacy}","received_events_url":"https://api.github.com/users/kawata-yuya/received_events","type":"User","site_admin":false},"html_url":"https://github.com/kawata-yuya/cpython","description":"The Python programming language","fork":true,"url":"https://api.github.com/repos/kawata-yuya/cpython","forks_url":"https://api.github.com/repos/kawata-yuya/cpython/forks","keys_url":"https://api.github.com/repos/kawata-yuya/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kawata-yuya/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kawata-yuya/cpython/teams","hooks_url":"https://api.github.com/repos/kawata-yuya/cpython/hooks","issue_events_url":"https://api.github.com/repos/kawata-yuya/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/kawata-yuya/cpython/events","assignees_url":"https://api.github.com/repos/kawata-yuya/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/kawata-yuya/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/kawata-yuya/cpython/tags","blobs_url":"https://api.github.com/repos/kawata-yuya/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kawata-yuya/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kawata-yuya/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/kawata-yuya/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kawata-yuya/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/kawata-yuya/cpython/languages","stargazers_url":"https://api.github.com/repos/kawata-yuya/cpython/stargazers","contributors_url":"https://api.github.com/repos/kawata-yuya/cpython/contributors","subscribers_url":"https://api.github.com/repos/kawata-yuya/cpython/subscribers","subscription_url":"https://api.github.com/repos/kawata-yuya/cpython/subscription","commits_url":"https://api.github.com/repos/kawata-yuya/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/kawata-yuya/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/kawata-yuya/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/kawata-yuya/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/kawata-yuya/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/kawata-yuya/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kawata-yuya/cpython/merges","archive_url":"https://api.github.com/repos/kawata-yuya/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kawata-yuya/cpython/downloads","issues_url":"https://api.github.com/repos/kawata-yuya/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/kawata-yuya/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/kawata-yuya/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/kawata-yuya/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kawata-yuya/cpython/labels{/name}","releases_url":"https://api.github.com/repos/kawata-yuya/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/kawata-yuya/cpython/deployments","created_at":"2021-07-21T07:00:01Z","updated_at":"2021-07-21T06:02:33Z","pushed_at":"2021-07-20T22:27:20Z","git_url":"git://github.com/kawata-yuya/cpython.git","ssh_url":"git@github.com:kawata-yuya/cpython.git","clone_url":"https://github.com/kawata-yuya/cpython.git","svn_url":"https://github.com/kawata-yuya/cpython","homepage":"https://www.python.org/","size":389658,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master","public":true}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":1525981,"login":"python","gravatar_id":"","url":"https://api.github.com/orgs/python","avatar_url":"https://avatars.githubusercontent.com/u/1525981?"}} +{"id":"17244792161","type":"PushEvent","actor":{"id":71702360,"login":"ZhouJianzj","display_login":"ZhouJianzj","gravatar_id":"","url":"https://api.github.com/users/ZhouJianzj","avatar_url":"https://avatars.githubusercontent.com/u/71702360?"},"repo":{"id":371244830,"name":"ZhouJianzj/Vue.js","url":"https://api.github.com/repos/ZhouJianzj/Vue.js"},"payload":{"push_id":7561706340,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"eed08791c40520ce256c15938b826e293c663df0","before":"0ececbd2f38ef7a0111e5cb4696d8d9abc193555","commits":[{"sha":"eed08791c40520ce256c15938b826e293c663df0","author":{"name":"ZhouJianzj","email":"576e16d22093e22c1906170e4170d4b8c928852a@qq.com"},"message":"elementUI、嵌套路由","distinct":true,"url":"https://api.github.com/repos/ZhouJianzj/Vue.js/commits/eed08791c40520ce256c15938b826e293c663df0"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792171","type":"IssueCommentEvent","actor":{"id":4538033,"login":"CarstenHollmann","display_login":"CarstenHollmann","gravatar_id":"","url":"https://api.github.com/users/CarstenHollmann","avatar_url":"https://avatars.githubusercontent.com/u/4538033?"},"repo":{"id":17742767,"name":"52North/SOS","url":"https://api.github.com/repos/52North/SOS"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/52North/SOS/issues/1112","repository_url":"https://api.github.com/repos/52North/SOS","labels_url":"https://api.github.com/repos/52North/SOS/issues/1112/labels{/name}","comments_url":"https://api.github.com/repos/52North/SOS/issues/1112/comments","events_url":"https://api.github.com/repos/52North/SOS/issues/1112/events","html_url":"https://github.com/52North/SOS/pull/1112","id":949298930,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDI2Mzcw","number":1112,"title":"Bump version.slf4j from 1.7.31 to 1.7.32","user":{"login":"dependabot[bot]","id":49699333,"node_id":"MDM6Qm90NDk2OTkzMzM=","avatar_url":"https://avatars.githubusercontent.com/in/29110?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot%5Bbot%5D","html_url":"https://github.com/apps/dependabot","followers_url":"https://api.github.com/users/dependabot%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot%5Bbot%5D/received_events","type":"Bot","site_admin":false},"labels":[{"id":1676802469,"node_id":"MDU6TGFiZWwxNjc2ODAyNDY5","url":"https://api.github.com/repos/52North/SOS/labels/dependencies","name":"dependencies","color":"0366d6","default":false,"description":"Pull requests that update a dependency file"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-07-21T04:02:21Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/52North/SOS/pulls/1112","html_url":"https://github.com/52North/SOS/pull/1112","diff_url":"https://github.com/52North/SOS/pull/1112.diff","patch_url":"https://github.com/52North/SOS/pull/1112.patch"},"body":"Bumps `version.slf4j` from 1.7.31 to 1.7.32.\nUpdates `slf4j-api` from 1.7.31 to 1.7.32\n
    \nCommits\n\n
    \n
    \n\nUpdates `jcl-over-slf4j` from 1.7.31 to 1.7.32\n
    \nCommits\n\n
    \n
    \n\nUpdates `jul-to-slf4j` from 1.7.31 to 1.7.32\n
    \nCommits\n\n
    \n
    \n\nUpdates `log4j-over-slf4j` from 1.7.31 to 1.7.32\n
    \nCommits\n\n
    \n
    \n\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
    ","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/52North/SOS/issues/comments/883942793","html_url":"https://github.com/52North/SOS/pull/1112#issuecomment-883942793","issue_url":"https://api.github.com/repos/52North/SOS/issues/1112","id":883942793,"node_id":"IC_kwDOAQ67r840r-WJ","user":{"login":"CarstenHollmann","id":4538033,"node_id":"MDQ6VXNlcjQ1MzgwMzM=","avatar_url":"https://avatars.githubusercontent.com/u/4538033?v=4","gravatar_id":"","url":"https://api.github.com/users/CarstenHollmann","html_url":"https://github.com/CarstenHollmann","followers_url":"https://api.github.com/users/CarstenHollmann/followers","following_url":"https://api.github.com/users/CarstenHollmann/following{/other_user}","gists_url":"https://api.github.com/users/CarstenHollmann/gists{/gist_id}","starred_url":"https://api.github.com/users/CarstenHollmann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CarstenHollmann/subscriptions","organizations_url":"https://api.github.com/users/CarstenHollmann/orgs","repos_url":"https://api.github.com/users/CarstenHollmann/repos","events_url":"https://api.github.com/users/CarstenHollmann/events{/privacy}","received_events_url":"https://api.github.com/users/CarstenHollmann/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","author_association":"MEMBER","body":"@dependabot recreate","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":3714494,"login":"52North","gravatar_id":"","url":"https://api.github.com/orgs/52North","avatar_url":"https://avatars.githubusercontent.com/u/3714494?"}} +{"id":"17244792191","type":"PushEvent","actor":{"id":830993,"login":"bihe0832","display_login":"bihe0832","gravatar_id":"","url":"https://api.github.com/users/bihe0832","avatar_url":"https://avatars.githubusercontent.com/u/830993?"},"repo":{"id":85289060,"name":"bihe0832/bihe0832.github.io","url":"https://api.github.com/repos/bihe0832/bihe0832.github.io"},"payload":{"push_id":7561706355,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"6ffa3a7b8924f8bc64814128b9c8a2aee07facc9","before":"ca9739d5c8f45a081c7f01ee09a6e5c54d7e29d6","commits":[{"sha":"6ffa3a7b8924f8bc64814128b9c8a2aee07facc9","author":{"name":"子勰","email":"e6fb06210fafc02fd7479ddbed2d042cc3a5155e@bihe0832.com"},"message":"装机步骤优化","distinct":true,"url":"https://api.github.com/repos/bihe0832/bihe0832.github.io/commits/6ffa3a7b8924f8bc64814128b9c8a2aee07facc9"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792160","type":"PushEvent","actor":{"id":87353876,"login":"CharcyZhang","display_login":"CharcyZhang","gravatar_id":"","url":"https://api.github.com/users/CharcyZhang","avatar_url":"https://avatars.githubusercontent.com/u/87353876?"},"repo":{"id":385829752,"name":"CharcyZhang/CharcyZhang.github.io","url":"https://api.github.com/repos/CharcyZhang/CharcyZhang.github.io"},"payload":{"push_id":7561706346,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"7cb762f690af2a88e384f61cfab9fd18d9045cc7","before":"9c46feaebe47930e8120f883affd1c3d67d674bd","commits":[{"sha":"7cb762f690af2a88e384f61cfab9fd18d9045cc7","author":{"name":"CharcyZhang","email":"88ef02781f18875f2bb561d3ad6e0cdae6967ad4@outlook.com"},"message":"Site updated: 2021-07-21 14:59:58","distinct":true,"url":"https://api.github.com/repos/CharcyZhang/CharcyZhang.github.io/commits/7cb762f690af2a88e384f61cfab9fd18d9045cc7"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792180","type":"IssueCommentEvent","actor":{"id":79381923,"login":"boostsecurity-staging-ci[bot]","display_login":"boostsecurity-staging-ci","gravatar_id":"","url":"https://api.github.com/users/boostsecurity-staging-ci[bot]","avatar_url":"https://avatars.githubusercontent.com/u/79381923?"},"repo":{"id":376948559,"name":"boost-e2e-stage-ci-buildkite/pr-comments","url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3686","repository_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments","labels_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3686/labels{/name}","comments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3686/comments","events_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3686/events","html_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3686","id":949390530,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTAzNjM1","number":3686,"title":"Test PR Comment","user":{"login":"boost-e2e-tester-user","id":74347264,"node_id":"MDQ6VXNlcjc0MzQ3MjY0","avatar_url":"https://avatars.githubusercontent.com/u/74347264?v=4","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-tester-user","html_url":"https://github.com/boost-e2e-tester-user","followers_url":"https://api.github.com/users/boost-e2e-tester-user/followers","following_url":"https://api.github.com/users/boost-e2e-tester-user/following{/other_user}","gists_url":"https://api.github.com/users/boost-e2e-tester-user/gists{/gist_id}","starred_url":"https://api.github.com/users/boost-e2e-tester-user/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boost-e2e-tester-user/subscriptions","organizations_url":"https://api.github.com/users/boost-e2e-tester-user/orgs","repos_url":"https://api.github.com/users/boost-e2e-tester-user/repos","events_url":"https://api.github.com/users/boost-e2e-tester-user/events{/privacy}","received_events_url":"https://api.github.com/users/boost-e2e-tester-user/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2021-07-21T06:57:44Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3686","html_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3686","diff_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3686.diff","patch_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3686.patch"},"body":null,"performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/comments/883942794","html_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3686#issuecomment-883942794","issue_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3686","id":883942794,"node_id":"IC_kwDOFnfHT840r-WK","user":{"login":"boostsecurity-staging-ci[bot]","id":79381923,"node_id":"MDM6Qm90NzkzODE5MjM=","avatar_url":"https://avatars.githubusercontent.com/in/101556?v=4","gravatar_id":"","url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D","html_url":"https://github.com/apps/boostsecurity-staging-ci","followers_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/followers","following_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/repos","events_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/boostsecurity-staging-ci%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","author_association":"NONE","body":"### :warning:  2 New Security Findings\nThe latest commit contains 2 new security issues.\n\n\n\n\n| **Findings**\n| ------------\n| **GCP GCS Acess Logs Off**
    Bucket should log access [ 📘 Learn More](https://docs.boostsecurity.io/rules/gcp-gcs-logs-off.html) https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/blob/45fe656831fffc25cf4ccb5089260d43ae9d18e8/goat.tf#L16-L19\n| **GCP GCS Anon Or Public Access**
    Ensure that Cloud Storage bucket is not anonymously or publicly accessible [ 📘 Learn More](https://docs.boostsecurity.io/rules/gcp-gcs-anon-or-public.html) https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/blob/45fe656831fffc25cf4ccb5089260d43ae9d18e8/goat.tf#L20-L24\n\n\n\n\n[Not an issue?](https://docs.boostsecurity.io/faq/index.html#how-can-i-ignore-a-finding) Ignore it by adding a comment on the line with just the word `noboost`.\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":81248900,"login":"boost-e2e-stage-ci-buildkite","gravatar_id":"","url":"https://api.github.com/orgs/boost-e2e-stage-ci-buildkite","avatar_url":"https://avatars.githubusercontent.com/u/81248900?"}} +{"id":"17244792192","type":"CreateEvent","actor":{"id":61691813,"login":"nik-redhat","display_login":"nik-redhat","gravatar_id":"","url":"https://api.github.com/users/nik-redhat","avatar_url":"https://avatars.githubusercontent.com/u/61691813?"},"repo":{"id":366715532,"name":"nik-redhat/redant","url":"https://api.github.com/repos/nik-redhat/redant"},"payload":{"ref":"test/peer_probe_while_snapd_running","ref_type":"branch","master_branch":"main","description":"A Test Automation Framework for a clustered network filesystem -> GlusterFS","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792202","type":"IssueCommentEvent","actor":{"id":46420768,"login":"JimB40","display_login":"JimB40","gravatar_id":"","url":"https://api.github.com/users/JimB40","avatar_url":"https://avatars.githubusercontent.com/u/46420768?"},"repo":{"id":364871330,"name":"EdgeTX/edgetx","url":"https://api.github.com/repos/EdgeTX/edgetx"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/EdgeTX/edgetx/issues/417","repository_url":"https://api.github.com/repos/EdgeTX/edgetx","labels_url":"https://api.github.com/repos/EdgeTX/edgetx/issues/417/labels{/name}","comments_url":"https://api.github.com/repos/EdgeTX/edgetx/issues/417/comments","events_url":"https://api.github.com/repos/EdgeTX/edgetx/issues/417/events","html_url":"https://github.com/EdgeTX/edgetx/issues/417","id":938800994,"node_id":"MDU6SXNzdWU5Mzg4MDA5OTQ=","number":417,"title":"Warnings can be dismissed by touching anywhere on screen","user":{"login":"jmil-dev","id":55990463,"node_id":"MDQ6VXNlcjU1OTkwNDYz","avatar_url":"https://avatars.githubusercontent.com/u/55990463?v=4","gravatar_id":"","url":"https://api.github.com/users/jmil-dev","html_url":"https://github.com/jmil-dev","followers_url":"https://api.github.com/users/jmil-dev/followers","following_url":"https://api.github.com/users/jmil-dev/following{/other_user}","gists_url":"https://api.github.com/users/jmil-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/jmil-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jmil-dev/subscriptions","organizations_url":"https://api.github.com/users/jmil-dev/orgs","repos_url":"https://api.github.com/users/jmil-dev/repos","events_url":"https://api.github.com/users/jmil-dev/events{/privacy}","received_events_url":"https://api.github.com/users/jmil-dev/received_events","type":"User","site_admin":false},"labels":[{"id":3051489750,"node_id":"MDU6TGFiZWwzMDUxNDg5NzUw","url":"https://api.github.com/repos/EdgeTX/edgetx/labels/UX-UI","name":"UX-UI","color":"118305","default":false,"description":"Related to user experience (UX) or user interface (UI) behaviour"},{"id":3004269185,"node_id":"MDU6TGFiZWwzMDA0MjY5MTg1","url":"https://api.github.com/repos/EdgeTX/edgetx/labels/color","name":"color","color":"4CE8A1","default":false,"description":"Related generally to color LCD radios"},{"id":2976987157,"node_id":"MDU6TGFiZWwyOTc2OTg3MTU3","url":"https://api.github.com/repos/EdgeTX/edgetx/labels/enhancement","name":"enhancement","color":"a2eeef","default":true,"description":"New feature or request"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":12,"created_at":"2021-07-07T11:41:55Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"If you start the radio and a warning appears (throttle/switch/failsafe) it can be dismissed by tapping the screen anywhere.\r\n\r\nThis seems a little unsafe as the screens are capacitive and respond to a very light touch. \r\n\r\nI think there should be a specific onscreen UI element that needs to be tapped - possibly with \"an are you sure\" confirmation, especially around throttle.","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/EdgeTX/edgetx/issues/comments/883942797","html_url":"https://github.com/EdgeTX/edgetx/issues/417#issuecomment-883942797","issue_url":"https://api.github.com/repos/EdgeTX/edgetx/issues/417","id":883942797,"node_id":"IC_kwDOFb9-os40r-WN","user":{"login":"JimB40","id":46420768,"node_id":"MDQ6VXNlcjQ2NDIwNzY4","avatar_url":"https://avatars.githubusercontent.com/u/46420768?v=4","gravatar_id":"","url":"https://api.github.com/users/JimB40","html_url":"https://github.com/JimB40","followers_url":"https://api.github.com/users/JimB40/followers","following_url":"https://api.github.com/users/JimB40/following{/other_user}","gists_url":"https://api.github.com/users/JimB40/gists{/gist_id}","starred_url":"https://api.github.com/users/JimB40/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimB40/subscriptions","organizations_url":"https://api.github.com/users/JimB40/orgs","repos_url":"https://api.github.com/users/JimB40/repos","events_url":"https://api.github.com/users/JimB40/events{/privacy}","received_events_url":"https://api.github.com/users/JimB40/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","author_association":"NONE","body":"Hey we are Edge :)\r\nModern drones firmware will not allow arming with throttle applied.\r\nI have throttle & switches warn off in my quads.\r\nSo throttle & switch warning may appear if option is selected in model setup but surely no warning that any of these options was set to off","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":83762968,"login":"EdgeTX","gravatar_id":"","url":"https://api.github.com/orgs/EdgeTX","avatar_url":"https://avatars.githubusercontent.com/u/83762968?"}} +{"id":"17244792205","type":"PushEvent","actor":{"id":21287366,"login":"GNUxeava","display_login":"GNUxeava","gravatar_id":"","url":"https://api.github.com/users/GNUxeava","avatar_url":"https://avatars.githubusercontent.com/u/21287366?"},"repo":{"id":248956791,"name":"GNUxeava/python","url":"https://api.github.com/repos/GNUxeava/python"},"payload":{"push_id":7561706358,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"c35491930ed202203aa06818c524f14dc17292d8","before":"b6a692c5881542173a75f621032e4ac5174229aa","commits":[{"sha":"c35491930ed202203aa06818c524f14dc17292d8","author":{"name":"GNUxeava","email":"1ba3c160d4a42bb32b833178bb72cf0d96605ab6@protonmail.com"},"message":"queue - initial","distinct":true,"url":"https://api.github.com/repos/GNUxeava/python/commits/c35491930ed202203aa06818c524f14dc17292d8"}]},"public":true,"created_at":"2021-07-21T07:00:02Z"} +{"id":"17244792210","type":"CreateEvent","actor":{"id":13083020,"login":"Matthew-J-Spencer","display_login":"Matthew-J-Spencer","gravatar_id":"","url":"https://api.github.com/users/Matthew-J-Spencer","avatar_url":"https://avatars.githubusercontent.com/u/13083020?"},"repo":{"id":388024730,"name":"tide-foundation/dauth-docs","url":"https://api.github.com/repos/tide-foundation/dauth-docs"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":50648942,"login":"tide-foundation","gravatar_id":"","url":"https://api.github.com/orgs/tide-foundation","avatar_url":"https://avatars.githubusercontent.com/u/50648942?"}} +{"id":"17244792282","type":"PullRequestReviewCommentEvent","actor":{"id":26194949,"login":"andreievg","display_login":"andreievg","gravatar_id":"","url":"https://api.github.com/users/andreievg","avatar_url":"https://avatars.githubusercontent.com/u/26194949?"},"repo":{"id":370520458,"name":"openmsupply/remote-server","url":"https://api.github.com/repos/openmsupply/remote-server"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/openmsupply/remote-server/pulls/comments/673711137","pull_request_review_id":711317379,"id":673711137,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3MzcxMTEzNw==","diff_hunk":"@@ -40,3 +45,41 @@ impl DatabaseSettings {\n )\n }\n }\n+\n+pub enum SettingsError {\n+ Config(ConfigError),\n+ Environment(EnvironmentError),\n+ Path(String),\n+}\n+\n+impl Debug for SettingsError {","path":"src/util/settings.rs","position":20,"original_position":20,"commit_id":"49a384ab75d2e3b639091430a8b1a1e0920a8946","original_commit_id":"49a384ab75d2e3b639091430a8b1a1e0920a8946","user":{"login":"andreievg","id":26194949,"node_id":"MDQ6VXNlcjI2MTk0OTQ5","avatar_url":"https://avatars.githubusercontent.com/u/26194949?v=4","gravatar_id":"","url":"https://api.github.com/users/andreievg","html_url":"https://github.com/andreievg","followers_url":"https://api.github.com/users/andreievg/followers","following_url":"https://api.github.com/users/andreievg/following{/other_user}","gists_url":"https://api.github.com/users/andreievg/gists{/gist_id}","starred_url":"https://api.github.com/users/andreievg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andreievg/subscriptions","organizations_url":"https://api.github.com/users/andreievg/orgs","repos_url":"https://api.github.com/users/andreievg/repos","events_url":"https://api.github.com/users/andreievg/events{/privacy}","received_events_url":"https://api.github.com/users/andreievg/received_events","type":"User","site_admin":false},"body":"True, forget about that, sorry comment miss-leading. (i thought both implementations are fully equivalent) ","created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:03Z","html_url":"https://github.com/openmsupply/remote-server/pull/224#discussion_r673711137","pull_request_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/comments/673711137"},"html":{"href":"https://github.com/openmsupply/remote-server/pull/224#discussion_r673711137"},"pull_request":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224"}},"start_line":null,"original_start_line":null,"start_side":null,"line":55,"original_line":55,"side":"RIGHT","in_reply_to_id":673613457},"pull_request":{"url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224","id":693971695,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzOTcxNjk1","html_url":"https://github.com/openmsupply/remote-server/pull/224","diff_url":"https://github.com/openmsupply/remote-server/pull/224.diff","patch_url":"https://github.com/openmsupply/remote-server/pull/224.patch","issue_url":"https://api.github.com/repos/openmsupply/remote-server/issues/224","number":224,"state":"open","locked":false,"title":"Configuration improvements (improvements)","user":{"login":"wlthomson","id":43223637,"node_id":"MDQ6VXNlcjQzMjIzNjM3","avatar_url":"https://avatars.githubusercontent.com/u/43223637?v=4","gravatar_id":"","url":"https://api.github.com/users/wlthomson","html_url":"https://github.com/wlthomson","followers_url":"https://api.github.com/users/wlthomson/followers","following_url":"https://api.github.com/users/wlthomson/following{/other_user}","gists_url":"https://api.github.com/users/wlthomson/gists{/gist_id}","starred_url":"https://api.github.com/users/wlthomson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wlthomson/subscriptions","organizations_url":"https://api.github.com/users/wlthomson/orgs","repos_url":"https://api.github.com/users/wlthomson/repos","events_url":"https://api.github.com/users/wlthomson/events{/privacy}","received_events_url":"https://api.github.com/users/wlthomson/received_events","type":"User","site_admin":false},"body":"Some more improvements to build on #222.\r\n\r\nE.g. of what the resulting output looks like:\r\n\r\n```\r\nthread 'main' panicked at 'Failed to parse configuration settings: configuration file \"/mnt/c/Users/will-desktop/Desktop/dev/repos/configuration-improvements-improvements/configuratio/base\" not found'\r\n```","created_at":"2021-07-21T01:18:14Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2b3ca1b03bedfa00cf19823ed26e0ca696299d6d","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/commits","review_comments_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/comments","review_comment_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openmsupply/remote-server/issues/224/comments","statuses_url":"https://api.github.com/repos/openmsupply/remote-server/statuses/49a384ab75d2e3b639091430a8b1a1e0920a8946","head":{"label":"openmsupply:configuration-improvements-improvements","ref":"configuration-improvements-improvements","sha":"49a384ab75d2e3b639091430a8b1a1e0920a8946","user":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"repo":{"id":370520458,"node_id":"MDEwOlJlcG9zaXRvcnkzNzA1MjA0NTg=","name":"remote-server","full_name":"openmsupply/remote-server","private":false,"owner":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/openmsupply/remote-server","description":"mSupply Remote Server is an offline-first implementation of mSupply health supply chain management software. It requires an mSupply central server.","fork":false,"url":"https://api.github.com/repos/openmsupply/remote-server","forks_url":"https://api.github.com/repos/openmsupply/remote-server/forks","keys_url":"https://api.github.com/repos/openmsupply/remote-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openmsupply/remote-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openmsupply/remote-server/teams","hooks_url":"https://api.github.com/repos/openmsupply/remote-server/hooks","issue_events_url":"https://api.github.com/repos/openmsupply/remote-server/issues/events{/number}","events_url":"https://api.github.com/repos/openmsupply/remote-server/events","assignees_url":"https://api.github.com/repos/openmsupply/remote-server/assignees{/user}","branches_url":"https://api.github.com/repos/openmsupply/remote-server/branches{/branch}","tags_url":"https://api.github.com/repos/openmsupply/remote-server/tags","blobs_url":"https://api.github.com/repos/openmsupply/remote-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openmsupply/remote-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openmsupply/remote-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/openmsupply/remote-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openmsupply/remote-server/statuses/{sha}","languages_url":"https://api.github.com/repos/openmsupply/remote-server/languages","stargazers_url":"https://api.github.com/repos/openmsupply/remote-server/stargazers","contributors_url":"https://api.github.com/repos/openmsupply/remote-server/contributors","subscribers_url":"https://api.github.com/repos/openmsupply/remote-server/subscribers","subscription_url":"https://api.github.com/repos/openmsupply/remote-server/subscription","commits_url":"https://api.github.com/repos/openmsupply/remote-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/openmsupply/remote-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/openmsupply/remote-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/openmsupply/remote-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/openmsupply/remote-server/contents/{+path}","compare_url":"https://api.github.com/repos/openmsupply/remote-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openmsupply/remote-server/merges","archive_url":"https://api.github.com/repos/openmsupply/remote-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openmsupply/remote-server/downloads","issues_url":"https://api.github.com/repos/openmsupply/remote-server/issues{/number}","pulls_url":"https://api.github.com/repos/openmsupply/remote-server/pulls{/number}","milestones_url":"https://api.github.com/repos/openmsupply/remote-server/milestones{/number}","notifications_url":"https://api.github.com/repos/openmsupply/remote-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openmsupply/remote-server/labels{/name}","releases_url":"https://api.github.com/repos/openmsupply/remote-server/releases{/id}","deployments_url":"https://api.github.com/repos/openmsupply/remote-server/deployments","created_at":"2021-05-25T00:34:00Z","updated_at":"2021-07-21T05:50:00Z","pushed_at":"2021-07-21T05:50:45Z","git_url":"git://github.com/openmsupply/remote-server.git","ssh_url":"git@github.com:openmsupply/remote-server.git","clone_url":"https://github.com/openmsupply/remote-server.git","svn_url":"https://github.com/openmsupply/remote-server","homepage":"","size":648,"stargazers_count":0,"watchers_count":0,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":25,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":25,"watchers":0,"default_branch":"master"}},"base":{"label":"openmsupply:configuration-improvements","ref":"configuration-improvements","sha":"fc5324d1cd71652e5db558cd60b330d2f5c5661f","user":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"repo":{"id":370520458,"node_id":"MDEwOlJlcG9zaXRvcnkzNzA1MjA0NTg=","name":"remote-server","full_name":"openmsupply/remote-server","private":false,"owner":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/openmsupply/remote-server","description":"mSupply Remote Server is an offline-first implementation of mSupply health supply chain management software. It requires an mSupply central server.","fork":false,"url":"https://api.github.com/repos/openmsupply/remote-server","forks_url":"https://api.github.com/repos/openmsupply/remote-server/forks","keys_url":"https://api.github.com/repos/openmsupply/remote-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openmsupply/remote-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openmsupply/remote-server/teams","hooks_url":"https://api.github.com/repos/openmsupply/remote-server/hooks","issue_events_url":"https://api.github.com/repos/openmsupply/remote-server/issues/events{/number}","events_url":"https://api.github.com/repos/openmsupply/remote-server/events","assignees_url":"https://api.github.com/repos/openmsupply/remote-server/assignees{/user}","branches_url":"https://api.github.com/repos/openmsupply/remote-server/branches{/branch}","tags_url":"https://api.github.com/repos/openmsupply/remote-server/tags","blobs_url":"https://api.github.com/repos/openmsupply/remote-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openmsupply/remote-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openmsupply/remote-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/openmsupply/remote-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openmsupply/remote-server/statuses/{sha}","languages_url":"https://api.github.com/repos/openmsupply/remote-server/languages","stargazers_url":"https://api.github.com/repos/openmsupply/remote-server/stargazers","contributors_url":"https://api.github.com/repos/openmsupply/remote-server/contributors","subscribers_url":"https://api.github.com/repos/openmsupply/remote-server/subscribers","subscription_url":"https://api.github.com/repos/openmsupply/remote-server/subscription","commits_url":"https://api.github.com/repos/openmsupply/remote-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/openmsupply/remote-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/openmsupply/remote-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/openmsupply/remote-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/openmsupply/remote-server/contents/{+path}","compare_url":"https://api.github.com/repos/openmsupply/remote-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openmsupply/remote-server/merges","archive_url":"https://api.github.com/repos/openmsupply/remote-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openmsupply/remote-server/downloads","issues_url":"https://api.github.com/repos/openmsupply/remote-server/issues{/number}","pulls_url":"https://api.github.com/repos/openmsupply/remote-server/pulls{/number}","milestones_url":"https://api.github.com/repos/openmsupply/remote-server/milestones{/number}","notifications_url":"https://api.github.com/repos/openmsupply/remote-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openmsupply/remote-server/labels{/name}","releases_url":"https://api.github.com/repos/openmsupply/remote-server/releases{/id}","deployments_url":"https://api.github.com/repos/openmsupply/remote-server/deployments","created_at":"2021-05-25T00:34:00Z","updated_at":"2021-07-21T05:50:00Z","pushed_at":"2021-07-21T05:50:45Z","git_url":"git://github.com/openmsupply/remote-server.git","ssh_url":"git@github.com:openmsupply/remote-server.git","clone_url":"https://github.com/openmsupply/remote-server.git","svn_url":"https://github.com/openmsupply/remote-server","homepage":"","size":648,"stargazers_count":0,"watchers_count":0,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":25,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":25,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224"},"html":{"href":"https://github.com/openmsupply/remote-server/pull/224"},"issue":{"href":"https://api.github.com/repos/openmsupply/remote-server/issues/224"},"comments":{"href":"https://api.github.com/repos/openmsupply/remote-server/issues/224/comments"},"review_comments":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/comments"},"review_comment":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/commits"},"statuses":{"href":"https://api.github.com/repos/openmsupply/remote-server/statuses/49a384ab75d2e3b639091430a8b1a1e0920a8946"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:02Z","org":{"id":45471207,"login":"openmsupply","gravatar_id":"","url":"https://api.github.com/orgs/openmsupply","avatar_url":"https://avatars.githubusercontent.com/u/45471207?"}} +{"id":"17244792217","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561706360,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"35c01d8b8650950a99a1ab9a206bee0acdb37323","before":"e53953c107fe3bf568e838241907ff708661d957","commits":[{"sha":"35c01d8b8650950a99a1ab9a206bee0acdb37323","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/35c01d8b8650950a99a1ab9a206bee0acdb37323"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792218","type":"IssuesEvent","actor":{"id":22133785,"login":"SheepTester","display_login":"SheepTester","gravatar_id":"","url":"https://api.github.com/users/SheepTester","avatar_url":"https://avatars.githubusercontent.com/u/22133785?"},"repo":{"id":264790929,"name":"SheepTester/htmlifier","url":"https://api.github.com/repos/SheepTester/htmlifier"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/SheepTester/htmlifier/issues/102","repository_url":"https://api.github.com/repos/SheepTester/htmlifier","labels_url":"https://api.github.com/repos/SheepTester/htmlifier/issues/102/labels{/name}","comments_url":"https://api.github.com/repos/SheepTester/htmlifier/issues/102/comments","events_url":"https://api.github.com/repos/SheepTester/htmlifier/issues/102/events","html_url":"https://github.com/SheepTester/htmlifier/issues/102","id":949391974,"node_id":"MDU6SXNzdWU5NDkzOTE5NzQ=","number":102,"title":"Multiple pointers","user":{"login":"SheepTester","id":22133785,"node_id":"MDQ6VXNlcjIyMTMzNzg1","avatar_url":"https://avatars.githubusercontent.com/u/22133785?v=4","gravatar_id":"","url":"https://api.github.com/users/SheepTester","html_url":"https://github.com/SheepTester","followers_url":"https://api.github.com/users/SheepTester/followers","following_url":"https://api.github.com/users/SheepTester/following{/other_user}","gists_url":"https://api.github.com/users/SheepTester/gists{/gist_id}","starred_url":"https://api.github.com/users/SheepTester/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SheepTester/subscriptions","organizations_url":"https://api.github.com/users/SheepTester/orgs","repos_url":"https://api.github.com/users/SheepTester/repos","events_url":"https://api.github.com/users/SheepTester/events{/privacy}","received_events_url":"https://api.github.com/users/SheepTester/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"OWNER","active_lock_reason":null,"body":"Ref: https://discord.com/channels/@me/818068592708550696/867079855106555915 (but this wasn't the first time this was asked)\r\n\r\nI've been enlightened and inspired by a recent exchange with an intellectual over email with a project regarding keys and on-screen keyboards. Although on-screen keyboards can't reliably detect for how long keys are held down, they should still be able to work by triggering the hat blocks. This is done by repeatedly sending the isDown: true thing to the VM\r\n\r\nPerhaps something similar can be done with the \"when this sprite clicked\" block. Thus, this issue is multi-part:\r\n\r\n1. Investigate whether postIOData-ing isDown: true for mouse multiple times can trigger the \"when this sprite clicked\" hat block multiple times.\r\n - Hopefully it does. Maybe some finicking might be required (eg if it requires a mouseup before the next mousedown)\r\n - If this is not possible, close this issue.\r\n2. Implement.\r\n - An option to support multiple fingers\r\n - Every pointerdown should cause a isDown: true at the new pointer's location\r\n - should return whether *at least* one pointer is down. Should check what the existing behaviour is\r\n - (mouse x) and (mouse y) can do whatever\r\n\r\nAlso, might consider adding an option to support on-screen keyboards?\r\n\r\nI'm writing this late at night; hopefully when I see this later I don't get confused","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792221","type":"CreateEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388024725,"name":"thatjohn01/781116602","url":"https://api.github.com/repos/thatjohn01/781116602"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":"瞬间人生--一个聪明的傻子的故事_PDF下载_徐光炜","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792224","type":"IssueCommentEvent","actor":{"id":57662928,"login":"LeonidVas","display_login":"LeonidVas","gravatar_id":"","url":"https://api.github.com/users/LeonidVas","avatar_url":"https://avatars.githubusercontent.com/u/57662928?"},"repo":{"id":68709874,"name":"knazarov/mkrepo","url":"https://api.github.com/repos/knazarov/mkrepo"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/knazarov/mkrepo/issues/30","repository_url":"https://api.github.com/repos/knazarov/mkrepo","labels_url":"https://api.github.com/repos/knazarov/mkrepo/issues/30/labels{/name}","comments_url":"https://api.github.com/repos/knazarov/mkrepo/issues/30/comments","events_url":"https://api.github.com/repos/knazarov/mkrepo/issues/30/events","html_url":"https://github.com/knazarov/mkrepo/issues/30","id":947746645,"node_id":"MDU6SXNzdWU5NDc3NDY2NDU=","number":30,"title":"Add an ability to specify \"suite\" and \"version\" for the Release file","user":{"login":"LeonidVas","id":57662928,"node_id":"MDQ6VXNlcjU3NjYyOTI4","avatar_url":"https://avatars.githubusercontent.com/u/57662928?v=4","gravatar_id":"","url":"https://api.github.com/users/LeonidVas","html_url":"https://github.com/LeonidVas","followers_url":"https://api.github.com/users/LeonidVas/followers","following_url":"https://api.github.com/users/LeonidVas/following{/other_user}","gists_url":"https://api.github.com/users/LeonidVas/gists{/gist_id}","starred_url":"https://api.github.com/users/LeonidVas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LeonidVas/subscriptions","organizations_url":"https://api.github.com/users/LeonidVas/orgs","repos_url":"https://api.github.com/users/LeonidVas/repos","events_url":"https://api.github.com/users/LeonidVas/events{/privacy}","received_events_url":"https://api.github.com/users/LeonidVas/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-19T15:02:54Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"author_association":"COLLABORATOR","active_lock_reason":null,"body":"It would be nice to add the ability to specify \"[suite](https://wiki.debian.org/DebianRepository/Format#Suite)\" and \"[version](https://wiki.debian.org/DebianRepository/Format#Version)\" fields for the Release file via environment variables.","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/knazarov/mkrepo/issues/comments/883942798","html_url":"https://github.com/knazarov/mkrepo/issues/30#issuecomment-883942798","issue_url":"https://api.github.com/repos/knazarov/mkrepo/issues/30","id":883942798,"node_id":"IC_kwDOBBht8s40r-WO","user":{"login":"LeonidVas","id":57662928,"node_id":"MDQ6VXNlcjU3NjYyOTI4","avatar_url":"https://avatars.githubusercontent.com/u/57662928?v=4","gravatar_id":"","url":"https://api.github.com/users/LeonidVas","html_url":"https://github.com/LeonidVas","followers_url":"https://api.github.com/users/LeonidVas/followers","following_url":"https://api.github.com/users/LeonidVas/following{/other_user}","gists_url":"https://api.github.com/users/LeonidVas/gists{/gist_id}","starred_url":"https://api.github.com/users/LeonidVas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LeonidVas/subscriptions","organizations_url":"https://api.github.com/users/LeonidVas/orgs","repos_url":"https://api.github.com/users/LeonidVas/repos","events_url":"https://api.github.com/users/LeonidVas/events{/privacy}","received_events_url":"https://api.github.com/users/LeonidVas/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:02Z","updated_at":"2021-07-21T07:00:02Z","author_association":"COLLABORATOR","body":"It won't solve any problem)\r\nBut my motivation was the following:\r\n* these fields are included to the list of fields that \"[have a well defined meaning](https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files)\" \r\n* one of the arguments of `Release` class constructor is `suite`, but it is not uses now (looks like dead code)\r\n* the `suite` field was used in the tarantool repositories","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792230","type":"PushEvent","actor":{"id":1170025,"login":"0xln","display_login":"0xln","gravatar_id":"","url":"https://api.github.com/users/0xln","avatar_url":"https://avatars.githubusercontent.com/u/1170025?"},"repo":{"id":258129694,"name":"0xln/Blog","url":"https://api.github.com/repos/0xln/Blog"},"payload":{"push_id":7561706371,"size":60,"distinct_size":60,"ref":"refs/heads/master","head":"f1d233251256af2a4a456ee88a0bbda18c51e9f6","before":"3fc07833423ccab5e9b4bc3f77c33c6409d04dcf","commits":[{"sha":"ca8844b9e8226a7926c4166e477c757273140cb1","author":{"name":"Daniel","email":"9a831c4df64657a4c34f91b2df20aa333d52fb98@gmail.com"},"message":"Implement a git pre-commit hook for gulp\n\n- Not working as intended.\n- Will fire every time there is an asset change (staged or non-staged).\n- Can't know if asset was optimized already.\n- No way to parse STDIN in git hooks.\n- Can't ask if the user wants to continue or abort commit.\n- More a nuissance than something useful.","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/ca8844b9e8226a7926c4166e477c757273140cb1"},{"sha":"5ec0a96ac19e4ca9829530a85e2430969c1f6e51","author":{"name":"Daniel","email":"9a831c4df64657a4c34f91b2df20aa333d52fb98@gmail.com"},"message":"Rewrite and disable gulp env checker\n\nAs gulp is not being invoked in the hook, it is ancillary.","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/5ec0a96ac19e4ca9829530a85e2430969c1f6e51"},{"sha":"23865bd551c86a73f65b7e7f2b87a3e1b4ab6933","author":{"name":"Daniel","email":"9a831c4df64657a4c34f91b2df20aa333d52fb98@gmail.com"},"message":"Rewrite non-staged assets checker\n\nWill abort if there are any.","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/23865bd551c86a73f65b7e7f2b87a3e1b4ab6933"},{"sha":"cec13feec2c19a18034ce2c06deaf30969fe9454","author":{"name":"Daniel","email":"9a831c4df64657a4c34f91b2df20aa333d52fb98@gmail.com"},"message":"Update documentation\n\nChanges:\n- Move hooks section to another README inside hooks dir.\n- Write instructions for bypassing.\n\nNew use case for pre-commit hook.\n- Before: auto-optimize assets pre-commit (not feasible)\n- After: check if there are non-staged assets, if so abort","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/cec13feec2c19a18034ce2c06deaf30969fe9454"},{"sha":"e12f4f5508c3a9d21b97d2e752dcbea515b4bf4b","author":{"name":"sylhare","email":"4a05f09746a35a1cf3a3b5859001cf244d696663@outlook.com"},"message":"Fix shadow and border","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/e12f4f5508c3a9d21b97d2e752dcbea515b4bf4b"},{"sha":"0b008f73402c0cb64b33173bbe329521abd9def0","author":{"name":"sylhare","email":"4a05f09746a35a1cf3a3b5859001cf244d696663@outlook.com"},"message":"Fix post borders","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/0b008f73402c0cb64b33173bbe329521abd9def0"},{"sha":"9bddee18000da04ec9cf8c21acd21fb3ff0a7aa8","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Sort alphabetically","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/9bddee18000da04ec9cf8c21acd21fb3ff0a7aa8"},{"sha":"610df9b1bf26f6cc7a13fb698e85aaa5bf4284b1","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Add Mastodon, Matrix and Telegram","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/610df9b1bf26f6cc7a13fb698e85aaa5bf4284b1"},{"sha":"ab14020f31da3fda8d4c652485b311597a46e8fb","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Use rel=\"me\" for identity consolidation\n\nAdopt XFN specification\nhttps://gmpg.org/xfn/and/#idconsolidation","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/ab14020f31da3fda8d4c652485b311597a46e8fb"},{"sha":"79c6f3367b24138bb8416809c06cf2cb6d817863","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Merge remote-tracking branch 'upstream/master' into social++","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/79c6f3367b24138bb8416809c06cf2cb6d817863"},{"sha":"e624a8db4133ee8da852f3ec63ecf601b927da1d","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Refactored icons\n\nIt will break configuration.","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/e624a8db4133ee8da852f3ec63ecf601b927da1d"},{"sha":"a6786fcbaef27d34bc2f326331c896dec7adabc3","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Include HackerNews","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/a6786fcbaef27d34bc2f326331c896dec7adabc3"},{"sha":"3ec2eeb9ea37366be82c42127a2e7a6b2f349abf","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Add a bunch of icons","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/3ec2eeb9ea37366be82c42127a2e7a6b2f349abf"},{"sha":"8fb0a1114deae955c518eda993d202b146d0448b","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Move social icons config to _config.yml","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/8fb0a1114deae955c518eda993d202b146d0448b"},{"sha":"07351e38afc09f85ac057ad39d9c046a0afb6164","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Remove deprecated field","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/07351e38afc09f85ac057ad39d9c046a0afb6164"},{"sha":"99e4b295d4ef7605051c14c1a480c11eab8ad3f8","author":{"name":"Daniel Souza","email":"cbaba2936ec94f338df1b8cb56e7afa32611150f@gmail.com"},"message":"Remove empty keys from icons.yml","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/99e4b295d4ef7605051c14c1a480c11eab8ad3f8"},{"sha":"3492998d03f6fc5a41efea7ac1ab6b99a25e14b7","author":{"name":"Hex","email":"51861e947a4430cfac8fc7d0fd8f67891a409f7d@gmail.com"},"message":"added utterances code","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/3492998d03f6fc5a41efea7ac1ab6b99a25e14b7"},{"sha":"cab7706e3a150466032fe4aeec05b1b1d0313051","author":{"name":"Hex","email":"51861e947a4430cfac8fc7d0fd8f67891a409f7d@gmail.com"},"message":"utterances and more changes","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/cab7706e3a150466032fe4aeec05b1b1d0313051"},{"sha":"31a8d2f503e2b984a7ac3171642ed71313518a00","author":{"name":"Hex","email":"51861e947a4430cfac8fc7d0fd8f67891a409f7d@gmail.com"},"message":"utterances and more","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/31a8d2f503e2b984a7ac3171642ed71313518a00"},{"sha":"227693bd07e67570776cf325a86e744cf7e62b2f","author":{"name":"LG","email":"5893d215c38efeb43002898c99f00c12d2fa864e@users.noreply.github.com"},"message":"Update _config.yml","distinct":true,"url":"https://api.github.com/repos/0xln/Blog/commits/227693bd07e67570776cf325a86e744cf7e62b2f"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792231","type":"PushEvent","actor":{"id":18002923,"login":"NickMitrokhin","display_login":"NickMitrokhin","gravatar_id":"","url":"https://api.github.com/users/NickMitrokhin","avatar_url":"https://avatars.githubusercontent.com/u/18002923?"},"repo":{"id":195004922,"name":"NickMitrokhin/DevExtreme","url":"https://api.github.com/repos/NickMitrokhin/DevExtreme"},"payload":{"push_id":7561706367,"size":8,"distinct_size":8,"ref":"refs/heads/20_2","head":"fb0a610c796c95d565827b41d49cb50572546ff5","before":"d018af2dd8b1561b79839742cff4b68df35f798f","commits":[{"sha":"f36d29b88af90f95baca1965715630368a4cca39","author":{"name":"Roman Semenov","email":"84c51f19620479f033e23f12095b1ace11570037@devexpress.com"},"message":"DataGrid - displays two loading indicators if the widget contains fixed columns (T1011801) (#18268)\n\n* fix\r\n\r\n(cherry picked from commit b54aa53900164b9af6fc33f381a0c720f8b4cada)\r\n\r\n* fix review\r\n\r\n(cherry picked from commit 8a6ddc286ea577906160fa41e2c6b375f32e923f)\r\n(cherry picked from commit 270e7f76e2871f42187f7b0b9ec61f4103d2c3b2)\r\n\r\n* add tests after review\r\n\r\n(cherry picked from commit bd04e833667051a88ba3c147a688e5d6c2efb1e8)\r\n(cherry picked from commit 66fb5375a35ebe9c78ba68e3ddbf89819996ded3)\r\n\r\n* remove debug","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/f36d29b88af90f95baca1965715630368a4cca39"},{"sha":"8b6498401c5e5c7abae583c008fa872c1e5b54b5","author":{"name":"Ignatov Dan","email":"0fd226043cd3fd2c5e0de7ea20bbc39fc86b9830@gmail.com"},"message":"Form - Move \"layoutManager._renderLabel\" to \"form.utils.js\" (#18312) (#18332)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/8b6498401c5e5c7abae583c008fa872c1e5b54b5"},{"sha":"02be756c5d1a6bf7e3d49f60b3e6efd7db7bb803","author":{"name":"Denis Gradoboev","email":"87c731d9a37e1881588da53fa52a8216d79eaac1@yandex.com"},"message":"dxScheduler - Fix blinking test (#17949) (#18352)\n\n(cherry picked from commit 2ad7302fcd4a86176de32288ad26bd5f1d75b816)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/02be756c5d1a6bf7e3d49f60b3e6efd7db7bb803"},{"sha":"31c0f03af189754942f4711abc529f1e24b9a50f","author":{"name":"VictorSavushkin","email":"69c2a72a78f84ca06064830f5e81a125eac53766@users.noreply.github.com"},"message":"Diagram - Nested-containers issues, T1005859 (#18354)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/31c0f03af189754942f4711abc529f1e24b9a50f"},{"sha":"ca798cecaec1b0b09c47f2384c79c9963bc356de","author":{"name":"Nick Mitrokhin","email":"f4ca497b1504a889c3f8703aa018117c001cf8ba@users.noreply.github.com"},"message":"datagrid: fix horizontal gap between header and content when the master detail is expanded (T1004507) (#18328) (#18330)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/ca798cecaec1b0b09c47f2384c79c9963bc356de"},{"sha":"0150f9a647cd912b1f408eca33d375534d6ed331","author":{"name":"Sergey Novikov","email":"d780c892552784cdfe42a463f8f5aa4a13ac14e6@users.noreply.github.com"},"message":"Bugfix T1014291 (#18365)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/0150f9a647cd912b1f408eca33d375534d6ed331"},{"sha":"01e1eeab5babe8cdee0fced32a3b7bca6fbc49a6","author":{"name":"Evgeniy.Zaborshchikov","email":"6a38ea8f42f0fa9e94b8bc2adc2c88891ce5060a@users.noreply.github.com"},"message":"LDML Date Parser: getRegExpInfo should return correct regular expression for unambiguous not separated `formats` and throw warning message in another case. (T1008667) (#18117) (#18364)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/01e1eeab5babe8cdee0fced32a3b7bca6fbc49a6"},{"sha":"fb0a610c796c95d565827b41d49cb50572546ff5","author":{"name":"Evgeniy.Zaborshchikov","email":"6a38ea8f42f0fa9e94b8bc2adc2c88891ce5060a@users.noreply.github.com"},"message":"Attach CSS device classes if viewport already exist (T1010826) (#18368) (#18379)","distinct":true,"url":"https://api.github.com/repos/NickMitrokhin/DevExtreme/commits/fb0a610c796c95d565827b41d49cb50572546ff5"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792236","type":"PushEvent","actor":{"id":85949713,"login":"BolliAkshitha","display_login":"BolliAkshitha","gravatar_id":"","url":"https://api.github.com/users/BolliAkshitha","avatar_url":"https://avatars.githubusercontent.com/u/85949713?"},"repo":{"id":387741243,"name":"BolliAkshitha/-pps-programs","url":"https://api.github.com/repos/BolliAkshitha/-pps-programs"},"payload":{"push_id":7561706366,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"a5e53b6d9b2eb50870c5c658881e2b321fabb306","before":"eae30e2f2f1628d87314d90426cff4c2227fcf18","commits":[{"sha":"a5e53b6d9b2eb50870c5c658881e2b321fabb306","author":{"name":"BolliAkshitha","email":"11a15edc12b5187612cb7e76e328723098c5aa6d@users.noreply.github.com"},"message":"Add files via upload","distinct":true,"url":"https://api.github.com/repos/BolliAkshitha/-pps-programs/commits/a5e53b6d9b2eb50870c5c658881e2b321fabb306"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792248","type":"PushEvent","actor":{"id":86811115,"login":"bamba280","display_login":"bamba280","gravatar_id":"","url":"https://api.github.com/users/bamba280","avatar_url":"https://avatars.githubusercontent.com/u/86811115?"},"repo":{"id":383036127,"name":"bamba280/personal","url":"https://api.github.com/repos/bamba280/personal"},"payload":{"push_id":7561706378,"size":1,"distinct_size":1,"ref":"refs/heads/itakura","head":"771b965ba4dd636cd551e9ad0ac625293798feff","before":"b5ddca06dc6b03dcae4ea47236af53f1f1a2a145","commits":[{"sha":"771b965ba4dd636cd551e9ad0ac625293798feff","author":{"name":"itakura","email":"672d5521ad75418c07cfc6811eed4acf9c9cc4ed@test.com"},"message":"15:58暫定詳細基本設計","distinct":true,"url":"https://api.github.com/repos/bamba280/personal/commits/771b965ba4dd636cd551e9ad0ac625293798feff"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792250","type":"WatchEvent","actor":{"id":74425534,"login":"sjoveska","display_login":"sjoveska","gravatar_id":"","url":"https://api.github.com/users/sjoveska","avatar_url":"https://avatars.githubusercontent.com/u/74425534?"},"repo":{"id":374119728,"name":"gabrieldim/Shopping-Cart-And-Products-Layout","url":"https://api.github.com/repos/gabrieldim/Shopping-Cart-And-Products-Layout"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792253","type":"PushEvent","actor":{"id":22544249,"login":"c-mertes","display_login":"c-mertes","gravatar_id":"","url":"https://api.github.com/users/c-mertes","avatar_url":"https://avatars.githubusercontent.com/u/22544249?"},"repo":{"id":213693892,"name":"gagneurlab/drop","url":"https://api.github.com/repos/gagneurlab/drop"},"payload":{"push_id":7561706387,"size":1,"distinct_size":1,"ref":"refs/heads/dev","head":"572beb4e979bd3c87022ec971d9e23e44c5096fe","before":"cd98bcd687772f603ada82cfdb06b082ce269aba","commits":[{"sha":"572beb4e979bd3c87022ec971d9e23e44c5096fe","author":{"name":"Christian Mertes","email":"0d9545ff4dc5b65d4f01bf587f8b55debe71de03@in.tum.de"},"message":"Update tests/pipeline/test_MAE.py","distinct":true,"url":"https://api.github.com/repos/gagneurlab/drop/commits/572beb4e979bd3c87022ec971d9e23e44c5096fe"}]},"public":true,"created_at":"2021-07-21T07:00:03Z","org":{"id":29893284,"login":"gagneurlab","gravatar_id":"","url":"https://api.github.com/orgs/gagneurlab","avatar_url":"https://avatars.githubusercontent.com/u/29893284?"}} +{"id":"17244792257","type":"PushEvent","actor":{"id":10744793,"login":"alisonthemonster","display_login":"alisonthemonster","gravatar_id":"","url":"https://api.github.com/users/alisonthemonster","avatar_url":"https://avatars.githubusercontent.com/u/10744793?"},"repo":{"id":162841152,"name":"alisonthemonster/Presently","url":"https://api.github.com/repos/alisonthemonster/Presently"},"payload":{"push_id":7561706379,"size":1,"distinct_size":1,"ref":"refs/heads/logging","head":"97eae360f686236c044250ed843e963245802083","before":"bf0249e4d273ca317b63f1bb7490a2eff217a031","commits":[{"sha":"97eae360f686236c044250ed843e963245802083","author":{"name":"alisonthemonster","email":"21daf519ef5309dff350061433670e592b28dc2a@gmail.com"},"message":"Fix issues with test themes","distinct":true,"url":"https://api.github.com/repos/alisonthemonster/Presently/commits/97eae360f686236c044250ed843e963245802083"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792264","type":"PushEvent","actor":{"id":46604893,"login":"rohankalbag","display_login":"rohankalbag","gravatar_id":"","url":"https://api.github.com/users/rohankalbag","avatar_url":"https://avatars.githubusercontent.com/u/46604893?"},"repo":{"id":383245102,"name":"rohankalbag/20d170033_IntroToAppDev","url":"https://api.github.com/repos/rohankalbag/20d170033_IntroToAppDev"},"payload":{"push_id":7561706384,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"0e17714b040753438f6b8e6e41731d68a4671d33","before":"5db7cd2e4b02612c2b074747c10f1e580a1eb133","commits":[{"sha":"0e17714b040753438f6b8e6e41731d68a4671d33","author":{"name":"Rohan Rajesh Kalbag","email":"0f0320094c4077ce81e56a721bfdb2bbce8d0b50@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/rohankalbag/20d170033_IntroToAppDev/commits/0e17714b040753438f6b8e6e41731d68a4671d33"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792274","type":"PullRequestReviewEvent","actor":{"id":26194949,"login":"andreievg","display_login":"andreievg","gravatar_id":"","url":"https://api.github.com/users/andreievg","avatar_url":"https://avatars.githubusercontent.com/u/26194949?"},"repo":{"id":370520458,"name":"openmsupply/remote-server","url":"https://api.github.com/repos/openmsupply/remote-server"},"payload":{"action":"created","review":{"id":711317379,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3Mzc5","user":{"login":"andreievg","id":26194949,"node_id":"MDQ6VXNlcjI2MTk0OTQ5","avatar_url":"https://avatars.githubusercontent.com/u/26194949?v=4","gravatar_id":"","url":"https://api.github.com/users/andreievg","html_url":"https://github.com/andreievg","followers_url":"https://api.github.com/users/andreievg/followers","following_url":"https://api.github.com/users/andreievg/following{/other_user}","gists_url":"https://api.github.com/users/andreievg/gists{/gist_id}","starred_url":"https://api.github.com/users/andreievg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andreievg/subscriptions","organizations_url":"https://api.github.com/users/andreievg/orgs","repos_url":"https://api.github.com/users/andreievg/repos","events_url":"https://api.github.com/users/andreievg/events{/privacy}","received_events_url":"https://api.github.com/users/andreievg/received_events","type":"User","site_admin":false},"body":null,"commit_id":"49a384ab75d2e3b639091430a8b1a1e0920a8946","submitted_at":"2021-07-21T07:00:02Z","state":"commented","html_url":"https://github.com/openmsupply/remote-server/pull/224#pullrequestreview-711317379","pull_request_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224","author_association":"NONE","_links":{"html":{"href":"https://github.com/openmsupply/remote-server/pull/224#pullrequestreview-711317379"},"pull_request":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224"}}},"pull_request":{"url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224","id":693971695,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzOTcxNjk1","html_url":"https://github.com/openmsupply/remote-server/pull/224","diff_url":"https://github.com/openmsupply/remote-server/pull/224.diff","patch_url":"https://github.com/openmsupply/remote-server/pull/224.patch","issue_url":"https://api.github.com/repos/openmsupply/remote-server/issues/224","number":224,"state":"open","locked":false,"title":"Configuration improvements (improvements)","user":{"login":"wlthomson","id":43223637,"node_id":"MDQ6VXNlcjQzMjIzNjM3","avatar_url":"https://avatars.githubusercontent.com/u/43223637?v=4","gravatar_id":"","url":"https://api.github.com/users/wlthomson","html_url":"https://github.com/wlthomson","followers_url":"https://api.github.com/users/wlthomson/followers","following_url":"https://api.github.com/users/wlthomson/following{/other_user}","gists_url":"https://api.github.com/users/wlthomson/gists{/gist_id}","starred_url":"https://api.github.com/users/wlthomson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wlthomson/subscriptions","organizations_url":"https://api.github.com/users/wlthomson/orgs","repos_url":"https://api.github.com/users/wlthomson/repos","events_url":"https://api.github.com/users/wlthomson/events{/privacy}","received_events_url":"https://api.github.com/users/wlthomson/received_events","type":"User","site_admin":false},"body":"Some more improvements to build on #222.\r\n\r\nE.g. of what the resulting output looks like:\r\n\r\n```\r\nthread 'main' panicked at 'Failed to parse configuration settings: configuration file \"/mnt/c/Users/will-desktop/Desktop/dev/repos/configuration-improvements-improvements/configuratio/base\" not found'\r\n```","created_at":"2021-07-21T01:18:14Z","updated_at":"2021-07-21T07:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2b3ca1b03bedfa00cf19823ed26e0ca696299d6d","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/commits","review_comments_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/comments","review_comment_url":"https://api.github.com/repos/openmsupply/remote-server/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openmsupply/remote-server/issues/224/comments","statuses_url":"https://api.github.com/repos/openmsupply/remote-server/statuses/49a384ab75d2e3b639091430a8b1a1e0920a8946","head":{"label":"openmsupply:configuration-improvements-improvements","ref":"configuration-improvements-improvements","sha":"49a384ab75d2e3b639091430a8b1a1e0920a8946","user":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"repo":{"id":370520458,"node_id":"MDEwOlJlcG9zaXRvcnkzNzA1MjA0NTg=","name":"remote-server","full_name":"openmsupply/remote-server","private":false,"owner":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/openmsupply/remote-server","description":"mSupply Remote Server is an offline-first implementation of mSupply health supply chain management software. It requires an mSupply central server.","fork":false,"url":"https://api.github.com/repos/openmsupply/remote-server","forks_url":"https://api.github.com/repos/openmsupply/remote-server/forks","keys_url":"https://api.github.com/repos/openmsupply/remote-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openmsupply/remote-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openmsupply/remote-server/teams","hooks_url":"https://api.github.com/repos/openmsupply/remote-server/hooks","issue_events_url":"https://api.github.com/repos/openmsupply/remote-server/issues/events{/number}","events_url":"https://api.github.com/repos/openmsupply/remote-server/events","assignees_url":"https://api.github.com/repos/openmsupply/remote-server/assignees{/user}","branches_url":"https://api.github.com/repos/openmsupply/remote-server/branches{/branch}","tags_url":"https://api.github.com/repos/openmsupply/remote-server/tags","blobs_url":"https://api.github.com/repos/openmsupply/remote-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openmsupply/remote-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openmsupply/remote-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/openmsupply/remote-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openmsupply/remote-server/statuses/{sha}","languages_url":"https://api.github.com/repos/openmsupply/remote-server/languages","stargazers_url":"https://api.github.com/repos/openmsupply/remote-server/stargazers","contributors_url":"https://api.github.com/repos/openmsupply/remote-server/contributors","subscribers_url":"https://api.github.com/repos/openmsupply/remote-server/subscribers","subscription_url":"https://api.github.com/repos/openmsupply/remote-server/subscription","commits_url":"https://api.github.com/repos/openmsupply/remote-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/openmsupply/remote-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/openmsupply/remote-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/openmsupply/remote-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/openmsupply/remote-server/contents/{+path}","compare_url":"https://api.github.com/repos/openmsupply/remote-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openmsupply/remote-server/merges","archive_url":"https://api.github.com/repos/openmsupply/remote-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openmsupply/remote-server/downloads","issues_url":"https://api.github.com/repos/openmsupply/remote-server/issues{/number}","pulls_url":"https://api.github.com/repos/openmsupply/remote-server/pulls{/number}","milestones_url":"https://api.github.com/repos/openmsupply/remote-server/milestones{/number}","notifications_url":"https://api.github.com/repos/openmsupply/remote-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openmsupply/remote-server/labels{/name}","releases_url":"https://api.github.com/repos/openmsupply/remote-server/releases{/id}","deployments_url":"https://api.github.com/repos/openmsupply/remote-server/deployments","created_at":"2021-05-25T00:34:00Z","updated_at":"2021-07-21T05:50:00Z","pushed_at":"2021-07-21T05:50:45Z","git_url":"git://github.com/openmsupply/remote-server.git","ssh_url":"git@github.com:openmsupply/remote-server.git","clone_url":"https://github.com/openmsupply/remote-server.git","svn_url":"https://github.com/openmsupply/remote-server","homepage":"","size":648,"stargazers_count":0,"watchers_count":0,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":25,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":25,"watchers":0,"default_branch":"master"}},"base":{"label":"openmsupply:configuration-improvements","ref":"configuration-improvements","sha":"fc5324d1cd71652e5db558cd60b330d2f5c5661f","user":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"repo":{"id":370520458,"node_id":"MDEwOlJlcG9zaXRvcnkzNzA1MjA0NTg=","name":"remote-server","full_name":"openmsupply/remote-server","private":false,"owner":{"login":"openmsupply","id":45471207,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1NDcxMjA3","avatar_url":"https://avatars.githubusercontent.com/u/45471207?v=4","gravatar_id":"","url":"https://api.github.com/users/openmsupply","html_url":"https://github.com/openmsupply","followers_url":"https://api.github.com/users/openmsupply/followers","following_url":"https://api.github.com/users/openmsupply/following{/other_user}","gists_url":"https://api.github.com/users/openmsupply/gists{/gist_id}","starred_url":"https://api.github.com/users/openmsupply/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openmsupply/subscriptions","organizations_url":"https://api.github.com/users/openmsupply/orgs","repos_url":"https://api.github.com/users/openmsupply/repos","events_url":"https://api.github.com/users/openmsupply/events{/privacy}","received_events_url":"https://api.github.com/users/openmsupply/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/openmsupply/remote-server","description":"mSupply Remote Server is an offline-first implementation of mSupply health supply chain management software. It requires an mSupply central server.","fork":false,"url":"https://api.github.com/repos/openmsupply/remote-server","forks_url":"https://api.github.com/repos/openmsupply/remote-server/forks","keys_url":"https://api.github.com/repos/openmsupply/remote-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openmsupply/remote-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openmsupply/remote-server/teams","hooks_url":"https://api.github.com/repos/openmsupply/remote-server/hooks","issue_events_url":"https://api.github.com/repos/openmsupply/remote-server/issues/events{/number}","events_url":"https://api.github.com/repos/openmsupply/remote-server/events","assignees_url":"https://api.github.com/repos/openmsupply/remote-server/assignees{/user}","branches_url":"https://api.github.com/repos/openmsupply/remote-server/branches{/branch}","tags_url":"https://api.github.com/repos/openmsupply/remote-server/tags","blobs_url":"https://api.github.com/repos/openmsupply/remote-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openmsupply/remote-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openmsupply/remote-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/openmsupply/remote-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openmsupply/remote-server/statuses/{sha}","languages_url":"https://api.github.com/repos/openmsupply/remote-server/languages","stargazers_url":"https://api.github.com/repos/openmsupply/remote-server/stargazers","contributors_url":"https://api.github.com/repos/openmsupply/remote-server/contributors","subscribers_url":"https://api.github.com/repos/openmsupply/remote-server/subscribers","subscription_url":"https://api.github.com/repos/openmsupply/remote-server/subscription","commits_url":"https://api.github.com/repos/openmsupply/remote-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/openmsupply/remote-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/openmsupply/remote-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/openmsupply/remote-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/openmsupply/remote-server/contents/{+path}","compare_url":"https://api.github.com/repos/openmsupply/remote-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openmsupply/remote-server/merges","archive_url":"https://api.github.com/repos/openmsupply/remote-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openmsupply/remote-server/downloads","issues_url":"https://api.github.com/repos/openmsupply/remote-server/issues{/number}","pulls_url":"https://api.github.com/repos/openmsupply/remote-server/pulls{/number}","milestones_url":"https://api.github.com/repos/openmsupply/remote-server/milestones{/number}","notifications_url":"https://api.github.com/repos/openmsupply/remote-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openmsupply/remote-server/labels{/name}","releases_url":"https://api.github.com/repos/openmsupply/remote-server/releases{/id}","deployments_url":"https://api.github.com/repos/openmsupply/remote-server/deployments","created_at":"2021-05-25T00:34:00Z","updated_at":"2021-07-21T05:50:00Z","pushed_at":"2021-07-21T05:50:45Z","git_url":"git://github.com/openmsupply/remote-server.git","ssh_url":"git@github.com:openmsupply/remote-server.git","clone_url":"https://github.com/openmsupply/remote-server.git","svn_url":"https://github.com/openmsupply/remote-server","homepage":"","size":648,"stargazers_count":0,"watchers_count":0,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":25,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":25,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224"},"html":{"href":"https://github.com/openmsupply/remote-server/pull/224"},"issue":{"href":"https://api.github.com/repos/openmsupply/remote-server/issues/224"},"comments":{"href":"https://api.github.com/repos/openmsupply/remote-server/issues/224/comments"},"review_comments":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/comments"},"review_comment":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openmsupply/remote-server/pulls/224/commits"},"statuses":{"href":"https://api.github.com/repos/openmsupply/remote-server/statuses/49a384ab75d2e3b639091430a8b1a1e0920a8946"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:03Z","org":{"id":45471207,"login":"openmsupply","gravatar_id":"","url":"https://api.github.com/orgs/openmsupply","avatar_url":"https://avatars.githubusercontent.com/u/45471207?"}} +{"id":"17244792278","type":"PushEvent","actor":{"id":69447371,"login":"brendanlsz","display_login":"brendanlsz","gravatar_id":"","url":"https://api.github.com/users/brendanlsz","avatar_url":"https://avatars.githubusercontent.com/u/69447371?"},"repo":{"id":382786978,"name":"brendanlsz/ShopZen","url":"https://api.github.com/repos/brendanlsz/ShopZen"},"payload":{"push_id":7561706396,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"7fedcab4d46245a58d368af638df04340611848b","before":"b77a498c95207a1846888f91cb735bd7974e4df8","commits":[{"sha":"7fedcab4d46245a58d368af638df04340611848b","author":{"name":"brendanlsz","email":"d80cfc9cb09728247f0fb81a8e9e55d1f1e766fe@gmail.com"},"message":"slide show","distinct":true,"url":"https://api.github.com/repos/brendanlsz/ShopZen/commits/7fedcab4d46245a58d368af638df04340611848b"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792285","type":"WatchEvent","actor":{"id":19624222,"login":"benlhy","display_login":"benlhy","gravatar_id":"","url":"https://api.github.com/users/benlhy","avatar_url":"https://avatars.githubusercontent.com/u/19624222?"},"repo":{"id":379133630,"name":"GovTechSIOT/decada-embedded-example-zephyr","url":"https://api.github.com/repos/GovTechSIOT/decada-embedded-example-zephyr"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:03Z","org":{"id":53718274,"login":"GovTechSIOT","gravatar_id":"","url":"https://api.github.com/orgs/GovTechSIOT","avatar_url":"https://avatars.githubusercontent.com/u/53718274?"}} +{"id":"17244792296","type":"PushEvent","actor":{"id":2687598,"login":"milesholt","display_login":"milesholt","gravatar_id":"","url":"https://api.github.com/users/milesholt","avatar_url":"https://avatars.githubusercontent.com/u/2687598?"},"repo":{"id":247480564,"name":"milesholt/autotrade1","url":"https://api.github.com/repos/milesholt/autotrade1"},"payload":{"push_id":7561706392,"size":1,"distinct_size":1,"ref":"refs/heads/version2","head":"b295fb49fd192fbb1a8fdcdcc88d0291b0d9b446","before":"996ccda165162d1b4f2a1ed7e1b4e3c489de9cdb","commits":[{"sha":"b295fb49fd192fbb1a8fdcdcc88d0291b0d9b446","author":{"name":"milesholt","email":"1a73af9e7ae00182733b2292511b814be66f065f@milesholt.co.uk"},"message":"File updated - July 21, 2021 8:00 AM","distinct":true,"url":"https://api.github.com/repos/milesholt/autotrade1/commits/b295fb49fd192fbb1a8fdcdcc88d0291b0d9b446"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792297","type":"PullRequestEvent","actor":{"id":17320781,"login":"krtk6160","display_login":"krtk6160","gravatar_id":"","url":"https://api.github.com/users/krtk6160","avatar_url":"https://avatars.githubusercontent.com/u/17320781?"},"repo":{"id":348296522,"name":"GaloyMoney/galoy","url":"https://api.github.com/repos/GaloyMoney/galoy"},"payload":{"action":"opened","number":362,"pull_request":{"url":"https://api.github.com/repos/GaloyMoney/galoy/pulls/362","id":694104882,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODgy","html_url":"https://github.com/GaloyMoney/galoy/pull/362","diff_url":"https://github.com/GaloyMoney/galoy/pull/362.diff","patch_url":"https://github.com/GaloyMoney/galoy/pull/362.patch","issue_url":"https://api.github.com/repos/GaloyMoney/galoy/issues/362","number":362,"state":"open","locked":false,"title":"Upgrade bitcoind chart to v0.2.1","user":{"login":"krtk6160","id":17320781,"node_id":"MDQ6VXNlcjE3MzIwNzgx","avatar_url":"https://avatars.githubusercontent.com/u/17320781?v=4","gravatar_id":"","url":"https://api.github.com/users/krtk6160","html_url":"https://github.com/krtk6160","followers_url":"https://api.github.com/users/krtk6160/followers","following_url":"https://api.github.com/users/krtk6160/following{/other_user}","gists_url":"https://api.github.com/users/krtk6160/gists{/gist_id}","starred_url":"https://api.github.com/users/krtk6160/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/krtk6160/subscriptions","organizations_url":"https://api.github.com/users/krtk6160/orgs","repos_url":"https://api.github.com/users/krtk6160/repos","events_url":"https://api.github.com/users/krtk6160/events{/privacy}","received_events_url":"https://api.github.com/users/krtk6160/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:00:03Z","updated_at":"2021-07-21T07:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/GaloyMoney/galoy/pulls/362/commits","review_comments_url":"https://api.github.com/repos/GaloyMoney/galoy/pulls/362/comments","review_comment_url":"https://api.github.com/repos/GaloyMoney/galoy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/GaloyMoney/galoy/issues/362/comments","statuses_url":"https://api.github.com/repos/GaloyMoney/galoy/statuses/9f19792e67e909b714e3453dc80e9215429f4500","head":{"label":"GaloyMoney:upgrade_bitcoind_0.2.1","ref":"upgrade_bitcoind_0.2.1","sha":"9f19792e67e909b714e3453dc80e9215429f4500","user":{"login":"GaloyMoney","id":77592451,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3NTkyNDUx","avatar_url":"https://avatars.githubusercontent.com/u/77592451?v=4","gravatar_id":"","url":"https://api.github.com/users/GaloyMoney","html_url":"https://github.com/GaloyMoney","followers_url":"https://api.github.com/users/GaloyMoney/followers","following_url":"https://api.github.com/users/GaloyMoney/following{/other_user}","gists_url":"https://api.github.com/users/GaloyMoney/gists{/gist_id}","starred_url":"https://api.github.com/users/GaloyMoney/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GaloyMoney/subscriptions","organizations_url":"https://api.github.com/users/GaloyMoney/orgs","repos_url":"https://api.github.com/users/GaloyMoney/repos","events_url":"https://api.github.com/users/GaloyMoney/events{/privacy}","received_events_url":"https://api.github.com/users/GaloyMoney/received_events","type":"Organization","site_admin":false},"repo":{"id":348296522,"node_id":"MDEwOlJlcG9zaXRvcnkzNDgyOTY1MjI=","name":"galoy","full_name":"GaloyMoney/galoy","private":false,"owner":{"login":"GaloyMoney","id":77592451,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3NTkyNDUx","avatar_url":"https://avatars.githubusercontent.com/u/77592451?v=4","gravatar_id":"","url":"https://api.github.com/users/GaloyMoney","html_url":"https://github.com/GaloyMoney","followers_url":"https://api.github.com/users/GaloyMoney/followers","following_url":"https://api.github.com/users/GaloyMoney/following{/other_user}","gists_url":"https://api.github.com/users/GaloyMoney/gists{/gist_id}","starred_url":"https://api.github.com/users/GaloyMoney/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GaloyMoney/subscriptions","organizations_url":"https://api.github.com/users/GaloyMoney/orgs","repos_url":"https://api.github.com/users/GaloyMoney/repos","events_url":"https://api.github.com/users/GaloyMoney/events{/privacy}","received_events_url":"https://api.github.com/users/GaloyMoney/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/GaloyMoney/galoy","description":"bitcoin banking infrastructure","fork":false,"url":"https://api.github.com/repos/GaloyMoney/galoy","forks_url":"https://api.github.com/repos/GaloyMoney/galoy/forks","keys_url":"https://api.github.com/repos/GaloyMoney/galoy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GaloyMoney/galoy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GaloyMoney/galoy/teams","hooks_url":"https://api.github.com/repos/GaloyMoney/galoy/hooks","issue_events_url":"https://api.github.com/repos/GaloyMoney/galoy/issues/events{/number}","events_url":"https://api.github.com/repos/GaloyMoney/galoy/events","assignees_url":"https://api.github.com/repos/GaloyMoney/galoy/assignees{/user}","branches_url":"https://api.github.com/repos/GaloyMoney/galoy/branches{/branch}","tags_url":"https://api.github.com/repos/GaloyMoney/galoy/tags","blobs_url":"https://api.github.com/repos/GaloyMoney/galoy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GaloyMoney/galoy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GaloyMoney/galoy/git/refs{/sha}","trees_url":"https://api.github.com/repos/GaloyMoney/galoy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GaloyMoney/galoy/statuses/{sha}","languages_url":"https://api.github.com/repos/GaloyMoney/galoy/languages","stargazers_url":"https://api.github.com/repos/GaloyMoney/galoy/stargazers","contributors_url":"https://api.github.com/repos/GaloyMoney/galoy/contributors","subscribers_url":"https://api.github.com/repos/GaloyMoney/galoy/subscribers","subscription_url":"https://api.github.com/repos/GaloyMoney/galoy/subscription","commits_url":"https://api.github.com/repos/GaloyMoney/galoy/commits{/sha}","git_commits_url":"https://api.github.com/repos/GaloyMoney/galoy/git/commits{/sha}","comments_url":"https://api.github.com/repos/GaloyMoney/galoy/comments{/number}","issue_comment_url":"https://api.github.com/repos/GaloyMoney/galoy/issues/comments{/number}","contents_url":"https://api.github.com/repos/GaloyMoney/galoy/contents/{+path}","compare_url":"https://api.github.com/repos/GaloyMoney/galoy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GaloyMoney/galoy/merges","archive_url":"https://api.github.com/repos/GaloyMoney/galoy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GaloyMoney/galoy/downloads","issues_url":"https://api.github.com/repos/GaloyMoney/galoy/issues{/number}","pulls_url":"https://api.github.com/repos/GaloyMoney/galoy/pulls{/number}","milestones_url":"https://api.github.com/repos/GaloyMoney/galoy/milestones{/number}","notifications_url":"https://api.github.com/repos/GaloyMoney/galoy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GaloyMoney/galoy/labels{/name}","releases_url":"https://api.github.com/repos/GaloyMoney/galoy/releases{/id}","deployments_url":"https://api.github.com/repos/GaloyMoney/galoy/deployments","created_at":"2021-03-16T10:02:03Z","updated_at":"2021-07-21T04:40:53Z","pushed_at":"2021-07-21T07:00:03Z","git_url":"git://github.com/GaloyMoney/galoy.git","ssh_url":"git@github.com:GaloyMoney/galoy.git","clone_url":"https://github.com/GaloyMoney/galoy.git","svn_url":"https://github.com/GaloyMoney/galoy","homepage":"https://galoy.io","size":5209,"stargazers_count":145,"watchers_count":145,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":24,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":122,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":24,"open_issues":122,"watchers":145,"default_branch":"main"}},"base":{"label":"GaloyMoney:main","ref":"main","sha":"96bc61cd6e74daea0be458551eabc113885da01b","user":{"login":"GaloyMoney","id":77592451,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3NTkyNDUx","avatar_url":"https://avatars.githubusercontent.com/u/77592451?v=4","gravatar_id":"","url":"https://api.github.com/users/GaloyMoney","html_url":"https://github.com/GaloyMoney","followers_url":"https://api.github.com/users/GaloyMoney/followers","following_url":"https://api.github.com/users/GaloyMoney/following{/other_user}","gists_url":"https://api.github.com/users/GaloyMoney/gists{/gist_id}","starred_url":"https://api.github.com/users/GaloyMoney/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GaloyMoney/subscriptions","organizations_url":"https://api.github.com/users/GaloyMoney/orgs","repos_url":"https://api.github.com/users/GaloyMoney/repos","events_url":"https://api.github.com/users/GaloyMoney/events{/privacy}","received_events_url":"https://api.github.com/users/GaloyMoney/received_events","type":"Organization","site_admin":false},"repo":{"id":348296522,"node_id":"MDEwOlJlcG9zaXRvcnkzNDgyOTY1MjI=","name":"galoy","full_name":"GaloyMoney/galoy","private":false,"owner":{"login":"GaloyMoney","id":77592451,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3NTkyNDUx","avatar_url":"https://avatars.githubusercontent.com/u/77592451?v=4","gravatar_id":"","url":"https://api.github.com/users/GaloyMoney","html_url":"https://github.com/GaloyMoney","followers_url":"https://api.github.com/users/GaloyMoney/followers","following_url":"https://api.github.com/users/GaloyMoney/following{/other_user}","gists_url":"https://api.github.com/users/GaloyMoney/gists{/gist_id}","starred_url":"https://api.github.com/users/GaloyMoney/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GaloyMoney/subscriptions","organizations_url":"https://api.github.com/users/GaloyMoney/orgs","repos_url":"https://api.github.com/users/GaloyMoney/repos","events_url":"https://api.github.com/users/GaloyMoney/events{/privacy}","received_events_url":"https://api.github.com/users/GaloyMoney/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/GaloyMoney/galoy","description":"bitcoin banking infrastructure","fork":false,"url":"https://api.github.com/repos/GaloyMoney/galoy","forks_url":"https://api.github.com/repos/GaloyMoney/galoy/forks","keys_url":"https://api.github.com/repos/GaloyMoney/galoy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GaloyMoney/galoy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GaloyMoney/galoy/teams","hooks_url":"https://api.github.com/repos/GaloyMoney/galoy/hooks","issue_events_url":"https://api.github.com/repos/GaloyMoney/galoy/issues/events{/number}","events_url":"https://api.github.com/repos/GaloyMoney/galoy/events","assignees_url":"https://api.github.com/repos/GaloyMoney/galoy/assignees{/user}","branches_url":"https://api.github.com/repos/GaloyMoney/galoy/branches{/branch}","tags_url":"https://api.github.com/repos/GaloyMoney/galoy/tags","blobs_url":"https://api.github.com/repos/GaloyMoney/galoy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GaloyMoney/galoy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GaloyMoney/galoy/git/refs{/sha}","trees_url":"https://api.github.com/repos/GaloyMoney/galoy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GaloyMoney/galoy/statuses/{sha}","languages_url":"https://api.github.com/repos/GaloyMoney/galoy/languages","stargazers_url":"https://api.github.com/repos/GaloyMoney/galoy/stargazers","contributors_url":"https://api.github.com/repos/GaloyMoney/galoy/contributors","subscribers_url":"https://api.github.com/repos/GaloyMoney/galoy/subscribers","subscription_url":"https://api.github.com/repos/GaloyMoney/galoy/subscription","commits_url":"https://api.github.com/repos/GaloyMoney/galoy/commits{/sha}","git_commits_url":"https://api.github.com/repos/GaloyMoney/galoy/git/commits{/sha}","comments_url":"https://api.github.com/repos/GaloyMoney/galoy/comments{/number}","issue_comment_url":"https://api.github.com/repos/GaloyMoney/galoy/issues/comments{/number}","contents_url":"https://api.github.com/repos/GaloyMoney/galoy/contents/{+path}","compare_url":"https://api.github.com/repos/GaloyMoney/galoy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GaloyMoney/galoy/merges","archive_url":"https://api.github.com/repos/GaloyMoney/galoy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GaloyMoney/galoy/downloads","issues_url":"https://api.github.com/repos/GaloyMoney/galoy/issues{/number}","pulls_url":"https://api.github.com/repos/GaloyMoney/galoy/pulls{/number}","milestones_url":"https://api.github.com/repos/GaloyMoney/galoy/milestones{/number}","notifications_url":"https://api.github.com/repos/GaloyMoney/galoy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GaloyMoney/galoy/labels{/name}","releases_url":"https://api.github.com/repos/GaloyMoney/galoy/releases{/id}","deployments_url":"https://api.github.com/repos/GaloyMoney/galoy/deployments","created_at":"2021-03-16T10:02:03Z","updated_at":"2021-07-21T04:40:53Z","pushed_at":"2021-07-21T07:00:03Z","git_url":"git://github.com/GaloyMoney/galoy.git","ssh_url":"git@github.com:GaloyMoney/galoy.git","clone_url":"https://github.com/GaloyMoney/galoy.git","svn_url":"https://github.com/GaloyMoney/galoy","homepage":"https://galoy.io","size":5209,"stargazers_count":145,"watchers_count":145,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":24,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":122,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":24,"open_issues":122,"watchers":145,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/GaloyMoney/galoy/pulls/362"},"html":{"href":"https://github.com/GaloyMoney/galoy/pull/362"},"issue":{"href":"https://api.github.com/repos/GaloyMoney/galoy/issues/362"},"comments":{"href":"https://api.github.com/repos/GaloyMoney/galoy/issues/362/comments"},"review_comments":{"href":"https://api.github.com/repos/GaloyMoney/galoy/pulls/362/comments"},"review_comment":{"href":"https://api.github.com/repos/GaloyMoney/galoy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/GaloyMoney/galoy/pulls/362/commits"},"statuses":{"href":"https://api.github.com/repos/GaloyMoney/galoy/statuses/9f19792e67e909b714e3453dc80e9215429f4500"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T07:00:03Z","org":{"id":77592451,"login":"GaloyMoney","gravatar_id":"","url":"https://api.github.com/orgs/GaloyMoney","avatar_url":"https://avatars.githubusercontent.com/u/77592451?"}} +{"id":"17244792312","type":"IssuesEvent","actor":{"id":172204,"login":"BohuTANG","display_login":"BohuTANG","gravatar_id":"","url":"https://api.github.com/users/BohuTANG","avatar_url":"https://avatars.githubusercontent.com/u/172204?"},"repo":{"id":302827809,"name":"datafuselabs/datafuse","url":"https://api.github.com/repos/datafuselabs/datafuse"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/datafuselabs/datafuse/issues/1136","repository_url":"https://api.github.com/repos/datafuselabs/datafuse","labels_url":"https://api.github.com/repos/datafuselabs/datafuse/issues/1136/labels{/name}","comments_url":"https://api.github.com/repos/datafuselabs/datafuse/issues/1136/comments","events_url":"https://api.github.com/repos/datafuselabs/datafuse/issues/1136/events","html_url":"https://github.com/datafuselabs/datafuse/issues/1136","id":949391980,"node_id":"MDU6SXNzdWU5NDkzOTE5ODA=","number":1136,"title":"[security] Http api TLS support","user":{"login":"BohuTANG","id":172204,"node_id":"MDQ6VXNlcjE3MjIwNA==","avatar_url":"https://avatars.githubusercontent.com/u/172204?v=4","gravatar_id":"","url":"https://api.github.com/users/BohuTANG","html_url":"https://github.com/BohuTANG","followers_url":"https://api.github.com/users/BohuTANG/followers","following_url":"https://api.github.com/users/BohuTANG/following{/other_user}","gists_url":"https://api.github.com/users/BohuTANG/gists{/gist_id}","starred_url":"https://api.github.com/users/BohuTANG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BohuTANG/subscriptions","organizations_url":"https://api.github.com/users/BohuTANG/orgs","repos_url":"https://api.github.com/users/BohuTANG/repos","events_url":"https://api.github.com/users/BohuTANG/events{/privacy}","received_events_url":"https://api.github.com/users/BohuTANG/received_events","type":"User","site_admin":false},"labels":[{"id":2414530921,"node_id":"MDU6TGFiZWwyNDE0NTMwOTIx","url":"https://api.github.com/repos/datafuselabs/datafuse/labels/feature","name":"feature","color":"FBCA04","default":false,"description":"Extra attention is needed"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T07:00:03Z","updated_at":"2021-07-21T07:00:03Z","closed_at":null,"author_association":"MEMBER","active_lock_reason":null,"body":"**Summary**\r\n\r\nAdd TLS for warp http.\r\nExample:\r\nhttps://github.com/seanmonstar/warp/blob/master/examples/tls.rs\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:03Z","org":{"id":80994548,"login":"datafuselabs","gravatar_id":"","url":"https://api.github.com/orgs/datafuselabs","avatar_url":"https://avatars.githubusercontent.com/u/80994548?"}} +{"id":"17244792315","type":"PushEvent","actor":{"id":22863512,"login":"YURARINGACHEV","display_login":"YURARINGACHEV","gravatar_id":"","url":"https://api.github.com/users/YURARINGACHEV","avatar_url":"https://avatars.githubusercontent.com/u/22863512?"},"repo":{"id":385816364,"name":"YURARINGACHEV/ruby_on_rails","url":"https://api.github.com/repos/YURARINGACHEV/ruby_on_rails"},"payload":{"push_id":7561706409,"size":1,"distinct_size":1,"ref":"refs/heads/lesson3","head":"9936f4b6860c408cea8f15936147043faaf57577","before":"f8ed6c06e0991a6b2776b44237be75d1e9a9f1d0","commits":[{"sha":"9936f4b6860c408cea8f15936147043faaf57577","author":{"name":"YOUR NAME","email":"23f6a0713fca098434027764f6a2a425f389fdab"},"message":"asd","distinct":true,"url":"https://api.github.com/repos/YURARINGACHEV/ruby_on_rails/commits/9936f4b6860c408cea8f15936147043faaf57577"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792332","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561706415,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8bad30072ea48ae8c430eb696c742391758231c1","before":"35c01d8b8650950a99a1ab9a206bee0acdb37323","commits":[{"sha":"8bad30072ea48ae8c430eb696c742391758231c1","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/8bad30072ea48ae8c430eb696c742391758231c1"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792342","type":"PushEvent","actor":{"id":38880224,"login":"fazchanneltv","display_login":"fazchanneltv","gravatar_id":"","url":"https://api.github.com/users/fazchanneltv","avatar_url":"https://avatars.githubusercontent.com/u/38880224?"},"repo":{"id":387105424,"name":"fazchanneltv/radio","url":"https://api.github.com/repos/fazchanneltv/radio"},"payload":{"push_id":7561706426,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"1605fcf33ed775f7baee35c8753f20a0e64833d6","before":"e5a48336e34f985d9c204c5c7444dcd892f8b66a","commits":[{"sha":"1605fcf33ed775f7baee35c8753f20a0e64833d6","author":{"name":"fazchanneltv","email":"7588a9eb7622457cfef32d700780dfed85ec5b9f@users.noreply.github.com"},"message":"Update style.css","distinct":true,"url":"https://api.github.com/repos/fazchanneltv/radio/commits/1605fcf33ed775f7baee35c8753f20a0e64833d6"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792344","type":"PushEvent","actor":{"id":46918839,"login":"rosa2070","display_login":"rosa2070","gravatar_id":"","url":"https://api.github.com/users/rosa2070","avatar_url":"https://avatars.githubusercontent.com/u/46918839?"},"repo":{"id":381922075,"name":"rosa2070/FAST_JAVA_AGAIN","url":"https://api.github.com/repos/rosa2070/FAST_JAVA_AGAIN"},"payload":{"push_id":7561706431,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"beac12e09e878cf6241ea256306b27df553ea4e6","before":"94f34ddb9358ee8db0c454b1b0ba4f3a41ad8868","commits":[{"sha":"beac12e09e878cf6241ea256306b27df553ea4e6","author":{"name":"jaeeunhyun","email":"4276311f62018bfb314b438b493116bcf870a6da@naver.com"},"message":"sgfd","distinct":true,"url":"https://api.github.com/repos/rosa2070/FAST_JAVA_AGAIN/commits/beac12e09e878cf6241ea256306b27df553ea4e6"}]},"public":true,"created_at":"2021-07-21T07:00:03Z"} +{"id":"17244792366","type":"PushEvent","actor":{"id":20573784,"login":"DavidCambre","display_login":"DavidCambre","gravatar_id":"","url":"https://api.github.com/users/DavidCambre","avatar_url":"https://avatars.githubusercontent.com/u/20573784?"},"repo":{"id":365583432,"name":"DavidCambre/godot","url":"https://api.github.com/repos/DavidCambre/godot"},"payload":{"push_id":7561706427,"size":1,"distinct_size":1,"ref":"refs/heads/Expose_VisualScriptCustomNode_TypeHints_m","head":"d03175f22ceac5b61bd90f7e0209dbef9734d2c3","before":"5807ec1d95f581a8875012976344a2054b56ac20","commits":[{"sha":"d03175f22ceac5b61bd90f7e0209dbef9734d2c3","author":{"name":"DavidCambre","email":"04d80ad3d6b9d9ff92b6f39db74f421b124ca051@gmail.com"},"message":"Update modules/visual_script/doc_classes/VisualScriptCustomNode.xml\n\nCo-authored-by: Rémi Verschelde ","distinct":true,"url":"https://api.github.com/repos/DavidCambre/godot/commits/d03175f22ceac5b61bd90f7e0209dbef9734d2c3"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792370","type":"PullRequestEvent","actor":{"id":32645537,"login":"Beziel","display_login":"Beziel","gravatar_id":"","url":"https://api.github.com/users/Beziel","avatar_url":"https://avatars.githubusercontent.com/u/32645537?"},"repo":{"id":388022780,"name":"Beziel/demo-gitflow","url":"https://api.github.com/repos/Beziel/demo-gitflow"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/1","id":694104836,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODM2","html_url":"https://github.com/Beziel/demo-gitflow/pull/1","diff_url":"https://github.com/Beziel/demo-gitflow/pull/1.diff","patch_url":"https://github.com/Beziel/demo-gitflow/pull/1.patch","issue_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues/1","number":1,"state":"closed","locked":false,"title":"Release/1.0.0","user":{"login":"Beziel","id":32645537,"node_id":"MDQ6VXNlcjMyNjQ1NTM3","avatar_url":"https://avatars.githubusercontent.com/u/32645537?v=4","gravatar_id":"","url":"https://api.github.com/users/Beziel","html_url":"https://github.com/Beziel","followers_url":"https://api.github.com/users/Beziel/followers","following_url":"https://api.github.com/users/Beziel/following{/other_user}","gists_url":"https://api.github.com/users/Beziel/gists{/gist_id}","starred_url":"https://api.github.com/users/Beziel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Beziel/subscriptions","organizations_url":"https://api.github.com/users/Beziel/orgs","repos_url":"https://api.github.com/users/Beziel/repos","events_url":"https://api.github.com/users/Beziel/events{/privacy}","received_events_url":"https://api.github.com/users/Beziel/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T06:59:57Z","updated_at":"2021-07-21T07:00:03Z","closed_at":"2021-07-21T07:00:03Z","merged_at":"2021-07-21T07:00:03Z","merge_commit_sha":"6f6d75cd1d878d21f95fa8873c3c21088d4106fe","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/1/commits","review_comments_url":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/1/comments","review_comment_url":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues/1/comments","statuses_url":"https://api.github.com/repos/Beziel/demo-gitflow/statuses/2fa94f06b8eb3319ddc8bb7984a91d71da44529d","head":{"label":"Beziel:release/1.0.0","ref":"release/1.0.0","sha":"2fa94f06b8eb3319ddc8bb7984a91d71da44529d","user":{"login":"Beziel","id":32645537,"node_id":"MDQ6VXNlcjMyNjQ1NTM3","avatar_url":"https://avatars.githubusercontent.com/u/32645537?v=4","gravatar_id":"","url":"https://api.github.com/users/Beziel","html_url":"https://github.com/Beziel","followers_url":"https://api.github.com/users/Beziel/followers","following_url":"https://api.github.com/users/Beziel/following{/other_user}","gists_url":"https://api.github.com/users/Beziel/gists{/gist_id}","starred_url":"https://api.github.com/users/Beziel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Beziel/subscriptions","organizations_url":"https://api.github.com/users/Beziel/orgs","repos_url":"https://api.github.com/users/Beziel/repos","events_url":"https://api.github.com/users/Beziel/events{/privacy}","received_events_url":"https://api.github.com/users/Beziel/received_events","type":"User","site_admin":false},"repo":{"id":388022780,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMjI3ODA=","name":"demo-gitflow","full_name":"Beziel/demo-gitflow","private":false,"owner":{"login":"Beziel","id":32645537,"node_id":"MDQ6VXNlcjMyNjQ1NTM3","avatar_url":"https://avatars.githubusercontent.com/u/32645537?v=4","gravatar_id":"","url":"https://api.github.com/users/Beziel","html_url":"https://github.com/Beziel","followers_url":"https://api.github.com/users/Beziel/followers","following_url":"https://api.github.com/users/Beziel/following{/other_user}","gists_url":"https://api.github.com/users/Beziel/gists{/gist_id}","starred_url":"https://api.github.com/users/Beziel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Beziel/subscriptions","organizations_url":"https://api.github.com/users/Beziel/orgs","repos_url":"https://api.github.com/users/Beziel/repos","events_url":"https://api.github.com/users/Beziel/events{/privacy}","received_events_url":"https://api.github.com/users/Beziel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Beziel/demo-gitflow","description":null,"fork":false,"url":"https://api.github.com/repos/Beziel/demo-gitflow","forks_url":"https://api.github.com/repos/Beziel/demo-gitflow/forks","keys_url":"https://api.github.com/repos/Beziel/demo-gitflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Beziel/demo-gitflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Beziel/demo-gitflow/teams","hooks_url":"https://api.github.com/repos/Beziel/demo-gitflow/hooks","issue_events_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues/events{/number}","events_url":"https://api.github.com/repos/Beziel/demo-gitflow/events","assignees_url":"https://api.github.com/repos/Beziel/demo-gitflow/assignees{/user}","branches_url":"https://api.github.com/repos/Beziel/demo-gitflow/branches{/branch}","tags_url":"https://api.github.com/repos/Beziel/demo-gitflow/tags","blobs_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Beziel/demo-gitflow/statuses/{sha}","languages_url":"https://api.github.com/repos/Beziel/demo-gitflow/languages","stargazers_url":"https://api.github.com/repos/Beziel/demo-gitflow/stargazers","contributors_url":"https://api.github.com/repos/Beziel/demo-gitflow/contributors","subscribers_url":"https://api.github.com/repos/Beziel/demo-gitflow/subscribers","subscription_url":"https://api.github.com/repos/Beziel/demo-gitflow/subscription","commits_url":"https://api.github.com/repos/Beziel/demo-gitflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/Beziel/demo-gitflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/Beziel/demo-gitflow/contents/{+path}","compare_url":"https://api.github.com/repos/Beziel/demo-gitflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Beziel/demo-gitflow/merges","archive_url":"https://api.github.com/repos/Beziel/demo-gitflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Beziel/demo-gitflow/downloads","issues_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues{/number}","pulls_url":"https://api.github.com/repos/Beziel/demo-gitflow/pulls{/number}","milestones_url":"https://api.github.com/repos/Beziel/demo-gitflow/milestones{/number}","notifications_url":"https://api.github.com/repos/Beziel/demo-gitflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Beziel/demo-gitflow/labels{/name}","releases_url":"https://api.github.com/repos/Beziel/demo-gitflow/releases{/id}","deployments_url":"https://api.github.com/repos/Beziel/demo-gitflow/deployments","created_at":"2021-07-21T06:52:11Z","updated_at":"2021-07-21T06:56:15Z","pushed_at":"2021-07-21T07:00:03Z","git_url":"git://github.com/Beziel/demo-gitflow.git","ssh_url":"git@github.com:Beziel/demo-gitflow.git","clone_url":"https://github.com/Beziel/demo-gitflow.git","svn_url":"https://github.com/Beziel/demo-gitflow","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"Beziel:main","ref":"main","sha":"18c286a19563567013176cc441b849d1835536b5","user":{"login":"Beziel","id":32645537,"node_id":"MDQ6VXNlcjMyNjQ1NTM3","avatar_url":"https://avatars.githubusercontent.com/u/32645537?v=4","gravatar_id":"","url":"https://api.github.com/users/Beziel","html_url":"https://github.com/Beziel","followers_url":"https://api.github.com/users/Beziel/followers","following_url":"https://api.github.com/users/Beziel/following{/other_user}","gists_url":"https://api.github.com/users/Beziel/gists{/gist_id}","starred_url":"https://api.github.com/users/Beziel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Beziel/subscriptions","organizations_url":"https://api.github.com/users/Beziel/orgs","repos_url":"https://api.github.com/users/Beziel/repos","events_url":"https://api.github.com/users/Beziel/events{/privacy}","received_events_url":"https://api.github.com/users/Beziel/received_events","type":"User","site_admin":false},"repo":{"id":388022780,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMjI3ODA=","name":"demo-gitflow","full_name":"Beziel/demo-gitflow","private":false,"owner":{"login":"Beziel","id":32645537,"node_id":"MDQ6VXNlcjMyNjQ1NTM3","avatar_url":"https://avatars.githubusercontent.com/u/32645537?v=4","gravatar_id":"","url":"https://api.github.com/users/Beziel","html_url":"https://github.com/Beziel","followers_url":"https://api.github.com/users/Beziel/followers","following_url":"https://api.github.com/users/Beziel/following{/other_user}","gists_url":"https://api.github.com/users/Beziel/gists{/gist_id}","starred_url":"https://api.github.com/users/Beziel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Beziel/subscriptions","organizations_url":"https://api.github.com/users/Beziel/orgs","repos_url":"https://api.github.com/users/Beziel/repos","events_url":"https://api.github.com/users/Beziel/events{/privacy}","received_events_url":"https://api.github.com/users/Beziel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Beziel/demo-gitflow","description":null,"fork":false,"url":"https://api.github.com/repos/Beziel/demo-gitflow","forks_url":"https://api.github.com/repos/Beziel/demo-gitflow/forks","keys_url":"https://api.github.com/repos/Beziel/demo-gitflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Beziel/demo-gitflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Beziel/demo-gitflow/teams","hooks_url":"https://api.github.com/repos/Beziel/demo-gitflow/hooks","issue_events_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues/events{/number}","events_url":"https://api.github.com/repos/Beziel/demo-gitflow/events","assignees_url":"https://api.github.com/repos/Beziel/demo-gitflow/assignees{/user}","branches_url":"https://api.github.com/repos/Beziel/demo-gitflow/branches{/branch}","tags_url":"https://api.github.com/repos/Beziel/demo-gitflow/tags","blobs_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Beziel/demo-gitflow/statuses/{sha}","languages_url":"https://api.github.com/repos/Beziel/demo-gitflow/languages","stargazers_url":"https://api.github.com/repos/Beziel/demo-gitflow/stargazers","contributors_url":"https://api.github.com/repos/Beziel/demo-gitflow/contributors","subscribers_url":"https://api.github.com/repos/Beziel/demo-gitflow/subscribers","subscription_url":"https://api.github.com/repos/Beziel/demo-gitflow/subscription","commits_url":"https://api.github.com/repos/Beziel/demo-gitflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/Beziel/demo-gitflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/Beziel/demo-gitflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/Beziel/demo-gitflow/contents/{+path}","compare_url":"https://api.github.com/repos/Beziel/demo-gitflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Beziel/demo-gitflow/merges","archive_url":"https://api.github.com/repos/Beziel/demo-gitflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Beziel/demo-gitflow/downloads","issues_url":"https://api.github.com/repos/Beziel/demo-gitflow/issues{/number}","pulls_url":"https://api.github.com/repos/Beziel/demo-gitflow/pulls{/number}","milestones_url":"https://api.github.com/repos/Beziel/demo-gitflow/milestones{/number}","notifications_url":"https://api.github.com/repos/Beziel/demo-gitflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Beziel/demo-gitflow/labels{/name}","releases_url":"https://api.github.com/repos/Beziel/demo-gitflow/releases{/id}","deployments_url":"https://api.github.com/repos/Beziel/demo-gitflow/deployments","created_at":"2021-07-21T06:52:11Z","updated_at":"2021-07-21T06:56:15Z","pushed_at":"2021-07-21T07:00:03Z","git_url":"git://github.com/Beziel/demo-gitflow.git","ssh_url":"git@github.com:Beziel/demo-gitflow.git","clone_url":"https://github.com/Beziel/demo-gitflow.git","svn_url":"https://github.com/Beziel/demo-gitflow","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/1"},"html":{"href":"https://github.com/Beziel/demo-gitflow/pull/1"},"issue":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/issues/1"},"comments":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/Beziel/demo-gitflow/statuses/2fa94f06b8eb3319ddc8bb7984a91d71da44529d"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"Beziel","id":32645537,"node_id":"MDQ6VXNlcjMyNjQ1NTM3","avatar_url":"https://avatars.githubusercontent.com/u/32645537?v=4","gravatar_id":"","url":"https://api.github.com/users/Beziel","html_url":"https://github.com/Beziel","followers_url":"https://api.github.com/users/Beziel/followers","following_url":"https://api.github.com/users/Beziel/following{/other_user}","gists_url":"https://api.github.com/users/Beziel/gists{/gist_id}","starred_url":"https://api.github.com/users/Beziel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Beziel/subscriptions","organizations_url":"https://api.github.com/users/Beziel/orgs","repos_url":"https://api.github.com/users/Beziel/repos","events_url":"https://api.github.com/users/Beziel/events{/privacy}","received_events_url":"https://api.github.com/users/Beziel/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":1,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792373","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":248646333,"name":"miwebst/ssRunner","url":"https://api.github.com/repos/miwebst/ssRunner"},"payload":{"push_id":7561706437,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"9624c2d0a26660b9cad330fede032c2500bccab9","before":"354d364064aad6fd2fbb6d0de79ea7cbc70c99f7","commits":[{"sha":"9624c2d0a26660b9cad330fede032c2500bccab9","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 7:00:03 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunner/commits/9624c2d0a26660b9cad330fede032c2500bccab9"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792374","type":"CreateEvent","actor":{"id":72749136,"login":"kotawonism","display_login":"kotawonism","gravatar_id":"","url":"https://api.github.com/users/kotawonism","avatar_url":"https://avatars.githubusercontent.com/u/72749136?"},"repo":{"id":388024733,"name":"kotawonism/react","url":"https://api.github.com/repos/kotawonism/react"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792375","type":"PushEvent","actor":{"id":7485710,"login":"ToryZhou","display_login":"ToryZhou","gravatar_id":"","url":"https://api.github.com/users/ToryZhou","avatar_url":"https://avatars.githubusercontent.com/u/7485710?"},"repo":{"id":96061680,"name":"ToryZhou/draw_io","url":"https://api.github.com/repos/ToryZhou/draw_io"},"payload":{"push_id":7561706439,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"b6ae60e8f8d985ea6d8c27b87e954d7f9d10046b","before":"d35679e3c5dc1d15c088105ac18431fff50fad3c","commits":[{"sha":"b6ae60e8f8d985ea6d8c27b87e954d7f9d10046b","author":{"name":"torychow","email":"33a4d31ca61fafe4b6830b4dacabdd1c4d7f84c8@qq.com"},"message":"Update BOP Architecture.drawio","distinct":true,"url":"https://api.github.com/repos/ToryZhou/draw_io/commits/b6ae60e8f8d985ea6d8c27b87e954d7f9d10046b"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792379","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":249503742,"name":"miwebst/ssRunnerReact","url":"https://api.github.com/repos/miwebst/ssRunnerReact"},"payload":{"push_id":7561706445,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"add793f2731d9f62648b4c7649c2d9d4c11c6756","before":"afd2755f5dff8597f69c0393af6d56531944e2c6","commits":[{"sha":"add793f2731d9f62648b4c7649c2d9d4c11c6756","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 7:00:03 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunnerReact/commits/add793f2731d9f62648b4c7649c2d9d4c11c6756"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792380","type":"PushEvent","actor":{"id":40586421,"login":"himobi","display_login":"himobi","gravatar_id":"","url":"https://api.github.com/users/himobi","avatar_url":"https://avatars.githubusercontent.com/u/40586421?"},"repo":{"id":138676186,"name":"himobi/hotspot","url":"https://api.github.com/repos/himobi/hotspot"},"payload":{"push_id":7561706440,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"0c9401da4a36551a87c89709651300b0bffc159d","before":"03c99342ff10ebc36212046bead68f82f73c402b","commits":[{"sha":"0c9401da4a36551a87c89709651300b0bffc159d","author":{"name":"himobi","email":"25be52bce459ebff50c126f5ac512648f87cbe75@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/himobi/hotspot/commits/0c9401da4a36551a87c89709651300b0bffc159d"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792381","type":"PushEvent","actor":{"id":13587915,"login":"BinBear","display_login":"BinBear","gravatar_id":"","url":"https://api.github.com/users/BinBear","avatar_url":"https://avatars.githubusercontent.com/u/13587915?"},"repo":{"id":387381308,"name":"BinBear/LeetCode","url":"https://api.github.com/repos/BinBear/LeetCode"},"payload":{"push_id":7561706438,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"646c27d26cccd6ba4265b51cb2c273704d45f36a","before":"6a6d296beb5e54727ba6cd5d74ea333a30feb1df","commits":[{"sha":"646c27d26cccd6ba4265b51cb2c273704d45f36a","author":{"name":"vin","email":"bda1f7ce004368cbf3b542b02dc078cc7ccbc88c@vindeMacBook-Pro.local"},"message":"📝 增加【字符串】-->【最长回文子串】","distinct":true,"url":"https://api.github.com/repos/BinBear/LeetCode/commits/646c27d26cccd6ba4265b51cb2c273704d45f36a"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792389","type":"PushEvent","actor":{"id":70911038,"login":"ultimate-hosts-blacklist-bot","display_login":"ultimate-hosts-blacklist-bot","gravatar_id":"","url":"https://api.github.com/users/ultimate-hosts-blacklist-bot","avatar_url":"https://avatars.githubusercontent.com/u/70911038?"},"repo":{"id":130666244,"name":"Ultimate-Hosts-Blacklist/Phishing.Database","url":"https://api.github.com/repos/Ultimate-Hosts-Blacklist/Phishing.Database"},"payload":{"push_id":7561706444,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"dbb7485e90044039e16f4780e8fbfcf3492068cc","before":"563eeda6bf44ce9ca2cf94682e8c556524bc9895","commits":[{"sha":"dbb7485e90044039e16f4780e8fbfcf3492068cc","author":{"name":"ultimate-hosts-blacklist-bot","email":"cf66b13d5686610465f885e6ad57f099f056751f@outlook.com"},"message":"[Autosave] Testing for Ultimate Hosts Blacklist\n\ni_XhjJ0NaPyauHrc3udblTGi","distinct":true,"url":"https://api.github.com/repos/Ultimate-Hosts-Blacklist/Phishing.Database/commits/dbb7485e90044039e16f4780e8fbfcf3492068cc"}]},"public":true,"created_at":"2021-07-21T07:00:04Z","org":{"id":37181187,"login":"Ultimate-Hosts-Blacklist","gravatar_id":"","url":"https://api.github.com/orgs/Ultimate-Hosts-Blacklist","avatar_url":"https://avatars.githubusercontent.com/u/37181187?"}} +{"id":"17244792390","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":249783941,"name":"miwebst/ssRunnerAngular","url":"https://api.github.com/repos/miwebst/ssRunnerAngular"},"payload":{"push_id":7561706448,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"57ce98911eb84b6e591c4f8e40628105450489cd","before":"6981c514462269d49236a08a170c8005d571f72b","commits":[{"sha":"57ce98911eb84b6e591c4f8e40628105450489cd","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 7:00:03 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunnerAngular/commits/57ce98911eb84b6e591c4f8e40628105450489cd"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792398","type":"CreateEvent","actor":{"id":71575781,"login":"Shin-sibainu","display_login":"Shin-sibainu","gravatar_id":"","url":"https://api.github.com/users/Shin-sibainu","avatar_url":"https://avatars.githubusercontent.com/u/71575781?"},"repo":{"id":388002580,"name":"Shin-sibainu/react-redux-crud-application","url":"https://api.github.com/repos/Shin-sibainu/react-redux-crud-application"},"payload":{"ref":"hello-world","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792400","type":"PushEvent","actor":{"id":82876799,"login":"hvaish01","display_login":"hvaish01","gravatar_id":"","url":"https://api.github.com/users/hvaish01","avatar_url":"https://avatars.githubusercontent.com/u/82876799?"},"repo":{"id":359772248,"name":"hvaish01/StixBundles","url":"https://api.github.com/repos/hvaish01/StixBundles"},"payload":{"push_id":7561706454,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"48f098644e5efee0604c33496b7d10ec32444853","before":"0aa3c41daa61b362a00ef78ea33f64354d1ada99","commits":[{"sha":"48f098644e5efee0604c33496b7d10ec32444853","author":{"name":"hvaish01","email":"2afd248fb67ac19cdeb0148e32ca7ee71c30c76d@users.noreply.github.com"},"message":"Updated at 12:00:02 21-07-2021","distinct":true,"url":"https://api.github.com/repos/hvaish01/StixBundles/commits/48f098644e5efee0604c33496b7d10ec32444853"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792403","type":"IssuesEvent","actor":{"id":10670690,"login":"pankaj-ag","display_login":"pankaj-ag","gravatar_id":"","url":"https://api.github.com/users/pankaj-ag","avatar_url":"https://avatars.githubusercontent.com/u/10670690?"},"repo":{"id":125008594,"name":"ColoredCow/portal","url":"https://api.github.com/repos/ColoredCow/portal"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/ColoredCow/portal/issues/797","repository_url":"https://api.github.com/repos/ColoredCow/portal","labels_url":"https://api.github.com/repos/ColoredCow/portal/issues/797/labels{/name}","comments_url":"https://api.github.com/repos/ColoredCow/portal/issues/797/comments","events_url":"https://api.github.com/repos/ColoredCow/portal/issues/797/events","html_url":"https://github.com/ColoredCow/portal/issues/797","id":949391988,"node_id":"MDU6SXNzdWU5NDkzOTE5ODg=","number":797,"title":"Rename conversion rate to conversion rate diff in the invoice module. ","user":{"login":"pankaj-ag","id":10670690,"node_id":"MDQ6VXNlcjEwNjcwNjkw","avatar_url":"https://avatars.githubusercontent.com/u/10670690?v=4","gravatar_id":"","url":"https://api.github.com/users/pankaj-ag","html_url":"https://github.com/pankaj-ag","followers_url":"https://api.github.com/users/pankaj-ag/followers","following_url":"https://api.github.com/users/pankaj-ag/following{/other_user}","gists_url":"https://api.github.com/users/pankaj-ag/gists{/gist_id}","starred_url":"https://api.github.com/users/pankaj-ag/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pankaj-ag/subscriptions","organizations_url":"https://api.github.com/users/pankaj-ag/orgs","repos_url":"https://api.github.com/users/pankaj-ag/repos","events_url":"https://api.github.com/users/pankaj-ag/events{/privacy}","received_events_url":"https://api.github.com/users/pankaj-ag/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T07:00:03Z","updated_at":"2021-07-21T07:00:03Z","closed_at":null,"author_association":"MEMBER","active_lock_reason":null,"body":"**Describe the bug**\r\nWe have a typo in the conversion rate, it should be conversion rate diff. \r\n\r\n\r\n**Expected behavior**\r\n1. Rename label from Conversion rate to conversion rate diff\r\n2. Rename database column from conversion_rate to conversion_rate_diff\r\n3. Update the invoice modal column as well.\r\n\r\n\r\n**Screenshots**\r\n\"Screenshot\r\n\r\n\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:04Z","org":{"id":17040495,"login":"ColoredCow","gravatar_id":"","url":"https://api.github.com/orgs/ColoredCow","avatar_url":"https://avatars.githubusercontent.com/u/17040495?"}} +{"id":"17244792410","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561706447,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"1e8d3c37bdaa27f94049689bd806b189b0e2397b","before":"8bad30072ea48ae8c430eb696c742391758231c1","commits":[{"sha":"1e8d3c37bdaa27f94049689bd806b189b0e2397b","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/1e8d3c37bdaa27f94049689bd806b189b0e2397b"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792412","type":"PushEvent","actor":{"id":16056222,"login":"atoka93","display_login":"atoka93","gravatar_id":"","url":"https://api.github.com/users/atoka93","avatar_url":"https://avatars.githubusercontent.com/u/16056222?"},"repo":{"id":380180906,"name":"atoka93/flutter","url":"https://api.github.com/repos/atoka93/flutter"},"payload":{"push_id":7561706446,"size":15,"distinct_size":15,"ref":"refs/heads/override-mediaquery-reland","head":"7fee22c09a99c3f3803fd89a1d6f1e9e6bd9aeb6","before":"c2cca8579ae6e8b98ac7bae3aadbd41391567bc3","commits":[{"sha":"0874e421f7b2d1c5c5428eb10500051c105e7964","author":{"name":"engine-flutter-autoroll","email":"a47edd1763f49b05bae299d69dc4f7760a67278c@skia.org"},"message":"Roll Engine from 9edde7423fba to f0325b6a3d6d (3 revisions) (#86690)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/0874e421f7b2d1c5c5428eb10500051c105e7964"},{"sha":"8802e325a0c5a4890d91840d129273379de13b7b","author":{"name":"Kate Lovett","email":"81c5e0f3070abf35a3b1d7524fc9dd001c6df39a@google.com"},"message":"Revert \"Make LiveTestWidgetsFlutterBinding work with setSurfaceSize and live tests (#86449)\" (#86730)\n\nThis reverts commit cd78190b0caec337a7ed51506d93f9ba20cefa29.","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/8802e325a0c5a4890d91840d129273379de13b7b"},{"sha":"94f71f374f904de336d81fcdcce3a48cbf26639a","author":{"name":"Taha Tesser","email":"f79453d7be905cb01b36f143d132af6b11a4cdb3@gmail.com"},"message":"[flutter_tools] Refactor Android Studio Detection on Windows (support 4.x and 202x releases detection) (#86624)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/94f71f374f904de336d81fcdcce3a48cbf26639a"},{"sha":"2b7b4bdcab1acfb8745a1b69ed395c29e505a079","author":{"name":"Greg Spencer","email":"83f1cccf7fd0a8e921e6f6c34bd822b6ff976e6e@users.noreply.github.com"},"message":"Fix an exception being thrown when focus groups are empty. (#86734)\n\nWhen a focus traversal group has no entries, but traversal is requested, we were throwing an exception because we couldn't find the group marker's scope in the list of candidates. This fixes that, and adds a test.","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/2b7b4bdcab1acfb8745a1b69ed395c29e505a079"},{"sha":"0504fac7e2bc80a6c44ca7fc556033571860ef4c","author":{"name":"Emmanuel Garcia","email":"863f45a983d17b4f7833f956ca6e746b37fb6365@google.com"},"message":"Android e2e screenshot (#84472)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/0504fac7e2bc80a6c44ca7fc556033571860ef4c"},{"sha":"89c8616f0e417a0be0610948d92bb7b9231b82ba","author":{"name":"Taha Tesser","email":"f79453d7be905cb01b36f143d132af6b11a4cdb3@gmail.com"},"message":"fix `_owner` nullability in `routers.dart` (#84840)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/89c8616f0e417a0be0610948d92bb7b9231b82ba"},{"sha":"376584289cc3b3b5a40687fc579cb2530945e3c0","author":{"name":"Arthas","email":"d81fa96e3a020fbaadb9935e27b6386f6c0023d5@163.com"},"message":"Add onPlatformViewCreated to HtmlElementView (#84095)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/376584289cc3b3b5a40687fc579cb2530945e3c0"},{"sha":"841beff5204ebff30b297cf6d4342b6b6db1bb39","author":{"name":"Jeff Ward","email":"bc07e03c2c31887191e05b41bc2bca84fb2ce2d4@willowtreeapps.com"},"message":"Support Float32Lists in StandardMessageCodec (#72613) (#83318)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/841beff5204ebff30b297cf6d4342b6b6db1bb39"},{"sha":"3d47dd851d8ae7bf3dd53f0d08699416f920c7f6","author":{"name":"engine-flutter-autoroll","email":"a47edd1763f49b05bae299d69dc4f7760a67278c@skia.org"},"message":"Roll Engine from f0325b6a3d6d to 19f853d5a733 (14 revisions) (#86725)\n\n* dd4850440 add test for scaled playback of DL and SkPicture (flutter/engine#27503)\r\n\r\n* 791e2dc60 Roll Fuchsia Linux SDK from FGuPZEZLt... to 665qcW5C1... (flutter/engine#27561)\r\n\r\n* dd95642ba Roll Skia from fe49b2c6f41b to 946a4cb8acb7 (9 revisions) (flutter/engine#27564)\r\n\r\n* b47764518 Added a test filter for objc tests (flutter/engine#27566)\r\n\r\n* 6975e55cb Roll Fuchsia Mac SDK from 897eI2xwc... to rQOi2N8BM... (flutter/engine#27572)\r\n\r\n* a0f41b21e Roll Skia from 946a4cb8acb7 to 38a6e5aa1a49 (8 revisions) (flutter/engine#27573)\r\n\r\n* 024dbabee Roll Skia from 38a6e5aa1a49 to 2373b9ed9617 (1 revision) (flutter/engine#27574)\r\n\r\n* 1898563b3 Roll Skia from 2373b9ed9617 to 3f6e8d8864bb (2 revisions) (flutter/engine#27575)\r\n\r\n* ef9ac7539 Roll Skia from 3f6e8d8864bb to b5cd95b58fba (2 revisions) (flutter/engine#27576)\r\n\r\n* 1de3b8778 MacOS: Release backbuffer surface when idle (flutter/engine#27189)\r\n\r\n* a1ed81a37 Roll Skia from b5cd95b58fba to d37bb6ae7248 (1 revision) (flutter/engine#27578)\r\n\r\n* 52b976c3e Extract the prebuilt Dart SDK to a temp directory and then move it after the extraction completes (flutter/engine#27569)\r\n\r\n* 00da6c3d8 Roll Fuchsia Linux SDK from 665qcW5C1... to q6H_ZE5Bs... (flutter/engine#27581)\r\n\r\n* 19f853d5a Roll Dart SDK from b410651bd18e to f82b36d0b4f0 (5 revisions) (flutter/engine#27582)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/3d47dd851d8ae7bf3dd53f0d08699416f920c7f6"},{"sha":"4ab29de67fc98c81d6c0269b427bc60896e32d8a","author":{"name":"Casey Rogers","email":"7cddd715df2ca47c807585b5adbf325fd6b7342b@google.com"},"message":"Cancel DraggableScrollableSheet ballistic animation if a new activity begins (#86614)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/4ab29de67fc98c81d6c0269b427bc60896e32d8a"},{"sha":"d8cdaba085d8b2209bddd3d52a31768650cb6928","author":{"name":"nt4f04uNd","email":"1136cd697587f2df9b91a0f65f7c50518988f322@gmail.com"},"message":"init (#82348)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/d8cdaba085d8b2209bddd3d52a31768650cb6928"},{"sha":"f3d8562c93f19d3288d04875a3672f68e7f530d6","author":{"name":"engine-flutter-autoroll","email":"a47edd1763f49b05bae299d69dc4f7760a67278c@skia.org"},"message":"Roll Engine from 19f853d5a733 to 9b270621e479 (8 revisions) (#86743)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/f3d8562c93f19d3288d04875a3672f68e7f530d6"},{"sha":"b0e51db8d5f0d29603373e34929b8c501bba5354","author":{"name":"Michael Goderbauer","email":"522722b1c28e4103284e3443be8591136ff57aa2@google.com"},"message":"Fixes for upcoming changes to avoid_classes_with_only_static_members (#86744)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/b0e51db8d5f0d29603373e34929b8c501bba5354"},{"sha":"5acf7e98f16222610a4e6bfe1e36b08a90cf53a4","author":{"name":"Zachary Anderson","email":"5eb3a7c582f90a28bb9481c4774e095ba372fd77@users.noreply.github.com"},"message":"Manually close the tree for issue #86754 (#86755)","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/5acf7e98f16222610a4e6bfe1e36b08a90cf53a4"},{"sha":"7fee22c09a99c3f3803fd89a1d6f1e9e6bd9aeb6","author":{"name":"Attila Szabó","email":"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d@attilaszabo.com"},"message":"Merge branch 'master' into override-mediaquery-reland","distinct":true,"url":"https://api.github.com/repos/atoka93/flutter/commits/7fee22c09a99c3f3803fd89a1d6f1e9e6bd9aeb6"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792416","type":"PublicEvent","actor":{"id":49427541,"login":"op30132","display_login":"op30132","gravatar_id":"","url":"https://api.github.com/users/op30132","avatar_url":"https://avatars.githubusercontent.com/u/49427541?"},"repo":{"id":353660572,"name":"op30132/todo-service","url":"https://api.github.com/repos/op30132/todo-service"},"payload":{},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792423","type":"PullRequestEvent","actor":{"id":87011024,"login":"anhnsq-2596","display_login":"anhnsq-2596","gravatar_id":"","url":"https://api.github.com/users/anhnsq-2596","avatar_url":"https://avatars.githubusercontent.com/u/87011024?"},"repo":{"id":387410570,"name":"anhnsq-2596/project-demo","url":"https://api.github.com/repos/anhnsq-2596/project-demo"},"payload":{"action":"opened","number":3,"pull_request":{"url":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/3","id":694104889,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODg5","html_url":"https://github.com/anhnsq-2596/project-demo/pull/3","diff_url":"https://github.com/anhnsq-2596/project-demo/pull/3.diff","patch_url":"https://github.com/anhnsq-2596/project-demo/pull/3.patch","issue_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/3","number":3,"state":"open","locked":false,"title":"Feature: Login & logout","user":{"login":"anhnsq-2596","id":87011024,"node_id":"MDQ6VXNlcjg3MDExMDI0","avatar_url":"https://avatars.githubusercontent.com/u/87011024?v=4","gravatar_id":"","url":"https://api.github.com/users/anhnsq-2596","html_url":"https://github.com/anhnsq-2596","followers_url":"https://api.github.com/users/anhnsq-2596/followers","following_url":"https://api.github.com/users/anhnsq-2596/following{/other_user}","gists_url":"https://api.github.com/users/anhnsq-2596/gists{/gist_id}","starred_url":"https://api.github.com/users/anhnsq-2596/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anhnsq-2596/subscriptions","organizations_url":"https://api.github.com/users/anhnsq-2596/orgs","repos_url":"https://api.github.com/users/anhnsq-2596/repos","events_url":"https://api.github.com/users/anhnsq-2596/events{/privacy}","received_events_url":"https://api.github.com/users/anhnsq-2596/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-21T07:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/3/commits","review_comments_url":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/3/comments","review_comment_url":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/comments{/number}","comments_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/3/comments","statuses_url":"https://api.github.com/repos/anhnsq-2596/project-demo/statuses/6424da80c9e6db47c57f184f71ed510b5e5b6ee2","head":{"label":"anhnsq-2596:feature/login","ref":"feature/login","sha":"6424da80c9e6db47c57f184f71ed510b5e5b6ee2","user":{"login":"anhnsq-2596","id":87011024,"node_id":"MDQ6VXNlcjg3MDExMDI0","avatar_url":"https://avatars.githubusercontent.com/u/87011024?v=4","gravatar_id":"","url":"https://api.github.com/users/anhnsq-2596","html_url":"https://github.com/anhnsq-2596","followers_url":"https://api.github.com/users/anhnsq-2596/followers","following_url":"https://api.github.com/users/anhnsq-2596/following{/other_user}","gists_url":"https://api.github.com/users/anhnsq-2596/gists{/gist_id}","starred_url":"https://api.github.com/users/anhnsq-2596/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anhnsq-2596/subscriptions","organizations_url":"https://api.github.com/users/anhnsq-2596/orgs","repos_url":"https://api.github.com/users/anhnsq-2596/repos","events_url":"https://api.github.com/users/anhnsq-2596/events{/privacy}","received_events_url":"https://api.github.com/users/anhnsq-2596/received_events","type":"User","site_admin":false},"repo":{"id":387410570,"node_id":"MDEwOlJlcG9zaXRvcnkzODc0MTA1NzA=","name":"project-demo","full_name":"anhnsq-2596/project-demo","private":false,"owner":{"login":"anhnsq-2596","id":87011024,"node_id":"MDQ6VXNlcjg3MDExMDI0","avatar_url":"https://avatars.githubusercontent.com/u/87011024?v=4","gravatar_id":"","url":"https://api.github.com/users/anhnsq-2596","html_url":"https://github.com/anhnsq-2596","followers_url":"https://api.github.com/users/anhnsq-2596/followers","following_url":"https://api.github.com/users/anhnsq-2596/following{/other_user}","gists_url":"https://api.github.com/users/anhnsq-2596/gists{/gist_id}","starred_url":"https://api.github.com/users/anhnsq-2596/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anhnsq-2596/subscriptions","organizations_url":"https://api.github.com/users/anhnsq-2596/orgs","repos_url":"https://api.github.com/users/anhnsq-2596/repos","events_url":"https://api.github.com/users/anhnsq-2596/events{/privacy}","received_events_url":"https://api.github.com/users/anhnsq-2596/received_events","type":"User","site_admin":false},"html_url":"https://github.com/anhnsq-2596/project-demo","description":null,"fork":false,"url":"https://api.github.com/repos/anhnsq-2596/project-demo","forks_url":"https://api.github.com/repos/anhnsq-2596/project-demo/forks","keys_url":"https://api.github.com/repos/anhnsq-2596/project-demo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/anhnsq-2596/project-demo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/anhnsq-2596/project-demo/teams","hooks_url":"https://api.github.com/repos/anhnsq-2596/project-demo/hooks","issue_events_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/events{/number}","events_url":"https://api.github.com/repos/anhnsq-2596/project-demo/events","assignees_url":"https://api.github.com/repos/anhnsq-2596/project-demo/assignees{/user}","branches_url":"https://api.github.com/repos/anhnsq-2596/project-demo/branches{/branch}","tags_url":"https://api.github.com/repos/anhnsq-2596/project-demo/tags","blobs_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/refs{/sha}","trees_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/anhnsq-2596/project-demo/statuses/{sha}","languages_url":"https://api.github.com/repos/anhnsq-2596/project-demo/languages","stargazers_url":"https://api.github.com/repos/anhnsq-2596/project-demo/stargazers","contributors_url":"https://api.github.com/repos/anhnsq-2596/project-demo/contributors","subscribers_url":"https://api.github.com/repos/anhnsq-2596/project-demo/subscribers","subscription_url":"https://api.github.com/repos/anhnsq-2596/project-demo/subscription","commits_url":"https://api.github.com/repos/anhnsq-2596/project-demo/commits{/sha}","git_commits_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/commits{/sha}","comments_url":"https://api.github.com/repos/anhnsq-2596/project-demo/comments{/number}","issue_comment_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/comments{/number}","contents_url":"https://api.github.com/repos/anhnsq-2596/project-demo/contents/{+path}","compare_url":"https://api.github.com/repos/anhnsq-2596/project-demo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/anhnsq-2596/project-demo/merges","archive_url":"https://api.github.com/repos/anhnsq-2596/project-demo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/anhnsq-2596/project-demo/downloads","issues_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues{/number}","pulls_url":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls{/number}","milestones_url":"https://api.github.com/repos/anhnsq-2596/project-demo/milestones{/number}","notifications_url":"https://api.github.com/repos/anhnsq-2596/project-demo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/anhnsq-2596/project-demo/labels{/name}","releases_url":"https://api.github.com/repos/anhnsq-2596/project-demo/releases{/id}","deployments_url":"https://api.github.com/repos/anhnsq-2596/project-demo/deployments","created_at":"2021-07-19T09:32:40Z","updated_at":"2021-07-21T03:11:59Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/anhnsq-2596/project-demo.git","ssh_url":"git@github.com:anhnsq-2596/project-demo.git","clone_url":"https://github.com/anhnsq-2596/project-demo.git","svn_url":"https://github.com/anhnsq-2596/project-demo","homepage":null,"size":223,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"base":{"label":"anhnsq-2596:main","ref":"main","sha":"24947f2f69038e711fd132933d20ecbd18068366","user":{"login":"anhnsq-2596","id":87011024,"node_id":"MDQ6VXNlcjg3MDExMDI0","avatar_url":"https://avatars.githubusercontent.com/u/87011024?v=4","gravatar_id":"","url":"https://api.github.com/users/anhnsq-2596","html_url":"https://github.com/anhnsq-2596","followers_url":"https://api.github.com/users/anhnsq-2596/followers","following_url":"https://api.github.com/users/anhnsq-2596/following{/other_user}","gists_url":"https://api.github.com/users/anhnsq-2596/gists{/gist_id}","starred_url":"https://api.github.com/users/anhnsq-2596/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anhnsq-2596/subscriptions","organizations_url":"https://api.github.com/users/anhnsq-2596/orgs","repos_url":"https://api.github.com/users/anhnsq-2596/repos","events_url":"https://api.github.com/users/anhnsq-2596/events{/privacy}","received_events_url":"https://api.github.com/users/anhnsq-2596/received_events","type":"User","site_admin":false},"repo":{"id":387410570,"node_id":"MDEwOlJlcG9zaXRvcnkzODc0MTA1NzA=","name":"project-demo","full_name":"anhnsq-2596/project-demo","private":false,"owner":{"login":"anhnsq-2596","id":87011024,"node_id":"MDQ6VXNlcjg3MDExMDI0","avatar_url":"https://avatars.githubusercontent.com/u/87011024?v=4","gravatar_id":"","url":"https://api.github.com/users/anhnsq-2596","html_url":"https://github.com/anhnsq-2596","followers_url":"https://api.github.com/users/anhnsq-2596/followers","following_url":"https://api.github.com/users/anhnsq-2596/following{/other_user}","gists_url":"https://api.github.com/users/anhnsq-2596/gists{/gist_id}","starred_url":"https://api.github.com/users/anhnsq-2596/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anhnsq-2596/subscriptions","organizations_url":"https://api.github.com/users/anhnsq-2596/orgs","repos_url":"https://api.github.com/users/anhnsq-2596/repos","events_url":"https://api.github.com/users/anhnsq-2596/events{/privacy}","received_events_url":"https://api.github.com/users/anhnsq-2596/received_events","type":"User","site_admin":false},"html_url":"https://github.com/anhnsq-2596/project-demo","description":null,"fork":false,"url":"https://api.github.com/repos/anhnsq-2596/project-demo","forks_url":"https://api.github.com/repos/anhnsq-2596/project-demo/forks","keys_url":"https://api.github.com/repos/anhnsq-2596/project-demo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/anhnsq-2596/project-demo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/anhnsq-2596/project-demo/teams","hooks_url":"https://api.github.com/repos/anhnsq-2596/project-demo/hooks","issue_events_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/events{/number}","events_url":"https://api.github.com/repos/anhnsq-2596/project-demo/events","assignees_url":"https://api.github.com/repos/anhnsq-2596/project-demo/assignees{/user}","branches_url":"https://api.github.com/repos/anhnsq-2596/project-demo/branches{/branch}","tags_url":"https://api.github.com/repos/anhnsq-2596/project-demo/tags","blobs_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/refs{/sha}","trees_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/anhnsq-2596/project-demo/statuses/{sha}","languages_url":"https://api.github.com/repos/anhnsq-2596/project-demo/languages","stargazers_url":"https://api.github.com/repos/anhnsq-2596/project-demo/stargazers","contributors_url":"https://api.github.com/repos/anhnsq-2596/project-demo/contributors","subscribers_url":"https://api.github.com/repos/anhnsq-2596/project-demo/subscribers","subscription_url":"https://api.github.com/repos/anhnsq-2596/project-demo/subscription","commits_url":"https://api.github.com/repos/anhnsq-2596/project-demo/commits{/sha}","git_commits_url":"https://api.github.com/repos/anhnsq-2596/project-demo/git/commits{/sha}","comments_url":"https://api.github.com/repos/anhnsq-2596/project-demo/comments{/number}","issue_comment_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/comments{/number}","contents_url":"https://api.github.com/repos/anhnsq-2596/project-demo/contents/{+path}","compare_url":"https://api.github.com/repos/anhnsq-2596/project-demo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/anhnsq-2596/project-demo/merges","archive_url":"https://api.github.com/repos/anhnsq-2596/project-demo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/anhnsq-2596/project-demo/downloads","issues_url":"https://api.github.com/repos/anhnsq-2596/project-demo/issues{/number}","pulls_url":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls{/number}","milestones_url":"https://api.github.com/repos/anhnsq-2596/project-demo/milestones{/number}","notifications_url":"https://api.github.com/repos/anhnsq-2596/project-demo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/anhnsq-2596/project-demo/labels{/name}","releases_url":"https://api.github.com/repos/anhnsq-2596/project-demo/releases{/id}","deployments_url":"https://api.github.com/repos/anhnsq-2596/project-demo/deployments","created_at":"2021-07-19T09:32:40Z","updated_at":"2021-07-21T03:11:59Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/anhnsq-2596/project-demo.git","ssh_url":"git@github.com:anhnsq-2596/project-demo.git","clone_url":"https://github.com/anhnsq-2596/project-demo.git","svn_url":"https://github.com/anhnsq-2596/project-demo","homepage":null,"size":223,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/3"},"html":{"href":"https://github.com/anhnsq-2596/project-demo/pull/3"},"issue":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/3"},"comments":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/3/comments"},"review_comment":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/pulls/3/commits"},"statuses":{"href":"https://api.github.com/repos/anhnsq-2596/project-demo/statuses/6424da80c9e6db47c57f184f71ed510b5e5b6ee2"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":409,"deletions":34,"changed_files":32}},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792424","type":"PullRequestEvent","actor":{"id":15313630,"login":"michaelWuensch","display_login":"michaelWuensch","gravatar_id":"","url":"https://api.github.com/users/michaelWuensch","avatar_url":"https://avatars.githubusercontent.com/u/15313630?"},"repo":{"id":160931942,"name":"LN-Zap/zap-android","url":"https://api.github.com/repos/LN-Zap/zap-android"},"payload":{"action":"closed","number":342,"pull_request":{"url":"https://api.github.com/repos/LN-Zap/zap-android/pulls/342","id":694102096,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTAyMDk2","html_url":"https://github.com/LN-Zap/zap-android/pull/342","diff_url":"https://github.com/LN-Zap/zap-android/pull/342.diff","patch_url":"https://github.com/LN-Zap/zap-android/pull/342.patch","issue_url":"https://api.github.com/repos/LN-Zap/zap-android/issues/342","number":342,"state":"closed","locked":false,"title":"Fix wallet status dot","user":{"login":"michaelWuensch","id":15313630,"node_id":"MDQ6VXNlcjE1MzEzNjMw","avatar_url":"https://avatars.githubusercontent.com/u/15313630?v=4","gravatar_id":"","url":"https://api.github.com/users/michaelWuensch","html_url":"https://github.com/michaelWuensch","followers_url":"https://api.github.com/users/michaelWuensch/followers","following_url":"https://api.github.com/users/michaelWuensch/following{/other_user}","gists_url":"https://api.github.com/users/michaelWuensch/gists{/gist_id}","starred_url":"https://api.github.com/users/michaelWuensch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michaelWuensch/subscriptions","organizations_url":"https://api.github.com/users/michaelWuensch/orgs","repos_url":"https://api.github.com/users/michaelWuensch/repos","events_url":"https://api.github.com/users/michaelWuensch/events{/privacy}","received_events_url":"https://api.github.com/users/michaelWuensch/received_events","type":"User","site_admin":false},"body":"\r\n\r\n## Description\r\n\r\nMoved the update function of the dot from onCreate() to onResume() so it gets updated when minimizing and maximizing again.\r\nMade the dot also visible on long wallet names. (before it was moved out of screen)\r\n\r\n## Motivation and Context\r\n\r\n\r\nThe status dot helps to see if a working lnd connection was established. It was invisible on long wallet names, so there was no indication at all if after bringing the app to foreground again the connection was actually working.\r\nThis should improve UX.\r\n\r\n## How Has This Been Tested?\r\n\r\n\r\n\r\nMy S9 with very long tor name from umbrel\r\n\r\n## Types of changes\r\n\r\n- [x] Bug fix (non-breaking change which fixes an issue)\r\n- [ ] New feature (non-breaking change which adds functionality)\r\n- [ ] Breaking change (fix or feature that would cause existing functionality to change)\r\n\r\n## Checklist:\r\n\r\n\r\n- [x] My code follows the code style of this project.\r\n- [ ] My change requires a change to the documentation.\r\n- [ ] I have updated the documentation accordingly.\r\n- [x] I have read the [Contribution document](../docs/CONTRIBUTING.md).\r\n- [ ] I have added tests to cover my changes.\r\n- [x] All new and existing tests passed.","created_at":"2021-07-21T06:54:55Z","updated_at":"2021-07-21T07:00:04Z","closed_at":"2021-07-21T07:00:03Z","merged_at":"2021-07-21T07:00:03Z","merge_commit_sha":"06df1ad43954195ed198ed64b238c184ec80b711","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/LN-Zap/zap-android/pulls/342/commits","review_comments_url":"https://api.github.com/repos/LN-Zap/zap-android/pulls/342/comments","review_comment_url":"https://api.github.com/repos/LN-Zap/zap-android/pulls/comments{/number}","comments_url":"https://api.github.com/repos/LN-Zap/zap-android/issues/342/comments","statuses_url":"https://api.github.com/repos/LN-Zap/zap-android/statuses/74bcbe9b26aabc1d258495bec951759984ecc8f5","head":{"label":"michaelWuensch:FixWalletStatusDot","ref":"FixWalletStatusDot","sha":"74bcbe9b26aabc1d258495bec951759984ecc8f5","user":{"login":"michaelWuensch","id":15313630,"node_id":"MDQ6VXNlcjE1MzEzNjMw","avatar_url":"https://avatars.githubusercontent.com/u/15313630?v=4","gravatar_id":"","url":"https://api.github.com/users/michaelWuensch","html_url":"https://github.com/michaelWuensch","followers_url":"https://api.github.com/users/michaelWuensch/followers","following_url":"https://api.github.com/users/michaelWuensch/following{/other_user}","gists_url":"https://api.github.com/users/michaelWuensch/gists{/gist_id}","starred_url":"https://api.github.com/users/michaelWuensch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michaelWuensch/subscriptions","organizations_url":"https://api.github.com/users/michaelWuensch/orgs","repos_url":"https://api.github.com/users/michaelWuensch/repos","events_url":"https://api.github.com/users/michaelWuensch/events{/privacy}","received_events_url":"https://api.github.com/users/michaelWuensch/received_events","type":"User","site_admin":false},"repo":{"id":191994403,"node_id":"MDEwOlJlcG9zaXRvcnkxOTE5OTQ0MDM=","name":"zap-android","full_name":"michaelWuensch/zap-android","private":false,"owner":{"login":"michaelWuensch","id":15313630,"node_id":"MDQ6VXNlcjE1MzEzNjMw","avatar_url":"https://avatars.githubusercontent.com/u/15313630?v=4","gravatar_id":"","url":"https://api.github.com/users/michaelWuensch","html_url":"https://github.com/michaelWuensch","followers_url":"https://api.github.com/users/michaelWuensch/followers","following_url":"https://api.github.com/users/michaelWuensch/following{/other_user}","gists_url":"https://api.github.com/users/michaelWuensch/gists{/gist_id}","starred_url":"https://api.github.com/users/michaelWuensch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michaelWuensch/subscriptions","organizations_url":"https://api.github.com/users/michaelWuensch/orgs","repos_url":"https://api.github.com/users/michaelWuensch/repos","events_url":"https://api.github.com/users/michaelWuensch/events{/privacy}","received_events_url":"https://api.github.com/users/michaelWuensch/received_events","type":"User","site_admin":false},"html_url":"https://github.com/michaelWuensch/zap-android","description":"Native android lightning wallet focused on user experience and ease of use ⚡️","fork":true,"url":"https://api.github.com/repos/michaelWuensch/zap-android","forks_url":"https://api.github.com/repos/michaelWuensch/zap-android/forks","keys_url":"https://api.github.com/repos/michaelWuensch/zap-android/keys{/key_id}","collaborators_url":"https://api.github.com/repos/michaelWuensch/zap-android/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/michaelWuensch/zap-android/teams","hooks_url":"https://api.github.com/repos/michaelWuensch/zap-android/hooks","issue_events_url":"https://api.github.com/repos/michaelWuensch/zap-android/issues/events{/number}","events_url":"https://api.github.com/repos/michaelWuensch/zap-android/events","assignees_url":"https://api.github.com/repos/michaelWuensch/zap-android/assignees{/user}","branches_url":"https://api.github.com/repos/michaelWuensch/zap-android/branches{/branch}","tags_url":"https://api.github.com/repos/michaelWuensch/zap-android/tags","blobs_url":"https://api.github.com/repos/michaelWuensch/zap-android/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/michaelWuensch/zap-android/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/michaelWuensch/zap-android/git/refs{/sha}","trees_url":"https://api.github.com/repos/michaelWuensch/zap-android/git/trees{/sha}","statuses_url":"https://api.github.com/repos/michaelWuensch/zap-android/statuses/{sha}","languages_url":"https://api.github.com/repos/michaelWuensch/zap-android/languages","stargazers_url":"https://api.github.com/repos/michaelWuensch/zap-android/stargazers","contributors_url":"https://api.github.com/repos/michaelWuensch/zap-android/contributors","subscribers_url":"https://api.github.com/repos/michaelWuensch/zap-android/subscribers","subscription_url":"https://api.github.com/repos/michaelWuensch/zap-android/subscription","commits_url":"https://api.github.com/repos/michaelWuensch/zap-android/commits{/sha}","git_commits_url":"https://api.github.com/repos/michaelWuensch/zap-android/git/commits{/sha}","comments_url":"https://api.github.com/repos/michaelWuensch/zap-android/comments{/number}","issue_comment_url":"https://api.github.com/repos/michaelWuensch/zap-android/issues/comments{/number}","contents_url":"https://api.github.com/repos/michaelWuensch/zap-android/contents/{+path}","compare_url":"https://api.github.com/repos/michaelWuensch/zap-android/compare/{base}...{head}","merges_url":"https://api.github.com/repos/michaelWuensch/zap-android/merges","archive_url":"https://api.github.com/repos/michaelWuensch/zap-android/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/michaelWuensch/zap-android/downloads","issues_url":"https://api.github.com/repos/michaelWuensch/zap-android/issues{/number}","pulls_url":"https://api.github.com/repos/michaelWuensch/zap-android/pulls{/number}","milestones_url":"https://api.github.com/repos/michaelWuensch/zap-android/milestones{/number}","notifications_url":"https://api.github.com/repos/michaelWuensch/zap-android/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/michaelWuensch/zap-android/labels{/name}","releases_url":"https://api.github.com/repos/michaelWuensch/zap-android/releases{/id}","deployments_url":"https://api.github.com/repos/michaelWuensch/zap-android/deployments","created_at":"2019-06-14T19:16:02Z","updated_at":"2021-07-18T10:53:58Z","pushed_at":"2021-07-21T06:55:24Z","git_url":"git://github.com/michaelWuensch/zap-android.git","ssh_url":"git@github.com:michaelWuensch/zap-android.git","clone_url":"https://github.com/michaelWuensch/zap-android.git","svn_url":"https://github.com/michaelWuensch/zap-android","homepage":"","size":3959,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"LN-Zap:master","ref":"master","sha":"7e2d404c4905b60ae608c9c3756f4f214aff0301","user":{"login":"LN-Zap","id":30905490,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMwOTA1NDkw","avatar_url":"https://avatars.githubusercontent.com/u/30905490?v=4","gravatar_id":"","url":"https://api.github.com/users/LN-Zap","html_url":"https://github.com/LN-Zap","followers_url":"https://api.github.com/users/LN-Zap/followers","following_url":"https://api.github.com/users/LN-Zap/following{/other_user}","gists_url":"https://api.github.com/users/LN-Zap/gists{/gist_id}","starred_url":"https://api.github.com/users/LN-Zap/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LN-Zap/subscriptions","organizations_url":"https://api.github.com/users/LN-Zap/orgs","repos_url":"https://api.github.com/users/LN-Zap/repos","events_url":"https://api.github.com/users/LN-Zap/events{/privacy}","received_events_url":"https://api.github.com/users/LN-Zap/received_events","type":"Organization","site_admin":false},"repo":{"id":160931942,"node_id":"MDEwOlJlcG9zaXRvcnkxNjA5MzE5NDI=","name":"zap-android","full_name":"LN-Zap/zap-android","private":false,"owner":{"login":"LN-Zap","id":30905490,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMwOTA1NDkw","avatar_url":"https://avatars.githubusercontent.com/u/30905490?v=4","gravatar_id":"","url":"https://api.github.com/users/LN-Zap","html_url":"https://github.com/LN-Zap","followers_url":"https://api.github.com/users/LN-Zap/followers","following_url":"https://api.github.com/users/LN-Zap/following{/other_user}","gists_url":"https://api.github.com/users/LN-Zap/gists{/gist_id}","starred_url":"https://api.github.com/users/LN-Zap/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LN-Zap/subscriptions","organizations_url":"https://api.github.com/users/LN-Zap/orgs","repos_url":"https://api.github.com/users/LN-Zap/repos","events_url":"https://api.github.com/users/LN-Zap/events{/privacy}","received_events_url":"https://api.github.com/users/LN-Zap/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/LN-Zap/zap-android","description":"Native android lightning wallet focused on user experience and ease of use ⚡️","fork":false,"url":"https://api.github.com/repos/LN-Zap/zap-android","forks_url":"https://api.github.com/repos/LN-Zap/zap-android/forks","keys_url":"https://api.github.com/repos/LN-Zap/zap-android/keys{/key_id}","collaborators_url":"https://api.github.com/repos/LN-Zap/zap-android/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/LN-Zap/zap-android/teams","hooks_url":"https://api.github.com/repos/LN-Zap/zap-android/hooks","issue_events_url":"https://api.github.com/repos/LN-Zap/zap-android/issues/events{/number}","events_url":"https://api.github.com/repos/LN-Zap/zap-android/events","assignees_url":"https://api.github.com/repos/LN-Zap/zap-android/assignees{/user}","branches_url":"https://api.github.com/repos/LN-Zap/zap-android/branches{/branch}","tags_url":"https://api.github.com/repos/LN-Zap/zap-android/tags","blobs_url":"https://api.github.com/repos/LN-Zap/zap-android/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/LN-Zap/zap-android/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/LN-Zap/zap-android/git/refs{/sha}","trees_url":"https://api.github.com/repos/LN-Zap/zap-android/git/trees{/sha}","statuses_url":"https://api.github.com/repos/LN-Zap/zap-android/statuses/{sha}","languages_url":"https://api.github.com/repos/LN-Zap/zap-android/languages","stargazers_url":"https://api.github.com/repos/LN-Zap/zap-android/stargazers","contributors_url":"https://api.github.com/repos/LN-Zap/zap-android/contributors","subscribers_url":"https://api.github.com/repos/LN-Zap/zap-android/subscribers","subscription_url":"https://api.github.com/repos/LN-Zap/zap-android/subscription","commits_url":"https://api.github.com/repos/LN-Zap/zap-android/commits{/sha}","git_commits_url":"https://api.github.com/repos/LN-Zap/zap-android/git/commits{/sha}","comments_url":"https://api.github.com/repos/LN-Zap/zap-android/comments{/number}","issue_comment_url":"https://api.github.com/repos/LN-Zap/zap-android/issues/comments{/number}","contents_url":"https://api.github.com/repos/LN-Zap/zap-android/contents/{+path}","compare_url":"https://api.github.com/repos/LN-Zap/zap-android/compare/{base}...{head}","merges_url":"https://api.github.com/repos/LN-Zap/zap-android/merges","archive_url":"https://api.github.com/repos/LN-Zap/zap-android/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/LN-Zap/zap-android/downloads","issues_url":"https://api.github.com/repos/LN-Zap/zap-android/issues{/number}","pulls_url":"https://api.github.com/repos/LN-Zap/zap-android/pulls{/number}","milestones_url":"https://api.github.com/repos/LN-Zap/zap-android/milestones{/number}","notifications_url":"https://api.github.com/repos/LN-Zap/zap-android/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/LN-Zap/zap-android/labels{/name}","releases_url":"https://api.github.com/repos/LN-Zap/zap-android/releases{/id}","deployments_url":"https://api.github.com/repos/LN-Zap/zap-android/deployments","created_at":"2018-12-08T11:21:11Z","updated_at":"2021-07-18T10:52:50Z","pushed_at":"2021-07-21T07:00:03Z","git_url":"git://github.com/LN-Zap/zap-android.git","ssh_url":"git@github.com:LN-Zap/zap-android.git","clone_url":"https://github.com/LN-Zap/zap-android.git","svn_url":"https://github.com/LN-Zap/zap-android","homepage":"","size":3951,"stargazers_count":79,"watchers_count":79,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":33,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":23,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":33,"open_issues":23,"watchers":79,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/LN-Zap/zap-android/pulls/342"},"html":{"href":"https://github.com/LN-Zap/zap-android/pull/342"},"issue":{"href":"https://api.github.com/repos/LN-Zap/zap-android/issues/342"},"comments":{"href":"https://api.github.com/repos/LN-Zap/zap-android/issues/342/comments"},"review_comments":{"href":"https://api.github.com/repos/LN-Zap/zap-android/pulls/342/comments"},"review_comment":{"href":"https://api.github.com/repos/LN-Zap/zap-android/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/LN-Zap/zap-android/pulls/342/commits"},"statuses":{"href":"https://api.github.com/repos/LN-Zap/zap-android/statuses/74bcbe9b26aabc1d258495bec951759984ecc8f5"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"michaelWuensch","id":15313630,"node_id":"MDQ6VXNlcjE1MzEzNjMw","avatar_url":"https://avatars.githubusercontent.com/u/15313630?v=4","gravatar_id":"","url":"https://api.github.com/users/michaelWuensch","html_url":"https://github.com/michaelWuensch","followers_url":"https://api.github.com/users/michaelWuensch/followers","following_url":"https://api.github.com/users/michaelWuensch/following{/other_user}","gists_url":"https://api.github.com/users/michaelWuensch/gists{/gist_id}","starred_url":"https://api.github.com/users/michaelWuensch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michaelWuensch/subscriptions","organizations_url":"https://api.github.com/users/michaelWuensch/orgs","repos_url":"https://api.github.com/users/michaelWuensch/repos","events_url":"https://api.github.com/users/michaelWuensch/events{/privacy}","received_events_url":"https://api.github.com/users/michaelWuensch/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":21,"deletions":10,"changed_files":4}},"public":true,"created_at":"2021-07-21T07:00:04Z","org":{"id":30905490,"login":"LN-Zap","gravatar_id":"","url":"https://api.github.com/orgs/LN-Zap","avatar_url":"https://avatars.githubusercontent.com/u/30905490?"}} +{"id":"17244792427","type":"PushEvent","actor":{"id":75016136,"login":"CastformMadrid","display_login":"CastformMadrid","gravatar_id":"","url":"https://api.github.com/users/CastformMadrid","avatar_url":"https://avatars.githubusercontent.com/u/75016136?"},"repo":{"id":315383656,"name":"alexelgt/Castform-Madrid","url":"https://api.github.com/repos/alexelgt/Castform-Madrid"},"payload":{"push_id":7561706470,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"c18ce1429a65e78d4052a2f525048fc3d71c1012","before":"3d9daadb9745e005cd35fa866be64e59aa8bb67f","commits":[{"sha":"c18ce1429a65e78d4052a2f525048fc3d71c1012","author":{"name":"CastformMadrid","email":"4bf3034b7f6dbaf30fedab8adaf278c90d929f80@gmail.com"},"message":"21/7/2021 09:00","distinct":true,"url":"https://api.github.com/repos/alexelgt/Castform-Madrid/commits/c18ce1429a65e78d4052a2f525048fc3d71c1012"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792435","type":"PushEvent","actor":{"id":541490,"login":"morrah","display_login":"morrah","gravatar_id":"","url":"https://api.github.com/users/morrah","avatar_url":"https://avatars.githubusercontent.com/u/541490?"},"repo":{"id":224001345,"name":"morrah/oro-market","url":"https://api.github.com/repos/morrah/oro-market"},"payload":{"push_id":7561706466,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"dea50f42eb9399854a65ed60ffa85f781fa84fe1","before":"45534b305f87bacad5e0a99a21aca8bcb43e2d91","commits":[{"sha":"dea50f42eb9399854a65ed60ffa85f781fa84fe1","author":{"name":"morrah","email":"5b2d4826958c41aa3d7920ac238926453de1302d@users.noreply.github.com"},"message":"data update","distinct":true,"url":"https://api.github.com/repos/morrah/oro-market/commits/dea50f42eb9399854a65ed60ffa85f781fa84fe1"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792437","type":"PushEvent","actor":{"id":524596,"login":"Varriount","display_login":"Varriount","gravatar_id":"","url":"https://api.github.com/users/Varriount","avatar_url":"https://avatars.githubusercontent.com/u/524596?"},"repo":{"id":378265592,"name":"Varriount/Yoyo","url":"https://api.github.com/repos/Varriount/Yoyo"},"payload":{"push_id":7561706455,"size":0,"distinct_size":0,"ref":"refs/heads/snowflake","head":"2d9a38d8134ef2d1a267a5fe8e83ee714f832e08","before":"2d9a38d8134ef2d1a267a5fe8e83ee714f832e08","commits":[]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792443","type":"PushEvent","actor":{"id":32645537,"login":"Beziel","display_login":"Beziel","gravatar_id":"","url":"https://api.github.com/users/Beziel","avatar_url":"https://avatars.githubusercontent.com/u/32645537?"},"repo":{"id":388022780,"name":"Beziel/demo-gitflow","url":"https://api.github.com/repos/Beziel/demo-gitflow"},"payload":{"push_id":7561706463,"size":3,"distinct_size":1,"ref":"refs/heads/main","head":"6f6d75cd1d878d21f95fa8873c3c21088d4106fe","before":"18c286a19563567013176cc441b849d1835536b5","commits":[{"sha":"2795c9da9e64b098a686668608e43424ad691bd2","author":{"name":"jenkins","email":"d95b56ce41a2e1ac4cecdd398defd7414407cc08@test.com"},"message":"[maven-release-plugin] prepare release v1.0.0","distinct":false,"url":"https://api.github.com/repos/Beziel/demo-gitflow/commits/2795c9da9e64b098a686668608e43424ad691bd2"},{"sha":"2fa94f06b8eb3319ddc8bb7984a91d71da44529d","author":{"name":"jenkins","email":"d95b56ce41a2e1ac4cecdd398defd7414407cc08@test.com"},"message":"[maven-release-plugin] prepare for next development iteration","distinct":false,"url":"https://api.github.com/repos/Beziel/demo-gitflow/commits/2fa94f06b8eb3319ddc8bb7984a91d71da44529d"},{"sha":"6f6d75cd1d878d21f95fa8873c3c21088d4106fe","author":{"name":"Beziel","email":"509f91063c05dda398f8d14637739901cbfe86cc@users.noreply.github.com"},"message":"Merge pull request #1 from Beziel/release/1.0.0\n\nRelease/1.0.0","distinct":true,"url":"https://api.github.com/repos/Beziel/demo-gitflow/commits/6f6d75cd1d878d21f95fa8873c3c21088d4106fe"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792444","type":"PushEvent","actor":{"id":25794267,"login":"abhi1122","display_login":"abhi1122","gravatar_id":"","url":"https://api.github.com/users/abhi1122","avatar_url":"https://avatars.githubusercontent.com/u/25794267?"},"repo":{"id":231720120,"name":"abhi1122/react-test","url":"https://api.github.com/repos/abhi1122/react-test"},"payload":{"push_id":7561706474,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"31c7c535ec80c9283e2890aaa05b111550d1218e","before":"90402ab6857a68864515bc13814f5a5e7e9b355b","commits":[{"sha":"31c7c535ec80c9283e2890aaa05b111550d1218e","author":{"name":"abhi1122","email":"a0e3c6ad9773afc14a8b2586a4e93f1f865645b1@gmail.com"},"message":"Update Jenkinsfile","distinct":true,"url":"https://api.github.com/repos/abhi1122/react-test/commits/31c7c535ec80c9283e2890aaa05b111550d1218e"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792447","type":"PushEvent","actor":{"id":85440641,"login":"sun0225","display_login":"sun0225","gravatar_id":"","url":"https://api.github.com/users/sun0225","avatar_url":"https://avatars.githubusercontent.com/u/85440641?"},"repo":{"id":374349858,"name":"sun0225/fei","url":"https://api.github.com/repos/sun0225/fei"},"payload":{"push_id":7561706469,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"9096a3a775114690bd129f504914291ee3a0f0ff","before":"91c2c4f25b64e182d572304c141ba09a5157f784","commits":[{"sha":"9096a3a775114690bd129f504914291ee3a0f0ff","author":{"name":"sun0225","email":"3eb5f2ff8c6c14e9452b3144893767da3bb7dd0e@qq.com"},"message":"Add files via upload","distinct":true,"url":"https://api.github.com/repos/sun0225/fei/commits/9096a3a775114690bd129f504914291ee3a0f0ff"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792461","type":"ForkEvent","actor":{"id":11326721,"login":"zhaijf1992","display_login":"zhaijf1992","gravatar_id":"","url":"https://api.github.com/users/zhaijf1992","avatar_url":"https://avatars.githubusercontent.com/u/11326721?"},"repo":{"id":180340000,"name":"cookcodeblog/OneDayDevOps","url":"https://api.github.com/repos/cookcodeblog/OneDayDevOps"},"payload":{"forkee":{"id":388024737,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMjQ3Mzc=","name":"OneDayDevOps","full_name":"zhaijf1992/OneDayDevOps","private":false,"owner":{"login":"zhaijf1992","id":11326721,"node_id":"MDQ6VXNlcjExMzI2NzIx","avatar_url":"https://avatars.githubusercontent.com/u/11326721?v=4","gravatar_id":"","url":"https://api.github.com/users/zhaijf1992","html_url":"https://github.com/zhaijf1992","followers_url":"https://api.github.com/users/zhaijf1992/followers","following_url":"https://api.github.com/users/zhaijf1992/following{/other_user}","gists_url":"https://api.github.com/users/zhaijf1992/gists{/gist_id}","starred_url":"https://api.github.com/users/zhaijf1992/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zhaijf1992/subscriptions","organizations_url":"https://api.github.com/users/zhaijf1992/orgs","repos_url":"https://api.github.com/users/zhaijf1992/repos","events_url":"https://api.github.com/users/zhaijf1992/events{/privacy}","received_events_url":"https://api.github.com/users/zhaijf1992/received_events","type":"User","site_admin":false},"html_url":"https://github.com/zhaijf1992/OneDayDevOps","description":"Build a DevOps platform in one day using open source components","fork":true,"url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps","forks_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/forks","keys_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/teams","hooks_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/hooks","issue_events_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/issues/events{/number}","events_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/events","assignees_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/assignees{/user}","branches_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/branches{/branch}","tags_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/tags","blobs_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/git/refs{/sha}","trees_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/statuses/{sha}","languages_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/languages","stargazers_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/stargazers","contributors_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/contributors","subscribers_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/subscribers","subscription_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/subscription","commits_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/commits{/sha}","git_commits_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/git/commits{/sha}","comments_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/comments{/number}","issue_comment_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/issues/comments{/number}","contents_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/contents/{+path}","compare_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/merges","archive_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/downloads","issues_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/issues{/number}","pulls_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/pulls{/number}","milestones_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/milestones{/number}","notifications_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/labels{/name}","releases_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/releases{/id}","deployments_url":"https://api.github.com/repos/zhaijf1992/OneDayDevOps/deployments","created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-06T17:07:51Z","pushed_at":"2020-11-13T03:25:52Z","git_url":"git://github.com/zhaijf1992/OneDayDevOps.git","ssh_url":"git@github.com:zhaijf1992/OneDayDevOps.git","clone_url":"https://github.com/zhaijf1992/OneDayDevOps.git","svn_url":"https://github.com/zhaijf1992/OneDayDevOps","homepage":"https://blog.csdn.net/nklinsirui/article/details/89416151","size":79,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master","public":true}},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792465","type":"PushEvent","actor":{"id":57280724,"login":"112312","display_login":"112312","gravatar_id":"","url":"https://api.github.com/users/112312","avatar_url":"https://avatars.githubusercontent.com/u/57280724?"},"repo":{"id":222697611,"name":"112312/112312.github.io","url":"https://api.github.com/repos/112312/112312.github.io"},"payload":{"push_id":7561706476,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"9e2d6788b56e1ea3e7242f87a79b2037a930f02f","before":"6f1420fe5e2a3761e191f4ec377e4a863bcc0f6a","commits":[{"sha":"9e2d6788b56e1ea3e7242f87a79b2037a930f02f","author":{"name":"NDFS","email":"a75a20f6cc7e06672d35a787e265ec9a92575273@users.noreply.github.com"},"message":"Delete 1.png","distinct":true,"url":"https://api.github.com/repos/112312/112312.github.io/commits/9e2d6788b56e1ea3e7242f87a79b2037a930f02f"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792469","type":"WatchEvent","actor":{"id":6847747,"login":"duqb","display_login":"duqb","gravatar_id":"","url":"https://api.github.com/users/duqb","avatar_url":"https://avatars.githubusercontent.com/u/6847747?"},"repo":{"id":196664765,"name":"pwstrick/daily","url":"https://api.github.com/repos/pwstrick/daily"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792470","type":"PushEvent","actor":{"id":2738836,"login":"Mgamerz","display_login":"Mgamerz","gravatar_id":"","url":"https://api.github.com/users/Mgamerz","avatar_url":"https://avatars.githubusercontent.com/u/2738836?"},"repo":{"id":206195000,"name":"ME3Tweaks/ME3TweaksModManager","url":"https://api.github.com/repos/ME3Tweaks/ME3TweaksModManager"},"payload":{"push_id":7561706475,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"df3127756ddadf63d46c740616b9500cef2ec3b1","before":"8721baf7e39ce1399f933bb8b27d323e7afe880d","commits":[{"sha":"df3127756ddadf63d46c740616b9500cef2ec3b1","author":{"name":"ME3Tweaks-Github-Sync","email":"77da312b502727c949f6f457441753cc14a6e2a5@me3tweaks.com"},"message":"Synchronize Fallback Startup Manifest to GitHub","distinct":true,"url":"https://api.github.com/repos/ME3Tweaks/ME3TweaksModManager/commits/df3127756ddadf63d46c740616b9500cef2ec3b1"}]},"public":true,"created_at":"2021-07-21T07:00:04Z","org":{"id":49797801,"login":"ME3Tweaks","gravatar_id":"","url":"https://api.github.com/orgs/ME3Tweaks","avatar_url":"https://avatars.githubusercontent.com/u/49797801?"}} +{"id":"17244792471","type":"PushEvent","actor":{"id":35090897,"login":"geekyorion","display_login":"geekyorion","gravatar_id":"","url":"https://api.github.com/users/geekyorion","avatar_url":"https://avatars.githubusercontent.com/u/35090897?"},"repo":{"id":228024960,"name":"geekyorion/devSocialMedia","url":"https://api.github.com/repos/geekyorion/devSocialMedia"},"payload":{"push_id":7561706478,"size":1,"distinct_size":1,"ref":"refs/heads/gitDeploy","head":"ee7ccfb324ccb248f41ba25a8c94e1cc7968810f","before":"daa9132de5bb82fcf1c50cd8fc0fddb9ccf8efd0","commits":[{"sha":"ee7ccfb324ccb248f41ba25a8c94e1cc7968810f","author":{"name":"Shashank Sharma","email":"2582e3ba67800baed5efa2e724dcf8229e9cf50b@users.noreply.github.com"},"message":"Update gitDeploy.js.yml","distinct":true,"url":"https://api.github.com/repos/geekyorion/devSocialMedia/commits/ee7ccfb324ccb248f41ba25a8c94e1cc7968810f"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792472","type":"PushEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388024725,"name":"thatjohn01/781116602","url":"https://api.github.com/repos/thatjohn01/781116602"},"payload":{"push_id":7561706483,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"3f38aabeeda7a9c59a4240a36f7bd3632ce86b73","before":"ff90d91d59bf59426cc444c626dc81d72348987e","commits":[{"sha":"3f38aabeeda7a9c59a4240a36f7bd3632ce86b73","author":{"name":"thatjohn01","email":"72eb81e66410b3da65da4cca287ce0578825ce64@users.noreply.github.com"},"message":"change README.md","distinct":true,"url":"https://api.github.com/repos/thatjohn01/781116602/commits/3f38aabeeda7a9c59a4240a36f7bd3632ce86b73"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792482","type":"PullRequestEvent","actor":{"id":49731213,"login":"s-hadinger","display_login":"s-hadinger","gravatar_id":"","url":"https://api.github.com/users/s-hadinger","avatar_url":"https://avatars.githubusercontent.com/u/49731213?"},"repo":{"id":80286288,"name":"arendst/Tasmota","url":"https://api.github.com/repos/arendst/Tasmota"},"payload":{"action":"closed","number":12711,"pull_request":{"url":"https://api.github.com/repos/arendst/Tasmota/pulls/12711","id":693818589,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzODE4NTg5","html_url":"https://github.com/arendst/Tasmota/pull/12711","diff_url":"https://github.com/arendst/Tasmota/pull/12711.diff","patch_url":"https://github.com/arendst/Tasmota/pull/12711.patch","issue_url":"https://api.github.com/repos/arendst/Tasmota/issues/12711","number":12711,"state":"closed","locked":false,"title":"Berry add tasmota.publish_result","user":{"login":"s-hadinger","id":49731213,"node_id":"MDQ6VXNlcjQ5NzMxMjEz","avatar_url":"https://avatars.githubusercontent.com/u/49731213?v=4","gravatar_id":"","url":"https://api.github.com/users/s-hadinger","html_url":"https://github.com/s-hadinger","followers_url":"https://api.github.com/users/s-hadinger/followers","following_url":"https://api.github.com/users/s-hadinger/following{/other_user}","gists_url":"https://api.github.com/users/s-hadinger/gists{/gist_id}","starred_url":"https://api.github.com/users/s-hadinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/s-hadinger/subscriptions","organizations_url":"https://api.github.com/users/s-hadinger/orgs","repos_url":"https://api.github.com/users/s-hadinger/repos","events_url":"https://api.github.com/users/s-hadinger/events{/privacy}","received_events_url":"https://api.github.com/users/s-hadinger/received_events","type":"User","site_admin":false},"body":"## Description:\r\n\r\nAdd Berry `tasmota.publish_result(payload:string, subtopic:string)` to publish a result as JSON and trigger any potential rule.\r\n\r\n## Checklist:\r\n - [x] The pull request is done against the latest development branch\r\n - [x] Only relevant files were touched\r\n - [x] Only one feature/fix was added per PR and the code change compiles without warnings\r\n - [ ] The code change is tested and works with Tasmota core ESP8266 V.2.7.4.9\r\n - [x] The code change is tested and works with Tasmota core ESP32 V.1.0.7.2\r\n - [x] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla).\r\n\r\n_NOTE: The code change must pass CI tests. **Your PR cannot be merged unless tests pass**_\r\n","created_at":"2021-07-20T20:32:32Z","updated_at":"2021-07-21T07:00:04Z","closed_at":"2021-07-21T07:00:04Z","merged_at":"2021-07-21T07:00:04Z","merge_commit_sha":"4fb8f679d73feb333bbff6593e300101ac5d1baa","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/arendst/Tasmota/pulls/12711/commits","review_comments_url":"https://api.github.com/repos/arendst/Tasmota/pulls/12711/comments","review_comment_url":"https://api.github.com/repos/arendst/Tasmota/pulls/comments{/number}","comments_url":"https://api.github.com/repos/arendst/Tasmota/issues/12711/comments","statuses_url":"https://api.github.com/repos/arendst/Tasmota/statuses/0539e8ec32d8f202c89af3e5731db11807970295","head":{"label":"s-hadinger:berry_publish_result","ref":"berry_publish_result","sha":"0539e8ec32d8f202c89af3e5731db11807970295","user":{"login":"s-hadinger","id":49731213,"node_id":"MDQ6VXNlcjQ5NzMxMjEz","avatar_url":"https://avatars.githubusercontent.com/u/49731213?v=4","gravatar_id":"","url":"https://api.github.com/users/s-hadinger","html_url":"https://github.com/s-hadinger","followers_url":"https://api.github.com/users/s-hadinger/followers","following_url":"https://api.github.com/users/s-hadinger/following{/other_user}","gists_url":"https://api.github.com/users/s-hadinger/gists{/gist_id}","starred_url":"https://api.github.com/users/s-hadinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/s-hadinger/subscriptions","organizations_url":"https://api.github.com/users/s-hadinger/orgs","repos_url":"https://api.github.com/users/s-hadinger/repos","events_url":"https://api.github.com/users/s-hadinger/events{/privacy}","received_events_url":"https://api.github.com/users/s-hadinger/received_events","type":"User","site_admin":false},"repo":{"id":181951063,"node_id":"MDEwOlJlcG9zaXRvcnkxODE5NTEwNjM=","name":"Tasmota","full_name":"s-hadinger/Tasmota","private":false,"owner":{"login":"s-hadinger","id":49731213,"node_id":"MDQ6VXNlcjQ5NzMxMjEz","avatar_url":"https://avatars.githubusercontent.com/u/49731213?v=4","gravatar_id":"","url":"https://api.github.com/users/s-hadinger","html_url":"https://github.com/s-hadinger","followers_url":"https://api.github.com/users/s-hadinger/followers","following_url":"https://api.github.com/users/s-hadinger/following{/other_user}","gists_url":"https://api.github.com/users/s-hadinger/gists{/gist_id}","starred_url":"https://api.github.com/users/s-hadinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/s-hadinger/subscriptions","organizations_url":"https://api.github.com/users/s-hadinger/orgs","repos_url":"https://api.github.com/users/s-hadinger/repos","events_url":"https://api.github.com/users/s-hadinger/events{/privacy}","received_events_url":"https://api.github.com/users/s-hadinger/received_events","type":"User","site_admin":false},"html_url":"https://github.com/s-hadinger/Tasmota","description":"Provide ESP8266 based itead Sonoff with Web, MQTT and OTA firmware using Arduino IDE or PlatformIO","fork":true,"url":"https://api.github.com/repos/s-hadinger/Tasmota","forks_url":"https://api.github.com/repos/s-hadinger/Tasmota/forks","keys_url":"https://api.github.com/repos/s-hadinger/Tasmota/keys{/key_id}","collaborators_url":"https://api.github.com/repos/s-hadinger/Tasmota/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/s-hadinger/Tasmota/teams","hooks_url":"https://api.github.com/repos/s-hadinger/Tasmota/hooks","issue_events_url":"https://api.github.com/repos/s-hadinger/Tasmota/issues/events{/number}","events_url":"https://api.github.com/repos/s-hadinger/Tasmota/events","assignees_url":"https://api.github.com/repos/s-hadinger/Tasmota/assignees{/user}","branches_url":"https://api.github.com/repos/s-hadinger/Tasmota/branches{/branch}","tags_url":"https://api.github.com/repos/s-hadinger/Tasmota/tags","blobs_url":"https://api.github.com/repos/s-hadinger/Tasmota/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/s-hadinger/Tasmota/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/s-hadinger/Tasmota/git/refs{/sha}","trees_url":"https://api.github.com/repos/s-hadinger/Tasmota/git/trees{/sha}","statuses_url":"https://api.github.com/repos/s-hadinger/Tasmota/statuses/{sha}","languages_url":"https://api.github.com/repos/s-hadinger/Tasmota/languages","stargazers_url":"https://api.github.com/repos/s-hadinger/Tasmota/stargazers","contributors_url":"https://api.github.com/repos/s-hadinger/Tasmota/contributors","subscribers_url":"https://api.github.com/repos/s-hadinger/Tasmota/subscribers","subscription_url":"https://api.github.com/repos/s-hadinger/Tasmota/subscription","commits_url":"https://api.github.com/repos/s-hadinger/Tasmota/commits{/sha}","git_commits_url":"https://api.github.com/repos/s-hadinger/Tasmota/git/commits{/sha}","comments_url":"https://api.github.com/repos/s-hadinger/Tasmota/comments{/number}","issue_comment_url":"https://api.github.com/repos/s-hadinger/Tasmota/issues/comments{/number}","contents_url":"https://api.github.com/repos/s-hadinger/Tasmota/contents/{+path}","compare_url":"https://api.github.com/repos/s-hadinger/Tasmota/compare/{base}...{head}","merges_url":"https://api.github.com/repos/s-hadinger/Tasmota/merges","archive_url":"https://api.github.com/repos/s-hadinger/Tasmota/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/s-hadinger/Tasmota/downloads","issues_url":"https://api.github.com/repos/s-hadinger/Tasmota/issues{/number}","pulls_url":"https://api.github.com/repos/s-hadinger/Tasmota/pulls{/number}","milestones_url":"https://api.github.com/repos/s-hadinger/Tasmota/milestones{/number}","notifications_url":"https://api.github.com/repos/s-hadinger/Tasmota/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/s-hadinger/Tasmota/labels{/name}","releases_url":"https://api.github.com/repos/s-hadinger/Tasmota/releases{/id}","deployments_url":"https://api.github.com/repos/s-hadinger/Tasmota/deployments","created_at":"2019-04-17T18:56:10Z","updated_at":"2021-07-20T19:36:40Z","pushed_at":"2021-07-20T20:31:28Z","git_url":"git://github.com/s-hadinger/Tasmota.git","ssh_url":"git@github.com:s-hadinger/Tasmota.git","clone_url":"https://github.com/s-hadinger/Tasmota.git","svn_url":"https://github.com/s-hadinger/Tasmota","homepage":"","size":157171,"stargazers_count":1,"watchers_count":1,"language":"C","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":0,"open_issues":0,"watchers":1,"default_branch":"development"}},"base":{"label":"arendst:development","ref":"development","sha":"acd9ea1661ceda475b90919d7892991ae00673df","user":{"login":"arendst","id":11044339,"node_id":"MDQ6VXNlcjExMDQ0MzM5","avatar_url":"https://avatars.githubusercontent.com/u/11044339?v=4","gravatar_id":"","url":"https://api.github.com/users/arendst","html_url":"https://github.com/arendst","followers_url":"https://api.github.com/users/arendst/followers","following_url":"https://api.github.com/users/arendst/following{/other_user}","gists_url":"https://api.github.com/users/arendst/gists{/gist_id}","starred_url":"https://api.github.com/users/arendst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arendst/subscriptions","organizations_url":"https://api.github.com/users/arendst/orgs","repos_url":"https://api.github.com/users/arendst/repos","events_url":"https://api.github.com/users/arendst/events{/privacy}","received_events_url":"https://api.github.com/users/arendst/received_events","type":"User","site_admin":false},"repo":{"id":80286288,"node_id":"MDEwOlJlcG9zaXRvcnk4MDI4NjI4OA==","name":"Tasmota","full_name":"arendst/Tasmota","private":false,"owner":{"login":"arendst","id":11044339,"node_id":"MDQ6VXNlcjExMDQ0MzM5","avatar_url":"https://avatars.githubusercontent.com/u/11044339?v=4","gravatar_id":"","url":"https://api.github.com/users/arendst","html_url":"https://github.com/arendst","followers_url":"https://api.github.com/users/arendst/followers","following_url":"https://api.github.com/users/arendst/following{/other_user}","gists_url":"https://api.github.com/users/arendst/gists{/gist_id}","starred_url":"https://api.github.com/users/arendst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arendst/subscriptions","organizations_url":"https://api.github.com/users/arendst/orgs","repos_url":"https://api.github.com/users/arendst/repos","events_url":"https://api.github.com/users/arendst/events{/privacy}","received_events_url":"https://api.github.com/users/arendst/received_events","type":"User","site_admin":false},"html_url":"https://github.com/arendst/Tasmota","description":"Alternative firmware for ESP8266 with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at","fork":false,"url":"https://api.github.com/repos/arendst/Tasmota","forks_url":"https://api.github.com/repos/arendst/Tasmota/forks","keys_url":"https://api.github.com/repos/arendst/Tasmota/keys{/key_id}","collaborators_url":"https://api.github.com/repos/arendst/Tasmota/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/arendst/Tasmota/teams","hooks_url":"https://api.github.com/repos/arendst/Tasmota/hooks","issue_events_url":"https://api.github.com/repos/arendst/Tasmota/issues/events{/number}","events_url":"https://api.github.com/repos/arendst/Tasmota/events","assignees_url":"https://api.github.com/repos/arendst/Tasmota/assignees{/user}","branches_url":"https://api.github.com/repos/arendst/Tasmota/branches{/branch}","tags_url":"https://api.github.com/repos/arendst/Tasmota/tags","blobs_url":"https://api.github.com/repos/arendst/Tasmota/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/arendst/Tasmota/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/arendst/Tasmota/git/refs{/sha}","trees_url":"https://api.github.com/repos/arendst/Tasmota/git/trees{/sha}","statuses_url":"https://api.github.com/repos/arendst/Tasmota/statuses/{sha}","languages_url":"https://api.github.com/repos/arendst/Tasmota/languages","stargazers_url":"https://api.github.com/repos/arendst/Tasmota/stargazers","contributors_url":"https://api.github.com/repos/arendst/Tasmota/contributors","subscribers_url":"https://api.github.com/repos/arendst/Tasmota/subscribers","subscription_url":"https://api.github.com/repos/arendst/Tasmota/subscription","commits_url":"https://api.github.com/repos/arendst/Tasmota/commits{/sha}","git_commits_url":"https://api.github.com/repos/arendst/Tasmota/git/commits{/sha}","comments_url":"https://api.github.com/repos/arendst/Tasmota/comments{/number}","issue_comment_url":"https://api.github.com/repos/arendst/Tasmota/issues/comments{/number}","contents_url":"https://api.github.com/repos/arendst/Tasmota/contents/{+path}","compare_url":"https://api.github.com/repos/arendst/Tasmota/compare/{base}...{head}","merges_url":"https://api.github.com/repos/arendst/Tasmota/merges","archive_url":"https://api.github.com/repos/arendst/Tasmota/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/arendst/Tasmota/downloads","issues_url":"https://api.github.com/repos/arendst/Tasmota/issues{/number}","pulls_url":"https://api.github.com/repos/arendst/Tasmota/pulls{/number}","milestones_url":"https://api.github.com/repos/arendst/Tasmota/milestones{/number}","notifications_url":"https://api.github.com/repos/arendst/Tasmota/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/arendst/Tasmota/labels{/name}","releases_url":"https://api.github.com/repos/arendst/Tasmota/releases{/id}","deployments_url":"https://api.github.com/repos/arendst/Tasmota/deployments","created_at":"2017-01-28T13:37:08Z","updated_at":"2021-07-21T05:14:23Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/arendst/Tasmota.git","ssh_url":"git@github.com:arendst/Tasmota.git","clone_url":"https://github.com/arendst/Tasmota.git","svn_url":"https://github.com/arendst/Tasmota","homepage":"https://tasmota.github.io/docs","size":1480255,"stargazers_count":15528,"watchers_count":15528,"language":"C","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":3434,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":17,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":3434,"open_issues":17,"watchers":15528,"default_branch":"development"}},"_links":{"self":{"href":"https://api.github.com/repos/arendst/Tasmota/pulls/12711"},"html":{"href":"https://github.com/arendst/Tasmota/pull/12711"},"issue":{"href":"https://api.github.com/repos/arendst/Tasmota/issues/12711"},"comments":{"href":"https://api.github.com/repos/arendst/Tasmota/issues/12711/comments"},"review_comments":{"href":"https://api.github.com/repos/arendst/Tasmota/pulls/12711/comments"},"review_comment":{"href":"https://api.github.com/repos/arendst/Tasmota/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/arendst/Tasmota/pulls/12711/commits"},"statuses":{"href":"https://api.github.com/repos/arendst/Tasmota/statuses/0539e8ec32d8f202c89af3e5731db11807970295"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"s-hadinger","id":49731213,"node_id":"MDQ6VXNlcjQ5NzMxMjEz","avatar_url":"https://avatars.githubusercontent.com/u/49731213?v=4","gravatar_id":"","url":"https://api.github.com/users/s-hadinger","html_url":"https://github.com/s-hadinger","followers_url":"https://api.github.com/users/s-hadinger/followers","following_url":"https://api.github.com/users/s-hadinger/following{/other_user}","gists_url":"https://api.github.com/users/s-hadinger/gists{/gist_id}","starred_url":"https://api.github.com/users/s-hadinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/s-hadinger/subscriptions","organizations_url":"https://api.github.com/users/s-hadinger/orgs","repos_url":"https://api.github.com/users/s-hadinger/repos","events_url":"https://api.github.com/users/s-hadinger/events{/privacy}","received_events_url":"https://api.github.com/users/s-hadinger/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1591,"deletions":1568,"changed_files":5}},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792488","type":"PullRequestEvent","actor":{"id":84075616,"login":"liguowen2001","display_login":"liguowen2001","gravatar_id":"","url":"https://api.github.com/users/liguowen2001","avatar_url":"https://avatars.githubusercontent.com/u/84075616?"},"repo":{"id":385091185,"name":"mengyunzhi/MicroCourse2021","url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021"},"payload":{"action":"opened","number":213,"pull_request":{"url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/213","id":694104892,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODky","html_url":"https://github.com/mengyunzhi/MicroCourse2021/pull/213","diff_url":"https://github.com/mengyunzhi/MicroCourse2021/pull/213.diff","patch_url":"https://github.com/mengyunzhi/MicroCourse2021/pull/213.patch","issue_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/213","number":213,"state":"open","locked":false,"title":"成绩导入bug修复212","user":{"login":"liguowen2001","id":84075616,"node_id":"MDQ6VXNlcjg0MDc1NjE2","avatar_url":"https://avatars.githubusercontent.com/u/84075616?v=4","gravatar_id":"","url":"https://api.github.com/users/liguowen2001","html_url":"https://github.com/liguowen2001","followers_url":"https://api.github.com/users/liguowen2001/followers","following_url":"https://api.github.com/users/liguowen2001/following{/other_user}","gists_url":"https://api.github.com/users/liguowen2001/gists{/gist_id}","starred_url":"https://api.github.com/users/liguowen2001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/liguowen2001/subscriptions","organizations_url":"https://api.github.com/users/liguowen2001/orgs","repos_url":"https://api.github.com/users/liguowen2001/repos","events_url":"https://api.github.com/users/liguowen2001/events{/privacy}","received_events_url":"https://api.github.com/users/liguowen2001/received_events","type":"User","site_admin":false},"body":"close #212 ","created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-21T07:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/213/commits","review_comments_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/213/comments","review_comment_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/213/comments","statuses_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/statuses/aa35cf16aa2a8f229edd21db445e62e576b0680b","head":{"label":"mengyunzhi:212","ref":"212","sha":"aa35cf16aa2a8f229edd21db445e62e576b0680b","user":{"login":"mengyunzhi","id":25142052,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI1MTQyMDUy","avatar_url":"https://avatars.githubusercontent.com/u/25142052?v=4","gravatar_id":"","url":"https://api.github.com/users/mengyunzhi","html_url":"https://github.com/mengyunzhi","followers_url":"https://api.github.com/users/mengyunzhi/followers","following_url":"https://api.github.com/users/mengyunzhi/following{/other_user}","gists_url":"https://api.github.com/users/mengyunzhi/gists{/gist_id}","starred_url":"https://api.github.com/users/mengyunzhi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mengyunzhi/subscriptions","organizations_url":"https://api.github.com/users/mengyunzhi/orgs","repos_url":"https://api.github.com/users/mengyunzhi/repos","events_url":"https://api.github.com/users/mengyunzhi/events{/privacy}","received_events_url":"https://api.github.com/users/mengyunzhi/received_events","type":"Organization","site_admin":false},"repo":{"id":385091185,"node_id":"MDEwOlJlcG9zaXRvcnkzODUwOTExODU=","name":"MicroCourse2021","full_name":"mengyunzhi/MicroCourse2021","private":false,"owner":{"login":"mengyunzhi","id":25142052,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI1MTQyMDUy","avatar_url":"https://avatars.githubusercontent.com/u/25142052?v=4","gravatar_id":"","url":"https://api.github.com/users/mengyunzhi","html_url":"https://github.com/mengyunzhi","followers_url":"https://api.github.com/users/mengyunzhi/followers","following_url":"https://api.github.com/users/mengyunzhi/following{/other_user}","gists_url":"https://api.github.com/users/mengyunzhi/gists{/gist_id}","starred_url":"https://api.github.com/users/mengyunzhi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mengyunzhi/subscriptions","organizations_url":"https://api.github.com/users/mengyunzhi/orgs","repos_url":"https://api.github.com/users/mengyunzhi/repos","events_url":"https://api.github.com/users/mengyunzhi/events{/privacy}","received_events_url":"https://api.github.com/users/mengyunzhi/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/mengyunzhi/MicroCourse2021","description":null,"fork":false,"url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021","forks_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/forks","keys_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/teams","hooks_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/hooks","issue_events_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/events{/number}","events_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/events","assignees_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/assignees{/user}","branches_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/branches{/branch}","tags_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/tags","blobs_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/refs{/sha}","trees_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/statuses/{sha}","languages_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/languages","stargazers_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/stargazers","contributors_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/contributors","subscribers_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/subscribers","subscription_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/subscription","commits_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/commits{/sha}","git_commits_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/commits{/sha}","comments_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/comments{/number}","issue_comment_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/comments{/number}","contents_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/contents/{+path}","compare_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/merges","archive_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/downloads","issues_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues{/number}","pulls_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls{/number}","milestones_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/milestones{/number}","notifications_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/labels{/name}","releases_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/releases{/id}","deployments_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/deployments","created_at":"2021-07-12T01:27:56Z","updated_at":"2021-07-21T03:37:30Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/mengyunzhi/MicroCourse2021.git","ssh_url":"git@github.com:mengyunzhi/MicroCourse2021.git","clone_url":"https://github.com/mengyunzhi/MicroCourse2021.git","svn_url":"https://github.com/mengyunzhi/MicroCourse2021","homepage":null,"size":1721,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":null,"forks":0,"open_issues":3,"watchers":0,"default_branch":"main"}},"base":{"label":"mengyunzhi:main","ref":"main","sha":"efc72ddde5289147a8b52130cb6a08517059727a","user":{"login":"mengyunzhi","id":25142052,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI1MTQyMDUy","avatar_url":"https://avatars.githubusercontent.com/u/25142052?v=4","gravatar_id":"","url":"https://api.github.com/users/mengyunzhi","html_url":"https://github.com/mengyunzhi","followers_url":"https://api.github.com/users/mengyunzhi/followers","following_url":"https://api.github.com/users/mengyunzhi/following{/other_user}","gists_url":"https://api.github.com/users/mengyunzhi/gists{/gist_id}","starred_url":"https://api.github.com/users/mengyunzhi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mengyunzhi/subscriptions","organizations_url":"https://api.github.com/users/mengyunzhi/orgs","repos_url":"https://api.github.com/users/mengyunzhi/repos","events_url":"https://api.github.com/users/mengyunzhi/events{/privacy}","received_events_url":"https://api.github.com/users/mengyunzhi/received_events","type":"Organization","site_admin":false},"repo":{"id":385091185,"node_id":"MDEwOlJlcG9zaXRvcnkzODUwOTExODU=","name":"MicroCourse2021","full_name":"mengyunzhi/MicroCourse2021","private":false,"owner":{"login":"mengyunzhi","id":25142052,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI1MTQyMDUy","avatar_url":"https://avatars.githubusercontent.com/u/25142052?v=4","gravatar_id":"","url":"https://api.github.com/users/mengyunzhi","html_url":"https://github.com/mengyunzhi","followers_url":"https://api.github.com/users/mengyunzhi/followers","following_url":"https://api.github.com/users/mengyunzhi/following{/other_user}","gists_url":"https://api.github.com/users/mengyunzhi/gists{/gist_id}","starred_url":"https://api.github.com/users/mengyunzhi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mengyunzhi/subscriptions","organizations_url":"https://api.github.com/users/mengyunzhi/orgs","repos_url":"https://api.github.com/users/mengyunzhi/repos","events_url":"https://api.github.com/users/mengyunzhi/events{/privacy}","received_events_url":"https://api.github.com/users/mengyunzhi/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/mengyunzhi/MicroCourse2021","description":null,"fork":false,"url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021","forks_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/forks","keys_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/teams","hooks_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/hooks","issue_events_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/events{/number}","events_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/events","assignees_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/assignees{/user}","branches_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/branches{/branch}","tags_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/tags","blobs_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/refs{/sha}","trees_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/statuses/{sha}","languages_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/languages","stargazers_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/stargazers","contributors_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/contributors","subscribers_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/subscribers","subscription_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/subscription","commits_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/commits{/sha}","git_commits_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/git/commits{/sha}","comments_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/comments{/number}","issue_comment_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/comments{/number}","contents_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/contents/{+path}","compare_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/merges","archive_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/downloads","issues_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues{/number}","pulls_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls{/number}","milestones_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/milestones{/number}","notifications_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/labels{/name}","releases_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/releases{/id}","deployments_url":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/deployments","created_at":"2021-07-12T01:27:56Z","updated_at":"2021-07-21T03:37:30Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/mengyunzhi/MicroCourse2021.git","ssh_url":"git@github.com:mengyunzhi/MicroCourse2021.git","clone_url":"https://github.com/mengyunzhi/MicroCourse2021.git","svn_url":"https://github.com/mengyunzhi/MicroCourse2021","homepage":null,"size":1721,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":null,"forks":0,"open_issues":3,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/213"},"html":{"href":"https://github.com/mengyunzhi/MicroCourse2021/pull/213"},"issue":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/213"},"comments":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/issues/213/comments"},"review_comments":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/213/comments"},"review_comment":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/pulls/213/commits"},"statuses":{"href":"https://api.github.com/repos/mengyunzhi/MicroCourse2021/statuses/aa35cf16aa2a8f229edd21db445e62e576b0680b"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":5,"deletions":8,"changed_files":1}},"public":true,"created_at":"2021-07-21T07:00:04Z","org":{"id":25142052,"login":"mengyunzhi","gravatar_id":"","url":"https://api.github.com/orgs/mengyunzhi","avatar_url":"https://avatars.githubusercontent.com/u/25142052?"}} +{"id":"17244792494","type":"WatchEvent","actor":{"id":39883467,"login":"Drunos","display_login":"Drunos","gravatar_id":"","url":"https://api.github.com/users/Drunos","avatar_url":"https://avatars.githubusercontent.com/u/39883467?"},"repo":{"id":94253765,"name":"ctubio/Krypto-trading-bot","url":"https://api.github.com/repos/ctubio/Krypto-trading-bot"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792497","type":"IssueCommentEvent","actor":{"id":4639210,"login":"thc202","display_login":"thc202","gravatar_id":"","url":"https://api.github.com/users/thc202","avatar_url":"https://avatars.githubusercontent.com/u/4639210?"},"repo":{"id":35210580,"name":"zaproxy/zap-extensions","url":"https://api.github.com/repos/zaproxy/zap-extensions"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/zaproxy/zap-extensions/issues/2947","repository_url":"https://api.github.com/repos/zaproxy/zap-extensions","labels_url":"https://api.github.com/repos/zaproxy/zap-extensions/issues/2947/labels{/name}","comments_url":"https://api.github.com/repos/zaproxy/zap-extensions/issues/2947/comments","events_url":"https://api.github.com/repos/zaproxy/zap-extensions/issues/2947/events","html_url":"https://github.com/zaproxy/zap-extensions/pull/2947","id":897668974,"node_id":"MDExOlB1bGxSZXF1ZXN0NjQ5NjQ2MDM0","number":2947,"title":"Fix for ZAP Source Code Disclosure-Java-false positive Issue 6595","user":{"login":"kosap","id":3292761,"node_id":"MDQ6VXNlcjMyOTI3NjE=","avatar_url":"https://avatars.githubusercontent.com/u/3292761?v=4","gravatar_id":"","url":"https://api.github.com/users/kosap","html_url":"https://github.com/kosap","followers_url":"https://api.github.com/users/kosap/followers","following_url":"https://api.github.com/users/kosap/following{/other_user}","gists_url":"https://api.github.com/users/kosap/gists{/gist_id}","starred_url":"https://api.github.com/users/kosap/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kosap/subscriptions","organizations_url":"https://api.github.com/users/kosap/orgs","repos_url":"https://api.github.com/users/kosap/repos","events_url":"https://api.github.com/users/kosap/events{/privacy}","received_events_url":"https://api.github.com/users/kosap/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":14,"created_at":"2021-05-21T04:37:08Z","updated_at":"2021-07-21T07:00:04Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/zaproxy/zap-extensions/pulls/2947","html_url":"https://github.com/zaproxy/zap-extensions/pull/2947","diff_url":"https://github.com/zaproxy/zap-extensions/pull/2947.diff","patch_url":"https://github.com/zaproxy/zap-extensions/pull/2947.patch"},"body":"As discussed in zaproxy/zaproxy#6595, modified SourceCodeDisclosureScanRule to ignore HttpResponse with content type header as css or javascript.\r\n\r\nFix zaproxy/zaproxy#6595.","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/zaproxy/zap-extensions/issues/comments/883942813","html_url":"https://github.com/zaproxy/zap-extensions/pull/2947#issuecomment-883942813","issue_url":"https://api.github.com/repos/zaproxy/zap-extensions/issues/2947","id":883942813,"node_id":"IC_kwDOAhlFVM40r-Wd","user":{"login":"thc202","id":4639210,"node_id":"MDQ6VXNlcjQ2MzkyMTA=","avatar_url":"https://avatars.githubusercontent.com/u/4639210?v=4","gravatar_id":"","url":"https://api.github.com/users/thc202","html_url":"https://github.com/thc202","followers_url":"https://api.github.com/users/thc202/followers","following_url":"https://api.github.com/users/thc202/following{/other_user}","gists_url":"https://api.github.com/users/thc202/gists{/gist_id}","starred_url":"https://api.github.com/users/thc202/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thc202/subscriptions","organizations_url":"https://api.github.com/users/thc202/orgs","repos_url":"https://api.github.com/users/thc202/repos","events_url":"https://api.github.com/users/thc202/events{/privacy}","received_events_url":"https://api.github.com/users/thc202/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-21T07:00:04Z","author_association":"MEMBER","body":"Commits could be fixed up.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:04Z","org":{"id":6716868,"login":"zaproxy","gravatar_id":"","url":"https://api.github.com/orgs/zaproxy","avatar_url":"https://avatars.githubusercontent.com/u/6716868?"}} +{"id":"17244792498","type":"WatchEvent","actor":{"id":74425534,"login":"sjoveska","display_login":"sjoveska","gravatar_id":"","url":"https://api.github.com/users/sjoveska","avatar_url":"https://avatars.githubusercontent.com/u/74425534?"},"repo":{"id":374106606,"name":"gabrieldim/Register-And-Payment-Form-Layout","url":"https://api.github.com/repos/gabrieldim/Register-And-Payment-Form-Layout"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792499","type":"CommitCommentEvent","actor":{"id":35613825,"login":"vercel[bot]","display_login":"vercel","gravatar_id":"","url":"https://api.github.com/users/vercel[bot]","avatar_url":"https://avatars.githubusercontent.com/u/35613825?"},"repo":{"id":356784453,"name":"lp-Imagine/lp-Imagine.github.io","url":"https://api.github.com/repos/lp-Imagine/lp-Imagine.github.io"},"payload":{"comment":{"url":"https://api.github.com/repos/lp-Imagine/lp-Imagine.github.io/comments/53767302","html_url":"https://github.com/lp-Imagine/lp-Imagine.github.io/commit/43a35f2161127c4a37492ca32b51535fcba0a4d4#commitcomment-53767302","id":53767302,"node_id":"MDEzOkNvbW1pdENvbW1lbnQ1Mzc2NzMwMg==","user":{"login":"vercel[bot]","id":35613825,"node_id":"MDM6Qm90MzU2MTM4MjU=","avatar_url":"https://avatars.githubusercontent.com/in/8329?v=4","gravatar_id":"","url":"https://api.github.com/users/vercel%5Bbot%5D","html_url":"https://github.com/apps/vercel","followers_url":"https://api.github.com/users/vercel%5Bbot%5D/followers","following_url":"https://api.github.com/users/vercel%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/vercel%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/vercel%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vercel%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/vercel%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/vercel%5Bbot%5D/repos","events_url":"https://api.github.com/users/vercel%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/vercel%5Bbot%5D/received_events","type":"Bot","site_admin":false},"position":null,"line":null,"path":null,"commit_id":"43a35f2161127c4a37492ca32b51535fcba0a4d4","created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-21T07:00:04Z","author_association":"NONE","body":"Successfully deployed to the following URLs:\n\n* [imagineblog.vercel.app](https://imagineblog.vercel.app) \n* [lp-imagine-github-io.vercel.app](https://lp-imagine-github-io.vercel.app) \n* [imagineblog.host](https://imagineblog.host) \n* [lp-imagine-github-io-lp-imagine.vercel.app](https://lp-imagine-github-io-lp-imagine.vercel.app) \n* [lp-imagine-github-io-git-master-lp-imagine.vercel.app](https://lp-imagine-github-io-git-master-lp-imagine.vercel.app)"}},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792503","type":"PushEvent","actor":{"id":40587912,"login":"supermobiteam2","display_login":"supermobiteam2","gravatar_id":"","url":"https://api.github.com/users/supermobiteam2","avatar_url":"https://avatars.githubusercontent.com/u/40587912?"},"repo":{"id":138681984,"name":"supermobiteam2/Tizi","url":"https://api.github.com/repos/supermobiteam2/Tizi"},"payload":{"push_id":7561706491,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"fe488697b938857878169d82f2b2d4c7f76deb8e","before":"3cdb66aaa102b90db6439b4933ad0771af9bce2b","commits":[{"sha":"fe488697b938857878169d82f2b2d4c7f76deb8e","author":{"name":"supermobiteam2","email":"f34688687956b708ea5937840a164da02e7b6797@users.noreply.github.com"},"message":"tizi ios","distinct":true,"url":"https://api.github.com/repos/supermobiteam2/Tizi/commits/fe488697b938857878169d82f2b2d4c7f76deb8e"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792504","type":"PushEvent","actor":{"id":1354510,"login":"leo91000","display_login":"leo91000","gravatar_id":"","url":"https://api.github.com/users/leo91000","avatar_url":"https://avatars.githubusercontent.com/u/1354510?"},"repo":{"id":372306529,"name":"leo91000/covid-japan-informations","url":"https://api.github.com/repos/leo91000/covid-japan-informations"},"payload":{"push_id":7561706493,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"84205448ea1418657e8ab7be41783563e47af2c3","before":"5586a9dc27e52729fcacc2f8436632c6bee95140","commits":[{"sha":"84205448ea1418657e8ab7be41783563e47af2c3","author":{"name":"Léo Coletta","email":"c44abbda51c039fd3030f3ac7bea105b5e3f239d@gmail.com"},"message":"update 2021-07-21T07:00:03.739Z","distinct":true,"url":"https://api.github.com/repos/leo91000/covid-japan-informations/commits/84205448ea1418657e8ab7be41783563e47af2c3"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792505","type":"PushEvent","actor":{"id":5430733,"login":"matsduf","display_login":"matsduf","gravatar_id":"","url":"https://api.github.com/users/matsduf","avatar_url":"https://avatars.githubusercontent.com/u/5430733?"},"repo":{"id":73199016,"name":"matsduf/zonemaster-backend","url":"https://api.github.com/repos/matsduf/zonemaster-backend"},"payload":{"push_id":7561706488,"size":3,"distinct_size":3,"ref":"refs/heads/develop","head":"d5588d0b1f0360de93ee97842ce7a1e014270a0a","before":"f7a76f2434537b52649e52c31507690355e89f5d","commits":[{"sha":"27c4f21734a1e4d7c99f4cc14c17411cf784c71b","author":{"name":"Mats Dufberg","email":"f8eab42c0f39d270f82330c995d2725c9b9a2c72@iis.se"},"message":"Adds API methods add_batch_job and get_batch_job_result","distinct":true,"url":"https://api.github.com/repos/matsduf/zonemaster-backend/commits/27c4f21734a1e4d7c99f4cc14c17411cf784c71b"},{"sha":"75b30eb334f4fc568134d059e988a3ea1f10f685","author":{"name":"Mats Dufberg","email":"f8eab42c0f39d270f82330c995d2725c9b9a2c72@iis.se"},"message":"Corrects typo in POD text","distinct":true,"url":"https://api.github.com/repos/matsduf/zonemaster-backend/commits/75b30eb334f4fc568134d059e988a3ea1f10f685"},{"sha":"d5588d0b1f0360de93ee97842ce7a1e014270a0a","author":{"name":"Mats Dufberg","email":"f8eab42c0f39d270f82330c995d2725c9b9a2c72@iis.se"},"message":"Merge pull request #825 from matsduf/add-batch-functions-zmb\n\nAdds API methods add_batch_job and get_batch_job_result","distinct":true,"url":"https://api.github.com/repos/matsduf/zonemaster-backend/commits/d5588d0b1f0360de93ee97842ce7a1e014270a0a"}]},"public":true,"created_at":"2021-07-21T07:00:04Z"} +{"id":"17244792513","type":"GollumEvent","actor":{"id":7449066,"login":"idma88","display_login":"idma88","gravatar_id":"","url":"https://api.github.com/users/idma88","avatar_url":"https://avatars.githubusercontent.com/u/7449066?"},"repo":{"id":388024451,"name":"sk-zk/md141","url":"https://api.github.com/repos/sk-zk/md141"},"payload":{"pages":[{"page_name":"Map-Overlay","title":"Map Overlay","summary":null,"action":"created","sha":"bb767455f2fefdf19d8959af2cf0108f64ff623f","html_url":"https://github.com/sk-zk/md141/wiki/Map-Overlay"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792520","type":"PullRequestReviewEvent","actor":{"id":79920680,"login":"idevainstr","display_login":"idevainstr","gravatar_id":"","url":"https://api.github.com/users/idevainstr","avatar_url":"https://avatars.githubusercontent.com/u/79920680?"},"repo":{"id":171557612,"name":"instructure/canvas-ios","url":"https://api.github.com/repos/instructure/canvas-ios"},"payload":{"action":"created","review":{"id":711317400,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3NDAw","user":{"login":"idevainstr","id":79920680,"node_id":"MDQ6VXNlcjc5OTIwNjgw","avatar_url":"https://avatars.githubusercontent.com/u/79920680?v=4","gravatar_id":"","url":"https://api.github.com/users/idevainstr","html_url":"https://github.com/idevainstr","followers_url":"https://api.github.com/users/idevainstr/followers","following_url":"https://api.github.com/users/idevainstr/following{/other_user}","gists_url":"https://api.github.com/users/idevainstr/gists{/gist_id}","starred_url":"https://api.github.com/users/idevainstr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/idevainstr/subscriptions","organizations_url":"https://api.github.com/users/idevainstr/orgs","repos_url":"https://api.github.com/users/idevainstr/repos","events_url":"https://api.github.com/users/idevainstr/events{/privacy}","received_events_url":"https://api.github.com/users/idevainstr/received_events","type":"User","site_admin":false},"body":"QA+1","commit_id":"8448004988b4e8c2af1d081d581e61ce175efdc6","submitted_at":"2021-07-21T07:00:04Z","state":"approved","html_url":"https://github.com/instructure/canvas-ios/pull/1905#pullrequestreview-711317400","pull_request_url":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/instructure/canvas-ios/pull/1905#pullrequestreview-711317400"},"pull_request":{"href":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905"}}},"pull_request":{"url":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905","id":692717889,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyNzE3ODg5","html_url":"https://github.com/instructure/canvas-ios/pull/1905","diff_url":"https://github.com/instructure/canvas-ios/pull/1905.diff","patch_url":"https://github.com/instructure/canvas-ios/pull/1905.patch","issue_url":"https://api.github.com/repos/instructure/canvas-ios/issues/1905","number":1905,"state":"open","locked":false,"title":"Handle not supported font traits.","user":{"login":"vargaat","id":72396990,"node_id":"MDQ6VXNlcjcyMzk2OTkw","avatar_url":"https://avatars.githubusercontent.com/u/72396990?v=4","gravatar_id":"","url":"https://api.github.com/users/vargaat","html_url":"https://github.com/vargaat","followers_url":"https://api.github.com/users/vargaat/followers","following_url":"https://api.github.com/users/vargaat/following{/other_user}","gists_url":"https://api.github.com/users/vargaat/gists{/gist_id}","starred_url":"https://api.github.com/users/vargaat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vargaat/subscriptions","organizations_url":"https://api.github.com/users/vargaat/orgs","repos_url":"https://api.github.com/users/vargaat/repos","events_url":"https://api.github.com/users/vargaat/events{/privacy}","received_events_url":"https://api.github.com/users/vargaat/received_events","type":"User","site_admin":false},"body":"refs: MBL-15544\r\naffects: Student\r\nrelease note: none\r\n\r\ntest plan: See ticket.","created_at":"2021-07-19T15:38:50Z","updated_at":"2021-07-21T07:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8350e707ef06fcc740787e0cc5cc4278ce34a62c","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905/commits","review_comments_url":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905/comments","review_comment_url":"https://api.github.com/repos/instructure/canvas-ios/pulls/comments{/number}","comments_url":"https://api.github.com/repos/instructure/canvas-ios/issues/1905/comments","statuses_url":"https://api.github.com/repos/instructure/canvas-ios/statuses/8448004988b4e8c2af1d081d581e61ce175efdc6","head":{"label":"instructure:bugfix/MBL-15544-K5-modules-crash","ref":"bugfix/MBL-15544-K5-modules-crash","sha":"8448004988b4e8c2af1d081d581e61ce175efdc6","user":{"login":"instructure","id":515326,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNTMyNg==","avatar_url":"https://avatars.githubusercontent.com/u/515326?v=4","gravatar_id":"","url":"https://api.github.com/users/instructure","html_url":"https://github.com/instructure","followers_url":"https://api.github.com/users/instructure/followers","following_url":"https://api.github.com/users/instructure/following{/other_user}","gists_url":"https://api.github.com/users/instructure/gists{/gist_id}","starred_url":"https://api.github.com/users/instructure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/instructure/subscriptions","organizations_url":"https://api.github.com/users/instructure/orgs","repos_url":"https://api.github.com/users/instructure/repos","events_url":"https://api.github.com/users/instructure/events{/privacy}","received_events_url":"https://api.github.com/users/instructure/received_events","type":"Organization","site_admin":false},"repo":{"id":171557612,"node_id":"MDEwOlJlcG9zaXRvcnkxNzE1NTc2MTI=","name":"canvas-ios","full_name":"instructure/canvas-ios","private":false,"owner":{"login":"instructure","id":515326,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNTMyNg==","avatar_url":"https://avatars.githubusercontent.com/u/515326?v=4","gravatar_id":"","url":"https://api.github.com/users/instructure","html_url":"https://github.com/instructure","followers_url":"https://api.github.com/users/instructure/followers","following_url":"https://api.github.com/users/instructure/following{/other_user}","gists_url":"https://api.github.com/users/instructure/gists{/gist_id}","starred_url":"https://api.github.com/users/instructure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/instructure/subscriptions","organizations_url":"https://api.github.com/users/instructure/orgs","repos_url":"https://api.github.com/users/instructure/repos","events_url":"https://api.github.com/users/instructure/events{/privacy}","received_events_url":"https://api.github.com/users/instructure/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/instructure/canvas-ios","description":"Canvas iOS apps","fork":false,"url":"https://api.github.com/repos/instructure/canvas-ios","forks_url":"https://api.github.com/repos/instructure/canvas-ios/forks","keys_url":"https://api.github.com/repos/instructure/canvas-ios/keys{/key_id}","collaborators_url":"https://api.github.com/repos/instructure/canvas-ios/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/instructure/canvas-ios/teams","hooks_url":"https://api.github.com/repos/instructure/canvas-ios/hooks","issue_events_url":"https://api.github.com/repos/instructure/canvas-ios/issues/events{/number}","events_url":"https://api.github.com/repos/instructure/canvas-ios/events","assignees_url":"https://api.github.com/repos/instructure/canvas-ios/assignees{/user}","branches_url":"https://api.github.com/repos/instructure/canvas-ios/branches{/branch}","tags_url":"https://api.github.com/repos/instructure/canvas-ios/tags","blobs_url":"https://api.github.com/repos/instructure/canvas-ios/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/instructure/canvas-ios/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/instructure/canvas-ios/git/refs{/sha}","trees_url":"https://api.github.com/repos/instructure/canvas-ios/git/trees{/sha}","statuses_url":"https://api.github.com/repos/instructure/canvas-ios/statuses/{sha}","languages_url":"https://api.github.com/repos/instructure/canvas-ios/languages","stargazers_url":"https://api.github.com/repos/instructure/canvas-ios/stargazers","contributors_url":"https://api.github.com/repos/instructure/canvas-ios/contributors","subscribers_url":"https://api.github.com/repos/instructure/canvas-ios/subscribers","subscription_url":"https://api.github.com/repos/instructure/canvas-ios/subscription","commits_url":"https://api.github.com/repos/instructure/canvas-ios/commits{/sha}","git_commits_url":"https://api.github.com/repos/instructure/canvas-ios/git/commits{/sha}","comments_url":"https://api.github.com/repos/instructure/canvas-ios/comments{/number}","issue_comment_url":"https://api.github.com/repos/instructure/canvas-ios/issues/comments{/number}","contents_url":"https://api.github.com/repos/instructure/canvas-ios/contents/{+path}","compare_url":"https://api.github.com/repos/instructure/canvas-ios/compare/{base}...{head}","merges_url":"https://api.github.com/repos/instructure/canvas-ios/merges","archive_url":"https://api.github.com/repos/instructure/canvas-ios/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/instructure/canvas-ios/downloads","issues_url":"https://api.github.com/repos/instructure/canvas-ios/issues{/number}","pulls_url":"https://api.github.com/repos/instructure/canvas-ios/pulls{/number}","milestones_url":"https://api.github.com/repos/instructure/canvas-ios/milestones{/number}","notifications_url":"https://api.github.com/repos/instructure/canvas-ios/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/instructure/canvas-ios/labels{/name}","releases_url":"https://api.github.com/repos/instructure/canvas-ios/releases{/id}","deployments_url":"https://api.github.com/repos/instructure/canvas-ios/deployments","created_at":"2019-02-19T22:06:53Z","updated_at":"2021-07-20T12:59:08Z","pushed_at":"2021-07-20T15:02:24Z","git_url":"git://github.com/instructure/canvas-ios.git","ssh_url":"git@github.com:instructure/canvas-ios.git","clone_url":"https://github.com/instructure/canvas-ios.git","svn_url":"https://github.com/instructure/canvas-ios","homepage":"","size":325508,"stargazers_count":297,"watchers_count":297,"language":"Swift","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":48,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":48,"open_issues":4,"watchers":297,"default_branch":"master"}},"base":{"label":"instructure:master","ref":"master","sha":"3db8b879b0b4f6983a8547a24e00bd090944d5ca","user":{"login":"instructure","id":515326,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNTMyNg==","avatar_url":"https://avatars.githubusercontent.com/u/515326?v=4","gravatar_id":"","url":"https://api.github.com/users/instructure","html_url":"https://github.com/instructure","followers_url":"https://api.github.com/users/instructure/followers","following_url":"https://api.github.com/users/instructure/following{/other_user}","gists_url":"https://api.github.com/users/instructure/gists{/gist_id}","starred_url":"https://api.github.com/users/instructure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/instructure/subscriptions","organizations_url":"https://api.github.com/users/instructure/orgs","repos_url":"https://api.github.com/users/instructure/repos","events_url":"https://api.github.com/users/instructure/events{/privacy}","received_events_url":"https://api.github.com/users/instructure/received_events","type":"Organization","site_admin":false},"repo":{"id":171557612,"node_id":"MDEwOlJlcG9zaXRvcnkxNzE1NTc2MTI=","name":"canvas-ios","full_name":"instructure/canvas-ios","private":false,"owner":{"login":"instructure","id":515326,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNTMyNg==","avatar_url":"https://avatars.githubusercontent.com/u/515326?v=4","gravatar_id":"","url":"https://api.github.com/users/instructure","html_url":"https://github.com/instructure","followers_url":"https://api.github.com/users/instructure/followers","following_url":"https://api.github.com/users/instructure/following{/other_user}","gists_url":"https://api.github.com/users/instructure/gists{/gist_id}","starred_url":"https://api.github.com/users/instructure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/instructure/subscriptions","organizations_url":"https://api.github.com/users/instructure/orgs","repos_url":"https://api.github.com/users/instructure/repos","events_url":"https://api.github.com/users/instructure/events{/privacy}","received_events_url":"https://api.github.com/users/instructure/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/instructure/canvas-ios","description":"Canvas iOS apps","fork":false,"url":"https://api.github.com/repos/instructure/canvas-ios","forks_url":"https://api.github.com/repos/instructure/canvas-ios/forks","keys_url":"https://api.github.com/repos/instructure/canvas-ios/keys{/key_id}","collaborators_url":"https://api.github.com/repos/instructure/canvas-ios/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/instructure/canvas-ios/teams","hooks_url":"https://api.github.com/repos/instructure/canvas-ios/hooks","issue_events_url":"https://api.github.com/repos/instructure/canvas-ios/issues/events{/number}","events_url":"https://api.github.com/repos/instructure/canvas-ios/events","assignees_url":"https://api.github.com/repos/instructure/canvas-ios/assignees{/user}","branches_url":"https://api.github.com/repos/instructure/canvas-ios/branches{/branch}","tags_url":"https://api.github.com/repos/instructure/canvas-ios/tags","blobs_url":"https://api.github.com/repos/instructure/canvas-ios/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/instructure/canvas-ios/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/instructure/canvas-ios/git/refs{/sha}","trees_url":"https://api.github.com/repos/instructure/canvas-ios/git/trees{/sha}","statuses_url":"https://api.github.com/repos/instructure/canvas-ios/statuses/{sha}","languages_url":"https://api.github.com/repos/instructure/canvas-ios/languages","stargazers_url":"https://api.github.com/repos/instructure/canvas-ios/stargazers","contributors_url":"https://api.github.com/repos/instructure/canvas-ios/contributors","subscribers_url":"https://api.github.com/repos/instructure/canvas-ios/subscribers","subscription_url":"https://api.github.com/repos/instructure/canvas-ios/subscription","commits_url":"https://api.github.com/repos/instructure/canvas-ios/commits{/sha}","git_commits_url":"https://api.github.com/repos/instructure/canvas-ios/git/commits{/sha}","comments_url":"https://api.github.com/repos/instructure/canvas-ios/comments{/number}","issue_comment_url":"https://api.github.com/repos/instructure/canvas-ios/issues/comments{/number}","contents_url":"https://api.github.com/repos/instructure/canvas-ios/contents/{+path}","compare_url":"https://api.github.com/repos/instructure/canvas-ios/compare/{base}...{head}","merges_url":"https://api.github.com/repos/instructure/canvas-ios/merges","archive_url":"https://api.github.com/repos/instructure/canvas-ios/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/instructure/canvas-ios/downloads","issues_url":"https://api.github.com/repos/instructure/canvas-ios/issues{/number}","pulls_url":"https://api.github.com/repos/instructure/canvas-ios/pulls{/number}","milestones_url":"https://api.github.com/repos/instructure/canvas-ios/milestones{/number}","notifications_url":"https://api.github.com/repos/instructure/canvas-ios/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/instructure/canvas-ios/labels{/name}","releases_url":"https://api.github.com/repos/instructure/canvas-ios/releases{/id}","deployments_url":"https://api.github.com/repos/instructure/canvas-ios/deployments","created_at":"2019-02-19T22:06:53Z","updated_at":"2021-07-20T12:59:08Z","pushed_at":"2021-07-20T15:02:24Z","git_url":"git://github.com/instructure/canvas-ios.git","ssh_url":"git@github.com:instructure/canvas-ios.git","clone_url":"https://github.com/instructure/canvas-ios.git","svn_url":"https://github.com/instructure/canvas-ios","homepage":"","size":325508,"stargazers_count":297,"watchers_count":297,"language":"Swift","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":48,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":48,"open_issues":4,"watchers":297,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905"},"html":{"href":"https://github.com/instructure/canvas-ios/pull/1905"},"issue":{"href":"https://api.github.com/repos/instructure/canvas-ios/issues/1905"},"comments":{"href":"https://api.github.com/repos/instructure/canvas-ios/issues/1905/comments"},"review_comments":{"href":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905/comments"},"review_comment":{"href":"https://api.github.com/repos/instructure/canvas-ios/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/instructure/canvas-ios/pulls/1905/commits"},"statuses":{"href":"https://api.github.com/repos/instructure/canvas-ios/statuses/8448004988b4e8c2af1d081d581e61ce175efdc6"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":515326,"login":"instructure","gravatar_id":"","url":"https://api.github.com/orgs/instructure","avatar_url":"https://avatars.githubusercontent.com/u/515326?"}} +{"id":"17244792521","type":"PushEvent","actor":{"id":5102591,"login":"tabuna","display_login":"tabuna","gravatar_id":"","url":"https://api.github.com/users/tabuna","avatar_url":"https://avatars.githubusercontent.com/u/5102591?"},"repo":{"id":273078175,"name":"xtsmi/aggregator","url":"https://api.github.com/repos/xtsmi/aggregator"},"payload":{"push_id":7561706481,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"5a682170203389052eab2861fd7ce07a8e13b5e5","before":"67ad275961a0071a2acfb4e54fca6d72f4c10ee0","commits":[{"sha":"5a682170203389052eab2861fd7ce07a8e13b5e5","author":{"name":"tabuna","email":"0eac113397cac68650ea2a697b0e7ad1e804020e@users.noreply.github.com"},"message":"Deploying to master from @ 05cbd9dbc98efa7aa33816dab7d5a14509efeee2 🚀","distinct":true,"url":"https://api.github.com/repos/xtsmi/aggregator/commits/5a682170203389052eab2861fd7ce07a8e13b5e5"}]},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":67076553,"login":"xtsmi","gravatar_id":"","url":"https://api.github.com/orgs/xtsmi","avatar_url":"https://avatars.githubusercontent.com/u/67076553?"}} +{"id":"17244792531","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561706512,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"fd13b1c5319c1f9ad4a95ec828ec6c11d514f219","before":"1e8d3c37bdaa27f94049689bd806b189b0e2397b","commits":[{"sha":"fd13b1c5319c1f9ad4a95ec828ec6c11d514f219","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/fd13b1c5319c1f9ad4a95ec828ec6c11d514f219"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792535","type":"PushEvent","actor":{"id":15313630,"login":"michaelWuensch","display_login":"michaelWuensch","gravatar_id":"","url":"https://api.github.com/users/michaelWuensch","avatar_url":"https://avatars.githubusercontent.com/u/15313630?"},"repo":{"id":160931942,"name":"LN-Zap/zap-android","url":"https://api.github.com/repos/LN-Zap/zap-android"},"payload":{"push_id":7561706502,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"06df1ad43954195ed198ed64b238c184ec80b711","before":"7e2d404c4905b60ae608c9c3756f4f214aff0301","commits":[{"sha":"06df1ad43954195ed198ed64b238c184ec80b711","author":{"name":"Michael Wünsch","email":"3283b6f35a21cbf66b7cd85dcfceb4f427c981dc@protonmail.com"},"message":"Fix wallet status dot (#342)","distinct":true,"url":"https://api.github.com/repos/LN-Zap/zap-android/commits/06df1ad43954195ed198ed64b238c184ec80b711"}]},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":30905490,"login":"LN-Zap","gravatar_id":"","url":"https://api.github.com/orgs/LN-Zap","avatar_url":"https://avatars.githubusercontent.com/u/30905490?"}} +{"id":"17244792541","type":"WatchEvent","actor":{"id":12371374,"login":"JimChenWYU","display_login":"JimChenWYU","gravatar_id":"","url":"https://api.github.com/users/JimChenWYU","avatar_url":"https://avatars.githubusercontent.com/u/12371374?"},"repo":{"id":272035489,"name":"greyireland/algorithm-pattern","url":"https://api.github.com/repos/greyireland/algorithm-pattern"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792545","type":"MemberEvent","actor":{"id":49743951,"login":"inbarhavilio","display_login":"inbarhavilio","gravatar_id":"","url":"https://api.github.com/users/inbarhavilio","avatar_url":"https://avatars.githubusercontent.com/u/49743951?"},"repo":{"id":374307936,"name":"inbarhavilio/ADT_List","url":"https://api.github.com/repos/inbarhavilio/ADT_List"},"payload":{"member":{"login":"yuvalbloom","id":69865367,"node_id":"MDQ6VXNlcjY5ODY1MzY3","avatar_url":"https://avatars.githubusercontent.com/u/69865367?v=4","gravatar_id":"","url":"https://api.github.com/users/yuvalbloom","html_url":"https://github.com/yuvalbloom","followers_url":"https://api.github.com/users/yuvalbloom/followers","following_url":"https://api.github.com/users/yuvalbloom/following{/other_user}","gists_url":"https://api.github.com/users/yuvalbloom/gists{/gist_id}","starred_url":"https://api.github.com/users/yuvalbloom/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yuvalbloom/subscriptions","organizations_url":"https://api.github.com/users/yuvalbloom/orgs","repos_url":"https://api.github.com/users/yuvalbloom/repos","events_url":"https://api.github.com/users/yuvalbloom/events{/privacy}","received_events_url":"https://api.github.com/users/yuvalbloom/received_events","type":"User","site_admin":false},"action":"added"},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792547","type":"WatchEvent","actor":{"id":12711726,"login":"sleepycodernotes","display_login":"sleepycodernotes","gravatar_id":"","url":"https://api.github.com/users/sleepycodernotes","avatar_url":"https://avatars.githubusercontent.com/u/12711726?"},"repo":{"id":235901813,"name":"PostHog/posthog","url":"https://api.github.com/repos/PostHog/posthog"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":60330232,"login":"PostHog","gravatar_id":"","url":"https://api.github.com/orgs/PostHog","avatar_url":"https://avatars.githubusercontent.com/u/60330232?"}} +{"id":"17244792550","type":"IssueCommentEvent","actor":{"id":1070273,"login":"kvaster","display_login":"kvaster","gravatar_id":"","url":"https://api.github.com/users/kvaster","avatar_url":"https://avatars.githubusercontent.com/u/1070273?"},"repo":{"id":48109239,"name":"cilium/cilium","url":"https://api.github.com/repos/cilium/cilium"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/cilium/cilium/issues/16772","repository_url":"https://api.github.com/repos/cilium/cilium","labels_url":"https://api.github.com/repos/cilium/cilium/issues/16772/labels{/name}","comments_url":"https://api.github.com/repos/cilium/cilium/issues/16772/comments","events_url":"https://api.github.com/repos/cilium/cilium/issues/16772/events","html_url":"https://github.com/cilium/cilium/pull/16772","id":936735447,"node_id":"MDExOlB1bGxSZXF1ZXN0NjgzMzk5NjEy","number":16772,"title":"Pass vlan tagged packets back to the kernel stack.","user":{"login":"kvaster","id":1070273,"node_id":"MDQ6VXNlcjEwNzAyNzM=","avatar_url":"https://avatars.githubusercontent.com/u/1070273?v=4","gravatar_id":"","url":"https://api.github.com/users/kvaster","html_url":"https://github.com/kvaster","followers_url":"https://api.github.com/users/kvaster/followers","following_url":"https://api.github.com/users/kvaster/following{/other_user}","gists_url":"https://api.github.com/users/kvaster/gists{/gist_id}","starred_url":"https://api.github.com/users/kvaster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kvaster/subscriptions","organizations_url":"https://api.github.com/users/kvaster/orgs","repos_url":"https://api.github.com/users/kvaster/repos","events_url":"https://api.github.com/users/kvaster/events{/privacy}","received_events_url":"https://api.github.com/users/kvaster/received_events","type":"User","site_admin":false},"labels":[{"id":732923415,"node_id":"MDU6TGFiZWw3MzI5MjM0MTU=","url":"https://api.github.com/repos/cilium/cilium/labels/release-note/minor","name":"release-note/minor","color":"c2e0c6","default":false,"description":"This PR changes functionality that users may find relevant to operating Cilium."}],"state":"open","locked":false,"assignee":{"login":"borkmann","id":677393,"node_id":"MDQ6VXNlcjY3NzM5Mw==","avatar_url":"https://avatars.githubusercontent.com/u/677393?v=4","gravatar_id":"","url":"https://api.github.com/users/borkmann","html_url":"https://github.com/borkmann","followers_url":"https://api.github.com/users/borkmann/followers","following_url":"https://api.github.com/users/borkmann/following{/other_user}","gists_url":"https://api.github.com/users/borkmann/gists{/gist_id}","starred_url":"https://api.github.com/users/borkmann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/borkmann/subscriptions","organizations_url":"https://api.github.com/users/borkmann/orgs","repos_url":"https://api.github.com/users/borkmann/repos","events_url":"https://api.github.com/users/borkmann/events{/privacy}","received_events_url":"https://api.github.com/users/borkmann/received_events","type":"User","site_admin":false},"assignees":[{"login":"borkmann","id":677393,"node_id":"MDQ6VXNlcjY3NzM5Mw==","avatar_url":"https://avatars.githubusercontent.com/u/677393?v=4","gravatar_id":"","url":"https://api.github.com/users/borkmann","html_url":"https://github.com/borkmann","followers_url":"https://api.github.com/users/borkmann/followers","following_url":"https://api.github.com/users/borkmann/following{/other_user}","gists_url":"https://api.github.com/users/borkmann/gists{/gist_id}","starred_url":"https://api.github.com/users/borkmann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/borkmann/subscriptions","organizations_url":"https://api.github.com/users/borkmann/orgs","repos_url":"https://api.github.com/users/borkmann/repos","events_url":"https://api.github.com/users/borkmann/events{/privacy}","received_events_url":"https://api.github.com/users/borkmann/received_events","type":"User","site_admin":false}],"milestone":null,"comments":24,"created_at":"2021-07-05T06:23:24Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/cilium/cilium/pulls/16772","html_url":"https://github.com/cilium/cilium/pull/16772","diff_url":"https://github.com/cilium/cilium/pull/16772.diff","patch_url":"https://github.com/cilium/cilium/pull/16772.patch"},"body":"VLAN packets will be catched with bpf on main interface first. We\r\nneed to passs it back to the kernel stack. VLAN info/tag will be stripped and\r\npacket will be reenqueued on proper interface or dropped.\r\n\r\nAlso we do not need to process VLAN packets on egress, cause either\r\nwe've already processed such packets in case bpf program is attached\r\nto VLAN interface or we do not need to process such packets at all in\r\nother way.\r\n\r\nVLAN tagged packets will be processed in the folowing way:\r\n* If vlan tag is zero packet will be processed as usual (cause it is targeted for main interface)\r\n* For each device will be allowed only corresponding vlan tags.\r\n* By default will be allowed only tags from devices controlled by cilium.\r\n* Any other tags may be allowed via option (`--vlan-bpf-bypass`), `--vlan-bpf-bypass 0` may be used for allowing all available vlan tags.\r\n* All other vlan tags will be always dropped for security reason.\r\n\r\nConcerns:\r\n* Probably ifindexes and vlan tags should be sorted to make configuration predictable.\r\n* Probably something like `*` should be used insteadof `0` for allowing all available vlan tags.\r\n\r\nPrevious pull request and discussion: https://github.com/cilium/cilium/pull/15534\r\n\r\nFixes: cilium#14579\r\n\r\nSigned-off-by: Viktor Kuzmin \r\n\r\n```release-note\r\nMake NodePort BPF to work on VLAN devices\r\n```","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/cilium/cilium/issues/comments/883942816","html_url":"https://github.com/cilium/cilium/pull/16772#issuecomment-883942816","issue_url":"https://api.github.com/repos/cilium/cilium/issues/16772","id":883942816,"node_id":"IC_kwDOAt4Wt840r-Wg","user":{"login":"kvaster","id":1070273,"node_id":"MDQ6VXNlcjEwNzAyNzM=","avatar_url":"https://avatars.githubusercontent.com/u/1070273?v=4","gravatar_id":"","url":"https://api.github.com/users/kvaster","html_url":"https://github.com/kvaster","followers_url":"https://api.github.com/users/kvaster/followers","following_url":"https://api.github.com/users/kvaster/following{/other_user}","gists_url":"https://api.github.com/users/kvaster/gists{/gist_id}","starred_url":"https://api.github.com/users/kvaster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kvaster/subscriptions","organizations_url":"https://api.github.com/users/kvaster/orgs","repos_url":"https://api.github.com/users/kvaster/repos","events_url":"https://api.github.com/users/kvaster/events{/privacy}","received_events_url":"https://api.github.com/users/kvaster/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","author_association":"NONE","body":"> Thanks. Marking as ready to merge.\r\n> \r\n> @kvaster As a follow up, could you extend the kube-proxy-replacement guide by documenting your change?\r\n\r\nI will do this as a follow up PR!","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":21054566,"login":"cilium","gravatar_id":"","url":"https://api.github.com/orgs/cilium","avatar_url":"https://avatars.githubusercontent.com/u/21054566?"}} +{"id":"17244792563","type":"PushEvent","actor":{"id":8789772,"login":"Surendra-Patil","display_login":"Surendra-Patil","gravatar_id":"","url":"https://api.github.com/users/Surendra-Patil","avatar_url":"https://avatars.githubusercontent.com/u/8789772?"},"repo":{"id":388010537,"name":"Surendra-Patil/cicd-pipeline-train-schedule-autodeploy","url":"https://api.github.com/repos/Surendra-Patil/cicd-pipeline-train-schedule-autodeploy"},"payload":{"push_id":7561706517,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"435633c0c21c324efc7ff43573cc5a5ae900f4c1","before":"b776195a468bb9dcd342ee727d6c0674d00eb73d","commits":[{"sha":"435633c0c21c324efc7ff43573cc5a5ae900f4c1","author":{"name":"Surendra Patil","email":"cc655173746c3439478882df241d8bd516e222fa@gmail.com"},"message":"Testing","distinct":true,"url":"https://api.github.com/repos/Surendra-Patil/cicd-pipeline-train-schedule-autodeploy/commits/435633c0c21c324efc7ff43573cc5a5ae900f4c1"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792567","type":"PullRequestEvent","actor":{"id":65147229,"login":"DonaldLTB","display_login":"DonaldLTB","gravatar_id":"","url":"https://api.github.com/users/DonaldLTB","avatar_url":"https://avatars.githubusercontent.com/u/65147229?"},"repo":{"id":340231166,"name":"DonaldLTB/HLL_Fan_Page_JP","url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP"},"payload":{"action":"opened","number":38,"pull_request":{"url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/38","id":694104898,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0ODk4","html_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP/pull/38","diff_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP/pull/38.diff","patch_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP/pull/38.patch","issue_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/38","number":38,"state":"open","locked":false,"title":"Tri","user":{"login":"DonaldLTB","id":65147229,"node_id":"MDQ6VXNlcjY1MTQ3MjI5","avatar_url":"https://avatars.githubusercontent.com/u/65147229?v=4","gravatar_id":"","url":"https://api.github.com/users/DonaldLTB","html_url":"https://github.com/DonaldLTB","followers_url":"https://api.github.com/users/DonaldLTB/followers","following_url":"https://api.github.com/users/DonaldLTB/following{/other_user}","gists_url":"https://api.github.com/users/DonaldLTB/gists{/gist_id}","starred_url":"https://api.github.com/users/DonaldLTB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DonaldLTB/subscriptions","organizations_url":"https://api.github.com/users/DonaldLTB/orgs","repos_url":"https://api.github.com/users/DonaldLTB/repos","events_url":"https://api.github.com/users/DonaldLTB/events{/privacy}","received_events_url":"https://api.github.com/users/DonaldLTB/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-21T07:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/38/commits","review_comments_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/38/comments","review_comment_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/comments{/number}","comments_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/38/comments","statuses_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/statuses/75cc0e19b782b177ddd5d12f5f655b84d1c9da89","head":{"label":"DonaldLTB:tri","ref":"tri","sha":"75cc0e19b782b177ddd5d12f5f655b84d1c9da89","user":{"login":"DonaldLTB","id":65147229,"node_id":"MDQ6VXNlcjY1MTQ3MjI5","avatar_url":"https://avatars.githubusercontent.com/u/65147229?v=4","gravatar_id":"","url":"https://api.github.com/users/DonaldLTB","html_url":"https://github.com/DonaldLTB","followers_url":"https://api.github.com/users/DonaldLTB/followers","following_url":"https://api.github.com/users/DonaldLTB/following{/other_user}","gists_url":"https://api.github.com/users/DonaldLTB/gists{/gist_id}","starred_url":"https://api.github.com/users/DonaldLTB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DonaldLTB/subscriptions","organizations_url":"https://api.github.com/users/DonaldLTB/orgs","repos_url":"https://api.github.com/users/DonaldLTB/repos","events_url":"https://api.github.com/users/DonaldLTB/events{/privacy}","received_events_url":"https://api.github.com/users/DonaldLTB/received_events","type":"User","site_admin":false},"repo":{"id":340231166,"node_id":"MDEwOlJlcG9zaXRvcnkzNDAyMzExNjY=","name":"HLL_Fan_Page_JP","full_name":"DonaldLTB/HLL_Fan_Page_JP","private":false,"owner":{"login":"DonaldLTB","id":65147229,"node_id":"MDQ6VXNlcjY1MTQ3MjI5","avatar_url":"https://avatars.githubusercontent.com/u/65147229?v=4","gravatar_id":"","url":"https://api.github.com/users/DonaldLTB","html_url":"https://github.com/DonaldLTB","followers_url":"https://api.github.com/users/DonaldLTB/followers","following_url":"https://api.github.com/users/DonaldLTB/following{/other_user}","gists_url":"https://api.github.com/users/DonaldLTB/gists{/gist_id}","starred_url":"https://api.github.com/users/DonaldLTB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DonaldLTB/subscriptions","organizations_url":"https://api.github.com/users/DonaldLTB/orgs","repos_url":"https://api.github.com/users/DonaldLTB/repos","events_url":"https://api.github.com/users/DonaldLTB/events{/privacy}","received_events_url":"https://api.github.com/users/DonaldLTB/received_events","type":"User","site_admin":false},"html_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP","description":"HLL Page for Japanese/Japanese-speaking audience","fork":false,"url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP","forks_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/forks","keys_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/teams","hooks_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/hooks","issue_events_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/events{/number}","events_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/events","assignees_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/assignees{/user}","branches_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/branches{/branch}","tags_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/tags","blobs_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/refs{/sha}","trees_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/statuses/{sha}","languages_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/languages","stargazers_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/stargazers","contributors_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/contributors","subscribers_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/subscribers","subscription_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/subscription","commits_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/commits{/sha}","git_commits_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/commits{/sha}","comments_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/comments{/number}","issue_comment_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/comments{/number}","contents_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/contents/{+path}","compare_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/merges","archive_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/downloads","issues_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues{/number}","pulls_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls{/number}","milestones_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/milestones{/number}","notifications_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/labels{/name}","releases_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/releases{/id}","deployments_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/deployments","created_at":"2021-02-19T01:59:51Z","updated_at":"2021-07-20T06:23:58Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/DonaldLTB/HLL_Fan_Page_JP.git","ssh_url":"git@github.com:DonaldLTB/HLL_Fan_Page_JP.git","clone_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP.git","svn_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP","homepage":null,"size":5422,"stargazers_count":1,"watchers_count":1,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":1,"default_branch":"master"}},"base":{"label":"DonaldLTB:master","ref":"master","sha":"ca3263e5a494d60464871687e012c0540be9bc85","user":{"login":"DonaldLTB","id":65147229,"node_id":"MDQ6VXNlcjY1MTQ3MjI5","avatar_url":"https://avatars.githubusercontent.com/u/65147229?v=4","gravatar_id":"","url":"https://api.github.com/users/DonaldLTB","html_url":"https://github.com/DonaldLTB","followers_url":"https://api.github.com/users/DonaldLTB/followers","following_url":"https://api.github.com/users/DonaldLTB/following{/other_user}","gists_url":"https://api.github.com/users/DonaldLTB/gists{/gist_id}","starred_url":"https://api.github.com/users/DonaldLTB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DonaldLTB/subscriptions","organizations_url":"https://api.github.com/users/DonaldLTB/orgs","repos_url":"https://api.github.com/users/DonaldLTB/repos","events_url":"https://api.github.com/users/DonaldLTB/events{/privacy}","received_events_url":"https://api.github.com/users/DonaldLTB/received_events","type":"User","site_admin":false},"repo":{"id":340231166,"node_id":"MDEwOlJlcG9zaXRvcnkzNDAyMzExNjY=","name":"HLL_Fan_Page_JP","full_name":"DonaldLTB/HLL_Fan_Page_JP","private":false,"owner":{"login":"DonaldLTB","id":65147229,"node_id":"MDQ6VXNlcjY1MTQ3MjI5","avatar_url":"https://avatars.githubusercontent.com/u/65147229?v=4","gravatar_id":"","url":"https://api.github.com/users/DonaldLTB","html_url":"https://github.com/DonaldLTB","followers_url":"https://api.github.com/users/DonaldLTB/followers","following_url":"https://api.github.com/users/DonaldLTB/following{/other_user}","gists_url":"https://api.github.com/users/DonaldLTB/gists{/gist_id}","starred_url":"https://api.github.com/users/DonaldLTB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DonaldLTB/subscriptions","organizations_url":"https://api.github.com/users/DonaldLTB/orgs","repos_url":"https://api.github.com/users/DonaldLTB/repos","events_url":"https://api.github.com/users/DonaldLTB/events{/privacy}","received_events_url":"https://api.github.com/users/DonaldLTB/received_events","type":"User","site_admin":false},"html_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP","description":"HLL Page for Japanese/Japanese-speaking audience","fork":false,"url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP","forks_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/forks","keys_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/teams","hooks_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/hooks","issue_events_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/events{/number}","events_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/events","assignees_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/assignees{/user}","branches_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/branches{/branch}","tags_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/tags","blobs_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/refs{/sha}","trees_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/statuses/{sha}","languages_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/languages","stargazers_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/stargazers","contributors_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/contributors","subscribers_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/subscribers","subscription_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/subscription","commits_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/commits{/sha}","git_commits_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/git/commits{/sha}","comments_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/comments{/number}","issue_comment_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/comments{/number}","contents_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/contents/{+path}","compare_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/merges","archive_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/downloads","issues_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues{/number}","pulls_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls{/number}","milestones_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/milestones{/number}","notifications_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/labels{/name}","releases_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/releases{/id}","deployments_url":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/deployments","created_at":"2021-02-19T01:59:51Z","updated_at":"2021-07-20T06:23:58Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/DonaldLTB/HLL_Fan_Page_JP.git","ssh_url":"git@github.com:DonaldLTB/HLL_Fan_Page_JP.git","clone_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP.git","svn_url":"https://github.com/DonaldLTB/HLL_Fan_Page_JP","homepage":null,"size":5422,"stargazers_count":1,"watchers_count":1,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/38"},"html":{"href":"https://github.com/DonaldLTB/HLL_Fan_Page_JP/pull/38"},"issue":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/38"},"comments":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/issues/38/comments"},"review_comments":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/38/comments"},"review_comment":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/pulls/38/commits"},"statuses":{"href":"https://api.github.com/repos/DonaldLTB/HLL_Fan_Page_JP/statuses/75cc0e19b782b177ddd5d12f5f655b84d1c9da89"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":4,"additions":15,"deletions":15,"changed_files":4}},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792578","type":"PullRequestEvent","actor":{"id":65045755,"login":"Vasile-Hij","display_login":"Vasile-Hij","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","avatar_url":"https://avatars.githubusercontent.com/u/65045755?"},"repo":{"id":387832280,"name":"Vasile-Hij/excels-pandas","url":"https://api.github.com/repos/Vasile-Hij/excels-pandas"},"payload":{"action":"opened","number":1,"pull_request":{"url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/1","id":694104900,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0OTAw","html_url":"https://github.com/Vasile-Hij/excels-pandas/pull/1","diff_url":"https://github.com/Vasile-Hij/excels-pandas/pull/1.diff","patch_url":"https://github.com/Vasile-Hij/excels-pandas/pull/1.patch","issue_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/1","number":1,"state":"open","locked":false,"title":"Update excels-pandas.ipynb","user":{"login":"Vasile-Hij","id":65045755,"node_id":"MDQ6VXNlcjY1MDQ1NzU1","avatar_url":"https://avatars.githubusercontent.com/u/65045755?v=4","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","html_url":"https://github.com/Vasile-Hij","followers_url":"https://api.github.com/users/Vasile-Hij/followers","following_url":"https://api.github.com/users/Vasile-Hij/following{/other_user}","gists_url":"https://api.github.com/users/Vasile-Hij/gists{/gist_id}","starred_url":"https://api.github.com/users/Vasile-Hij/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Vasile-Hij/subscriptions","organizations_url":"https://api.github.com/users/Vasile-Hij/orgs","repos_url":"https://api.github.com/users/Vasile-Hij/repos","events_url":"https://api.github.com/users/Vasile-Hij/events{/privacy}","received_events_url":"https://api.github.com/users/Vasile-Hij/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:00:04Z","updated_at":"2021-07-21T07:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/1/commits","review_comments_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/1/comments","review_comment_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/1/comments","statuses_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/statuses/2c236fd216550ebb29e8b91d16558be31a84a0ea","head":{"label":"Vasile-Hij:v0.1","ref":"v0.1","sha":"2c236fd216550ebb29e8b91d16558be31a84a0ea","user":{"login":"Vasile-Hij","id":65045755,"node_id":"MDQ6VXNlcjY1MDQ1NzU1","avatar_url":"https://avatars.githubusercontent.com/u/65045755?v=4","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","html_url":"https://github.com/Vasile-Hij","followers_url":"https://api.github.com/users/Vasile-Hij/followers","following_url":"https://api.github.com/users/Vasile-Hij/following{/other_user}","gists_url":"https://api.github.com/users/Vasile-Hij/gists{/gist_id}","starred_url":"https://api.github.com/users/Vasile-Hij/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Vasile-Hij/subscriptions","organizations_url":"https://api.github.com/users/Vasile-Hij/orgs","repos_url":"https://api.github.com/users/Vasile-Hij/repos","events_url":"https://api.github.com/users/Vasile-Hij/events{/privacy}","received_events_url":"https://api.github.com/users/Vasile-Hij/received_events","type":"User","site_admin":false},"repo":{"id":387832280,"node_id":"MDEwOlJlcG9zaXRvcnkzODc4MzIyODA=","name":"excels-pandas","full_name":"Vasile-Hij/excels-pandas","private":false,"owner":{"login":"Vasile-Hij","id":65045755,"node_id":"MDQ6VXNlcjY1MDQ1NzU1","avatar_url":"https://avatars.githubusercontent.com/u/65045755?v=4","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","html_url":"https://github.com/Vasile-Hij","followers_url":"https://api.github.com/users/Vasile-Hij/followers","following_url":"https://api.github.com/users/Vasile-Hij/following{/other_user}","gists_url":"https://api.github.com/users/Vasile-Hij/gists{/gist_id}","starred_url":"https://api.github.com/users/Vasile-Hij/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Vasile-Hij/subscriptions","organizations_url":"https://api.github.com/users/Vasile-Hij/orgs","repos_url":"https://api.github.com/users/Vasile-Hij/repos","events_url":"https://api.github.com/users/Vasile-Hij/events{/privacy}","received_events_url":"https://api.github.com/users/Vasile-Hij/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Vasile-Hij/excels-pandas","description":null,"fork":false,"url":"https://api.github.com/repos/Vasile-Hij/excels-pandas","forks_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/forks","keys_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/teams","hooks_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/hooks","issue_events_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/events{/number}","events_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/events","assignees_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/assignees{/user}","branches_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/branches{/branch}","tags_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/tags","blobs_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/refs{/sha}","trees_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/statuses/{sha}","languages_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/languages","stargazers_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/stargazers","contributors_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/contributors","subscribers_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/subscribers","subscription_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/subscription","commits_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/commits{/sha}","git_commits_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/commits{/sha}","comments_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/comments{/number}","issue_comment_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/comments{/number}","contents_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/contents/{+path}","compare_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/merges","archive_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/downloads","issues_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues{/number}","pulls_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls{/number}","milestones_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/milestones{/number}","notifications_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/labels{/name}","releases_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/releases{/id}","deployments_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/deployments","created_at":"2021-07-20T15:16:44Z","updated_at":"2021-07-20T20:39:08Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/Vasile-Hij/excels-pandas.git","ssh_url":"git@github.com:Vasile-Hij/excels-pandas.git","clone_url":"https://github.com/Vasile-Hij/excels-pandas.git","svn_url":"https://github.com/Vasile-Hij/excels-pandas","homepage":null,"size":39,"stargazers_count":0,"watchers_count":0,"language":"Jupyter Notebook","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"base":{"label":"Vasile-Hij:main","ref":"main","sha":"8204e9cfd8ed1d0e3174e3fc9bc5edccaf443349","user":{"login":"Vasile-Hij","id":65045755,"node_id":"MDQ6VXNlcjY1MDQ1NzU1","avatar_url":"https://avatars.githubusercontent.com/u/65045755?v=4","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","html_url":"https://github.com/Vasile-Hij","followers_url":"https://api.github.com/users/Vasile-Hij/followers","following_url":"https://api.github.com/users/Vasile-Hij/following{/other_user}","gists_url":"https://api.github.com/users/Vasile-Hij/gists{/gist_id}","starred_url":"https://api.github.com/users/Vasile-Hij/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Vasile-Hij/subscriptions","organizations_url":"https://api.github.com/users/Vasile-Hij/orgs","repos_url":"https://api.github.com/users/Vasile-Hij/repos","events_url":"https://api.github.com/users/Vasile-Hij/events{/privacy}","received_events_url":"https://api.github.com/users/Vasile-Hij/received_events","type":"User","site_admin":false},"repo":{"id":387832280,"node_id":"MDEwOlJlcG9zaXRvcnkzODc4MzIyODA=","name":"excels-pandas","full_name":"Vasile-Hij/excels-pandas","private":false,"owner":{"login":"Vasile-Hij","id":65045755,"node_id":"MDQ6VXNlcjY1MDQ1NzU1","avatar_url":"https://avatars.githubusercontent.com/u/65045755?v=4","gravatar_id":"","url":"https://api.github.com/users/Vasile-Hij","html_url":"https://github.com/Vasile-Hij","followers_url":"https://api.github.com/users/Vasile-Hij/followers","following_url":"https://api.github.com/users/Vasile-Hij/following{/other_user}","gists_url":"https://api.github.com/users/Vasile-Hij/gists{/gist_id}","starred_url":"https://api.github.com/users/Vasile-Hij/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Vasile-Hij/subscriptions","organizations_url":"https://api.github.com/users/Vasile-Hij/orgs","repos_url":"https://api.github.com/users/Vasile-Hij/repos","events_url":"https://api.github.com/users/Vasile-Hij/events{/privacy}","received_events_url":"https://api.github.com/users/Vasile-Hij/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Vasile-Hij/excels-pandas","description":null,"fork":false,"url":"https://api.github.com/repos/Vasile-Hij/excels-pandas","forks_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/forks","keys_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/teams","hooks_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/hooks","issue_events_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/events{/number}","events_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/events","assignees_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/assignees{/user}","branches_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/branches{/branch}","tags_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/tags","blobs_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/refs{/sha}","trees_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/statuses/{sha}","languages_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/languages","stargazers_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/stargazers","contributors_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/contributors","subscribers_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/subscribers","subscription_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/subscription","commits_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/commits{/sha}","git_commits_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/git/commits{/sha}","comments_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/comments{/number}","issue_comment_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/comments{/number}","contents_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/contents/{+path}","compare_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/merges","archive_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/downloads","issues_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues{/number}","pulls_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls{/number}","milestones_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/milestones{/number}","notifications_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/labels{/name}","releases_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/releases{/id}","deployments_url":"https://api.github.com/repos/Vasile-Hij/excels-pandas/deployments","created_at":"2021-07-20T15:16:44Z","updated_at":"2021-07-20T20:39:08Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/Vasile-Hij/excels-pandas.git","ssh_url":"git@github.com:Vasile-Hij/excels-pandas.git","clone_url":"https://github.com/Vasile-Hij/excels-pandas.git","svn_url":"https://github.com/Vasile-Hij/excels-pandas","homepage":null,"size":39,"stargazers_count":0,"watchers_count":0,"language":"Jupyter Notebook","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/1"},"html":{"href":"https://github.com/Vasile-Hij/excels-pandas/pull/1"},"issue":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/1"},"comments":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/Vasile-Hij/excels-pandas/statuses/2c236fd216550ebb29e8b91d16558be31a84a0ea"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":15,"deletions":7,"changed_files":1}},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792579","type":"PushEvent","actor":{"id":22955941,"login":"Volodichev","display_login":"Volodichev","gravatar_id":"","url":"https://api.github.com/users/Volodichev","avatar_url":"https://avatars.githubusercontent.com/u/22955941?"},"repo":{"id":374556291,"name":"Volodichev/proxy-list","url":"https://api.github.com/repos/Volodichev/proxy-list"},"payload":{"push_id":7561706525,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"d88fed842c5252fff44229061355698c72c21a23","before":"c1363b4b7f22d217c0b38e74e4f603e31f328f2d","commits":[{"sha":"d88fed842c5252fff44229061355698c72c21a23","author":{"name":"Alexander","email":"db02dbb7d5376400a8069895ae273c92e0d1f31d@gmail.com"},"message":"hmn keys 21.07.21 10:00:01","distinct":true,"url":"https://api.github.com/repos/Volodichev/proxy-list/commits/d88fed842c5252fff44229061355698c72c21a23"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792580","type":"PushEvent","actor":{"id":13213761,"login":"spencer155","display_login":"spencer155","gravatar_id":"","url":"https://api.github.com/users/spencer155","avatar_url":"https://avatars.githubusercontent.com/u/13213761?"},"repo":{"id":124195997,"name":"spencer155/sun","url":"https://api.github.com/repos/spencer155/sun"},"payload":{"push_id":7561706524,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"4d61ee1e8212ffc776275da527223f5438827dea","before":"c82e3463493ecc9b3f36eab29e7dc1757c7b3247","commits":[{"sha":"4d61ee1e8212ffc776275da527223f5438827dea","author":{"name":"孙斌","email":"3a4ff063ca39860aa839c0a9171a57f22d750ae3@sunbin-MacBook-Pro.local"},"message":"context函数组件","distinct":true,"url":"https://api.github.com/repos/spencer155/sun/commits/4d61ee1e8212ffc776275da527223f5438827dea"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792582","type":"PullRequestReviewEvent","actor":{"id":7221389,"login":"mazipan","display_login":"mazipan","gravatar_id":"","url":"https://api.github.com/users/mazipan","avatar_url":"https://avatars.githubusercontent.com/u/7221389?"},"repo":{"id":383169156,"name":"kawalcovid19/wargabantuwarga.com","url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com"},"payload":{"action":"created","review":{"id":711317405,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3NDA1","user":{"login":"mazipan","id":7221389,"node_id":"MDQ6VXNlcjcyMjEzODk=","avatar_url":"https://avatars.githubusercontent.com/u/7221389?v=4","gravatar_id":"","url":"https://api.github.com/users/mazipan","html_url":"https://github.com/mazipan","followers_url":"https://api.github.com/users/mazipan/followers","following_url":"https://api.github.com/users/mazipan/following{/other_user}","gists_url":"https://api.github.com/users/mazipan/gists{/gist_id}","starred_url":"https://api.github.com/users/mazipan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mazipan/subscriptions","organizations_url":"https://api.github.com/users/mazipan/orgs","repos_url":"https://api.github.com/users/mazipan/repos","events_url":"https://api.github.com/users/mazipan/events{/privacy}","received_events_url":"https://api.github.com/users/mazipan/received_events","type":"User","site_admin":false},"body":"Looks good to me","commit_id":"d50f8c9f091c50f9d134975f2590a6b5dbddd99d","submitted_at":"2021-07-21T07:00:05Z","state":"approved","html_url":"https://github.com/kawalcovid19/wargabantuwarga.com/pull/239#pullrequestreview-711317405","pull_request_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239","author_association":"COLLABORATOR","_links":{"html":{"href":"https://github.com/kawalcovid19/wargabantuwarga.com/pull/239#pullrequestreview-711317405"},"pull_request":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239"}}},"pull_request":{"url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239","id":694023383,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDIzMzgz","html_url":"https://github.com/kawalcovid19/wargabantuwarga.com/pull/239","diff_url":"https://github.com/kawalcovid19/wargabantuwarga.com/pull/239.diff","patch_url":"https://github.com/kawalcovid19/wargabantuwarga.com/pull/239.patch","issue_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/239","number":239,"state":"open","locked":false,"title":"design system - button","user":{"login":"resir014","id":5663877,"node_id":"MDQ6VXNlcjU2NjM4Nzc=","avatar_url":"https://avatars.githubusercontent.com/u/5663877?v=4","gravatar_id":"","url":"https://api.github.com/users/resir014","html_url":"https://github.com/resir014","followers_url":"https://api.github.com/users/resir014/followers","following_url":"https://api.github.com/users/resir014/following{/other_user}","gists_url":"https://api.github.com/users/resir014/gists{/gist_id}","starred_url":"https://api.github.com/users/resir014/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resir014/subscriptions","organizations_url":"https://api.github.com/users/resir014/orgs","repos_url":"https://api.github.com/users/resir014/repos","events_url":"https://api.github.com/users/resir014/events{/privacy}","received_events_url":"https://api.github.com/users/resir014/received_events","type":"User","site_admin":false},"body":"Closes #212\r\n\r\n## Description\r\n\r\nAdds various features to our existing button components\r\n\r\n## Current Tasks\r\n\r\n\r\n\r\n- [x] Add White Button variant\r\n- [x] Expand each button's size options (xs, sm, md, lg, xl)\r\n- [x] Allow for rounded buttons\r\n- [x] Allow option to add icons in buttons (from Heroicons)\r\n- [x] Refactor button helper functions\r\n- [x] Apply to existing button components\r\n- [x] Add button loading states","created_at":"2021-07-21T03:53:15Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0fe0d2ad030be929f15213445c79e690006c1820","assignee":null,"assignees":[],"requested_reviewers":[{"login":"zainfathoni","id":6315466,"node_id":"MDQ6VXNlcjYzMTU0NjY=","avatar_url":"https://avatars.githubusercontent.com/u/6315466?v=4","gravatar_id":"","url":"https://api.github.com/users/zainfathoni","html_url":"https://github.com/zainfathoni","followers_url":"https://api.github.com/users/zainfathoni/followers","following_url":"https://api.github.com/users/zainfathoni/following{/other_user}","gists_url":"https://api.github.com/users/zainfathoni/gists{/gist_id}","starred_url":"https://api.github.com/users/zainfathoni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zainfathoni/subscriptions","organizations_url":"https://api.github.com/users/zainfathoni/orgs","repos_url":"https://api.github.com/users/zainfathoni/repos","events_url":"https://api.github.com/users/zainfathoni/events{/privacy}","received_events_url":"https://api.github.com/users/zainfathoni/received_events","type":"User","site_admin":false},{"login":"ekafyi","id":6597211,"node_id":"MDQ6VXNlcjY1OTcyMTE=","avatar_url":"https://avatars.githubusercontent.com/u/6597211?v=4","gravatar_id":"","url":"https://api.github.com/users/ekafyi","html_url":"https://github.com/ekafyi","followers_url":"https://api.github.com/users/ekafyi/followers","following_url":"https://api.github.com/users/ekafyi/following{/other_user}","gists_url":"https://api.github.com/users/ekafyi/gists{/gist_id}","starred_url":"https://api.github.com/users/ekafyi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ekafyi/subscriptions","organizations_url":"https://api.github.com/users/ekafyi/orgs","repos_url":"https://api.github.com/users/ekafyi/repos","events_url":"https://api.github.com/users/ekafyi/events{/privacy}","received_events_url":"https://api.github.com/users/ekafyi/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239/commits","review_comments_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239/comments","review_comment_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/239/comments","statuses_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/statuses/d50f8c9f091c50f9d134975f2590a6b5dbddd99d","head":{"label":"kawalcovid19:design-system-button","ref":"design-system-button","sha":"d50f8c9f091c50f9d134975f2590a6b5dbddd99d","user":{"login":"kawalcovid19","id":62234891,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyMjM0ODkx","avatar_url":"https://avatars.githubusercontent.com/u/62234891?v=4","gravatar_id":"","url":"https://api.github.com/users/kawalcovid19","html_url":"https://github.com/kawalcovid19","followers_url":"https://api.github.com/users/kawalcovid19/followers","following_url":"https://api.github.com/users/kawalcovid19/following{/other_user}","gists_url":"https://api.github.com/users/kawalcovid19/gists{/gist_id}","starred_url":"https://api.github.com/users/kawalcovid19/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kawalcovid19/subscriptions","organizations_url":"https://api.github.com/users/kawalcovid19/orgs","repos_url":"https://api.github.com/users/kawalcovid19/repos","events_url":"https://api.github.com/users/kawalcovid19/events{/privacy}","received_events_url":"https://api.github.com/users/kawalcovid19/received_events","type":"Organization","site_admin":false},"repo":{"id":383169156,"node_id":"MDEwOlJlcG9zaXRvcnkzODMxNjkxNTY=","name":"wargabantuwarga.com","full_name":"kawalcovid19/wargabantuwarga.com","private":false,"owner":{"login":"kawalcovid19","id":62234891,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyMjM0ODkx","avatar_url":"https://avatars.githubusercontent.com/u/62234891?v=4","gravatar_id":"","url":"https://api.github.com/users/kawalcovid19","html_url":"https://github.com/kawalcovid19","followers_url":"https://api.github.com/users/kawalcovid19/followers","following_url":"https://api.github.com/users/kawalcovid19/following{/other_user}","gists_url":"https://api.github.com/users/kawalcovid19/gists{/gist_id}","starred_url":"https://api.github.com/users/kawalcovid19/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kawalcovid19/subscriptions","organizations_url":"https://api.github.com/users/kawalcovid19/orgs","repos_url":"https://api.github.com/users/kawalcovid19/repos","events_url":"https://api.github.com/users/kawalcovid19/events{/privacy}","received_events_url":"https://api.github.com/users/kawalcovid19/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/kawalcovid19/wargabantuwarga.com","description":"Inisiatif warga untuk berbagi informasi seputar fasilitas kesehatan dan alat kesehatan untuk COVID-19.","fork":false,"url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com","forks_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/forks","keys_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/teams","hooks_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/hooks","issue_events_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/events{/number}","events_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/events","assignees_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/assignees{/user}","branches_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/branches{/branch}","tags_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/tags","blobs_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/statuses/{sha}","languages_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/languages","stargazers_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/stargazers","contributors_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/contributors","subscribers_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/subscribers","subscription_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/subscription","commits_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/contents/{+path}","compare_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/merges","archive_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/downloads","issues_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues{/number}","pulls_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls{/number}","milestones_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/milestones{/number}","notifications_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/labels{/name}","releases_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/releases{/id}","deployments_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/deployments","created_at":"2021-07-05T14:30:01Z","updated_at":"2021-07-21T06:52:39Z","pushed_at":"2021-07-21T06:55:38Z","git_url":"git://github.com/kawalcovid19/wargabantuwarga.com.git","ssh_url":"git@github.com:kawalcovid19/wargabantuwarga.com.git","clone_url":"https://github.com/kawalcovid19/wargabantuwarga.com.git","svn_url":"https://github.com/kawalcovid19/wargabantuwarga.com","homepage":"https://www.wargabantuwarga.com","size":1660,"stargazers_count":165,"watchers_count":165,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":37,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":53,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":37,"open_issues":53,"watchers":165,"default_branch":"main"}},"base":{"label":"kawalcovid19:main","ref":"main","sha":"551bdf55e8de6ee5c39bdb9de1838304fef8a6b8","user":{"login":"kawalcovid19","id":62234891,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyMjM0ODkx","avatar_url":"https://avatars.githubusercontent.com/u/62234891?v=4","gravatar_id":"","url":"https://api.github.com/users/kawalcovid19","html_url":"https://github.com/kawalcovid19","followers_url":"https://api.github.com/users/kawalcovid19/followers","following_url":"https://api.github.com/users/kawalcovid19/following{/other_user}","gists_url":"https://api.github.com/users/kawalcovid19/gists{/gist_id}","starred_url":"https://api.github.com/users/kawalcovid19/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kawalcovid19/subscriptions","organizations_url":"https://api.github.com/users/kawalcovid19/orgs","repos_url":"https://api.github.com/users/kawalcovid19/repos","events_url":"https://api.github.com/users/kawalcovid19/events{/privacy}","received_events_url":"https://api.github.com/users/kawalcovid19/received_events","type":"Organization","site_admin":false},"repo":{"id":383169156,"node_id":"MDEwOlJlcG9zaXRvcnkzODMxNjkxNTY=","name":"wargabantuwarga.com","full_name":"kawalcovid19/wargabantuwarga.com","private":false,"owner":{"login":"kawalcovid19","id":62234891,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyMjM0ODkx","avatar_url":"https://avatars.githubusercontent.com/u/62234891?v=4","gravatar_id":"","url":"https://api.github.com/users/kawalcovid19","html_url":"https://github.com/kawalcovid19","followers_url":"https://api.github.com/users/kawalcovid19/followers","following_url":"https://api.github.com/users/kawalcovid19/following{/other_user}","gists_url":"https://api.github.com/users/kawalcovid19/gists{/gist_id}","starred_url":"https://api.github.com/users/kawalcovid19/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kawalcovid19/subscriptions","organizations_url":"https://api.github.com/users/kawalcovid19/orgs","repos_url":"https://api.github.com/users/kawalcovid19/repos","events_url":"https://api.github.com/users/kawalcovid19/events{/privacy}","received_events_url":"https://api.github.com/users/kawalcovid19/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/kawalcovid19/wargabantuwarga.com","description":"Inisiatif warga untuk berbagi informasi seputar fasilitas kesehatan dan alat kesehatan untuk COVID-19.","fork":false,"url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com","forks_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/forks","keys_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/teams","hooks_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/hooks","issue_events_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/events{/number}","events_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/events","assignees_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/assignees{/user}","branches_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/branches{/branch}","tags_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/tags","blobs_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/statuses/{sha}","languages_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/languages","stargazers_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/stargazers","contributors_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/contributors","subscribers_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/subscribers","subscription_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/subscription","commits_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/contents/{+path}","compare_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/merges","archive_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/downloads","issues_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues{/number}","pulls_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls{/number}","milestones_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/milestones{/number}","notifications_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/labels{/name}","releases_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/releases{/id}","deployments_url":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/deployments","created_at":"2021-07-05T14:30:01Z","updated_at":"2021-07-21T06:52:39Z","pushed_at":"2021-07-21T06:55:38Z","git_url":"git://github.com/kawalcovid19/wargabantuwarga.com.git","ssh_url":"git@github.com:kawalcovid19/wargabantuwarga.com.git","clone_url":"https://github.com/kawalcovid19/wargabantuwarga.com.git","svn_url":"https://github.com/kawalcovid19/wargabantuwarga.com","homepage":"https://www.wargabantuwarga.com","size":1660,"stargazers_count":165,"watchers_count":165,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":37,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":53,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":37,"open_issues":53,"watchers":165,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239"},"html":{"href":"https://github.com/kawalcovid19/wargabantuwarga.com/pull/239"},"issue":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/239"},"comments":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/issues/239/comments"},"review_comments":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239/comments"},"review_comment":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/pulls/239/commits"},"statuses":{"href":"https://api.github.com/repos/kawalcovid19/wargabantuwarga.com/statuses/d50f8c9f091c50f9d134975f2590a6b5dbddd99d"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":62234891,"login":"kawalcovid19","gravatar_id":"","url":"https://api.github.com/orgs/kawalcovid19","avatar_url":"https://avatars.githubusercontent.com/u/62234891?"}} +{"id":"17244792585","type":"CreateEvent","actor":{"id":11707729,"login":"gabrieldonadel","display_login":"gabrieldonadel","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","avatar_url":"https://avatars.githubusercontent.com/u/11707729?"},"repo":{"id":387916158,"name":"gabrieldonadel/pull-requests-limits","url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits"},"payload":{"ref":"4636","ref_type":"branch","master_branch":"master","description":"Testing Github Pull requests limits","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792586","type":"WatchEvent","actor":{"id":54252308,"login":"bfan6751","display_login":"bfan6751","gravatar_id":"","url":"https://api.github.com/users/bfan6751","avatar_url":"https://avatars.githubusercontent.com/u/54252308?"},"repo":{"id":280608729,"name":"UZ-SLAMLab/ORB_SLAM3","url":"https://api.github.com/repos/UZ-SLAMLab/ORB_SLAM3"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":25055183,"login":"UZ-SLAMLab","gravatar_id":"","url":"https://api.github.com/orgs/UZ-SLAMLab","avatar_url":"https://avatars.githubusercontent.com/u/25055183?"}} +{"id":"17244792588","type":"PushEvent","actor":{"id":85989057,"login":"internetzbot","display_login":"internetzbot","gravatar_id":"","url":"https://api.github.com/users/internetzbot","avatar_url":"https://avatars.githubusercontent.com/u/85989057?"},"repo":{"id":377260146,"name":"internetztube/jaukerl-ooe-archive","url":"https://api.github.com/repos/internetztube/jaukerl-ooe-archive"},"payload":{"push_id":7561706523,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"a3b1abfd5e52cb7c5993c1a484dd8c851d73bf26","before":"9c7a65b8a9804d337f9bf30e1f07cbd40bab37fd","commits":[{"sha":"a3b1abfd5e52cb7c5993c1a484dd8c851d73bf26","author":{"name":"internetzbot","email":"c5d6e309f28eeb1bf280506bcf24a90da3518803@users.noreply.github.com"},"message":"Add 2021-07-21T07:00:00+00:00.json.","distinct":true,"url":"https://api.github.com/repos/internetztube/jaukerl-ooe-archive/commits/a3b1abfd5e52cb7c5993c1a484dd8c851d73bf26"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792592","type":"PushEvent","actor":{"id":3379460,"login":"beanslee2012","display_login":"beanslee2012","gravatar_id":"","url":"https://api.github.com/users/beanslee2012","avatar_url":"https://avatars.githubusercontent.com/u/3379460?"},"repo":{"id":215003385,"name":"beanslee2012/games","url":"https://api.github.com/repos/beanslee2012/games"},"payload":{"push_id":7561706526,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"d597a1ea1c79280206699e944c9b1dd315585ded","before":"c07ffe667a2cf2f0497cc701f27ec062b7f9150d","commits":[{"sha":"d597a1ea1c79280206699e944c9b1dd315585ded","author":{"name":"beanslee2012","email":"9aadcc12fe6ab8c01e6245204a102e9e34317220@gmail.com"},"message":"daily update","distinct":true,"url":"https://api.github.com/repos/beanslee2012/games/commits/d597a1ea1c79280206699e944c9b1dd315585ded"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792594","type":"IssueCommentEvent","actor":{"id":3520788,"login":"arnosthavelka","display_login":"arnosthavelka","gravatar_id":"","url":"https://api.github.com/users/arnosthavelka","avatar_url":"https://avatars.githubusercontent.com/u/3520788?"},"repo":{"id":80031573,"name":"arnosthavelka/junit-poc","url":"https://api.github.com/repos/arnosthavelka/junit-poc"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37","repository_url":"https://api.github.com/repos/arnosthavelka/junit-poc","labels_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37/labels{/name}","comments_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37/comments","events_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37/events","html_url":"https://github.com/arnosthavelka/junit-poc/issues/37","id":664238030,"node_id":"MDU6SXNzdWU2NjQyMzgwMzA=","number":37,"title":"Maven profile for JDK previews","user":{"login":"arnosthavelka","id":3520788,"node_id":"MDQ6VXNlcjM1MjA3ODg=","avatar_url":"https://avatars.githubusercontent.com/u/3520788?v=4","gravatar_id":"","url":"https://api.github.com/users/arnosthavelka","html_url":"https://github.com/arnosthavelka","followers_url":"https://api.github.com/users/arnosthavelka/followers","following_url":"https://api.github.com/users/arnosthavelka/following{/other_user}","gists_url":"https://api.github.com/users/arnosthavelka/gists{/gist_id}","starred_url":"https://api.github.com/users/arnosthavelka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arnosthavelka/subscriptions","organizations_url":"https://api.github.com/users/arnosthavelka/orgs","repos_url":"https://api.github.com/users/arnosthavelka/repos","events_url":"https://api.github.com/users/arnosthavelka/events{/privacy}","received_events_url":"https://api.github.com/users/arnosthavelka/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2020-07-23T06:40:56Z","updated_at":"2021-07-21T07:00:05Z","closed_at":"2021-07-21T07:00:05Z","author_association":"OWNER","active_lock_reason":null,"body":"The preview features are enabled by flag `--enable-preview`. This option has to be added to these maven plugins:\r\n- `maven-compiler-plugin`\r\n- `maven-surefire-plugin`\r\n- `maven-failsafe-plugin`","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/comments/883942818","html_url":"https://github.com/arnosthavelka/junit-poc/issues/37#issuecomment-883942818","issue_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37","id":883942818,"node_id":"IC_kwDOBMUvVc40r-Wi","user":{"login":"arnosthavelka","id":3520788,"node_id":"MDQ6VXNlcjM1MjA3ODg=","avatar_url":"https://avatars.githubusercontent.com/u/3520788?v=4","gravatar_id":"","url":"https://api.github.com/users/arnosthavelka","html_url":"https://github.com/arnosthavelka","followers_url":"https://api.github.com/users/arnosthavelka/followers","following_url":"https://api.github.com/users/arnosthavelka/following{/other_user}","gists_url":"https://api.github.com/users/arnosthavelka/gists{/gist_id}","starred_url":"https://api.github.com/users/arnosthavelka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arnosthavelka/subscriptions","organizations_url":"https://api.github.com/users/arnosthavelka/orgs","repos_url":"https://api.github.com/users/arnosthavelka/repos","events_url":"https://api.github.com/users/arnosthavelka/events{/privacy}","received_events_url":"https://api.github.com/users/arnosthavelka/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","author_association":"OWNER","body":"The preview features were removed as it complicates JDK upgrades.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792599","type":"PushEvent","actor":{"id":82876799,"login":"hvaish01","display_login":"hvaish01","gravatar_id":"","url":"https://api.github.com/users/hvaish01","avatar_url":"https://avatars.githubusercontent.com/u/82876799?"},"repo":{"id":359772248,"name":"hvaish01/StixBundles","url":"https://api.github.com/repos/hvaish01/StixBundles"},"payload":{"push_id":7561706531,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"9c83fb7bf31c43b73892fd80a90cc607adf902ca","before":"48f098644e5efee0604c33496b7d10ec32444853","commits":[{"sha":"9c83fb7bf31c43b73892fd80a90cc607adf902ca","author":{"name":"hvaish01","email":"2afd248fb67ac19cdeb0148e32ca7ee71c30c76d@users.noreply.github.com"},"message":"Updated at 12:00:04 21-07-2021","distinct":true,"url":"https://api.github.com/repos/hvaish01/StixBundles/commits/9c83fb7bf31c43b73892fd80a90cc607adf902ca"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792602","type":"PullRequestReviewEvent","actor":{"id":12186261,"login":"chengtbf","display_login":"chengtbf","gravatar_id":"","url":"https://api.github.com/users/chengtbf","avatar_url":"https://avatars.githubusercontent.com/u/12186261?"},"repo":{"id":81634683,"name":"Oneflow-Inc/oneflow","url":"https://api.github.com/repos/Oneflow-Inc/oneflow"},"payload":{"action":"created","review":{"id":711317403,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3NDAz","user":{"login":"chengtbf","id":12186261,"node_id":"MDQ6VXNlcjEyMTg2MjYx","avatar_url":"https://avatars.githubusercontent.com/u/12186261?v=4","gravatar_id":"","url":"https://api.github.com/users/chengtbf","html_url":"https://github.com/chengtbf","followers_url":"https://api.github.com/users/chengtbf/followers","following_url":"https://api.github.com/users/chengtbf/following{/other_user}","gists_url":"https://api.github.com/users/chengtbf/gists{/gist_id}","starred_url":"https://api.github.com/users/chengtbf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chengtbf/subscriptions","organizations_url":"https://api.github.com/users/chengtbf/orgs","repos_url":"https://api.github.com/users/chengtbf/repos","events_url":"https://api.github.com/users/chengtbf/events{/privacy}","received_events_url":"https://api.github.com/users/chengtbf/received_events","type":"User","site_admin":false},"body":null,"commit_id":"fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","submitted_at":"2021-07-21T07:00:05Z","state":"commented","html_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533#pullrequestreview-711317403","pull_request_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/Oneflow-Inc/oneflow/pull/5533#pullrequestreview-711317403"},"pull_request":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533"}}},"pull_request":{"url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533","id":692075999,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyMDc1OTk5","html_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533","diff_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533.diff","patch_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533.patch","issue_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533","number":5533,"state":"open","locked":false,"title":"Fea/nn graph/optimizer","user":{"login":"strint","id":3468789,"node_id":"MDQ6VXNlcjM0Njg3ODk=","avatar_url":"https://avatars.githubusercontent.com/u/3468789?v=4","gravatar_id":"","url":"https://api.github.com/users/strint","html_url":"https://github.com/strint","followers_url":"https://api.github.com/users/strint/followers","following_url":"https://api.github.com/users/strint/following{/other_user}","gists_url":"https://api.github.com/users/strint/gists{/gist_id}","starred_url":"https://api.github.com/users/strint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/strint/subscriptions","organizations_url":"https://api.github.com/users/strint/orgs","repos_url":"https://api.github.com/users/strint/repos","events_url":"https://api.github.com/users/strint/events{/privacy}","received_events_url":"https://api.github.com/users/strint/received_events","type":"User","site_admin":false},"body":"- [x] add lazy optimizer\r\n- [x] add sgd optimizer\r\n- [x] eager tensor.backward() to lazy add_loss(tensor)\r\n\r\nsupprot l2:在adam + adamw optimizer的pr中处理,已经整理好了思路。\r\n\r\ng.config.proto\r\n```\r\njob_conf {\r\n job_name: \"CustomGraph0_0\"\r\n train_conf {\r\n optimizer_conf {\r\n variable_op_names: \"m.para0\" # variable op_name\r\n variable_op_names: \"m.para1\"\r\n base_learning_rate: 0.10000000149011612 # lr\r\n momentum_conf { # momentum\r\n beta: 0.20000000298023224 # beta\r\n }\r\n }\r\n optimizer_conf { # multi optimizer\r\n variable_op_names: \"m.para3\"\r\n base_learning_rate: 0.10000000149011612\r\n momentum_conf {\r\n beta: 0.20000000298023224\r\n }\r\n }\r\n optimizer_conf {\r\n variable_op_names: \"m.para4\"\r\n base_learning_rate: 0.10000000149011612\r\n momentum_conf {\r\n beta: 0.20000000298023224\r\n }\r\n }\r\n loss_lbn: \"input_0/out\" # losss\r\n loss_scale_factor: 0.30000001192092896 # scale\r\n }\r\n}\r\n```","created_at":"2021-07-18T11:47:06Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3bd4b8cfefed1200a2bc0de3654eddac0a9b0dd6","assignee":null,"assignees":[],"requested_reviewers":[{"login":"leaves-zwx","id":7133477,"node_id":"MDQ6VXNlcjcxMzM0Nzc=","avatar_url":"https://avatars.githubusercontent.com/u/7133477?v=4","gravatar_id":"","url":"https://api.github.com/users/leaves-zwx","html_url":"https://github.com/leaves-zwx","followers_url":"https://api.github.com/users/leaves-zwx/followers","following_url":"https://api.github.com/users/leaves-zwx/following{/other_user}","gists_url":"https://api.github.com/users/leaves-zwx/gists{/gist_id}","starred_url":"https://api.github.com/users/leaves-zwx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leaves-zwx/subscriptions","organizations_url":"https://api.github.com/users/leaves-zwx/orgs","repos_url":"https://api.github.com/users/leaves-zwx/repos","events_url":"https://api.github.com/users/leaves-zwx/events{/privacy}","received_events_url":"https://api.github.com/users/leaves-zwx/received_events","type":"User","site_admin":false},{"login":"wyg1997","id":39889784,"node_id":"MDQ6VXNlcjM5ODg5Nzg0","avatar_url":"https://avatars.githubusercontent.com/u/39889784?v=4","gravatar_id":"","url":"https://api.github.com/users/wyg1997","html_url":"https://github.com/wyg1997","followers_url":"https://api.github.com/users/wyg1997/followers","following_url":"https://api.github.com/users/wyg1997/following{/other_user}","gists_url":"https://api.github.com/users/wyg1997/gists{/gist_id}","starred_url":"https://api.github.com/users/wyg1997/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wyg1997/subscriptions","organizations_url":"https://api.github.com/users/wyg1997/orgs","repos_url":"https://api.github.com/users/wyg1997/repos","events_url":"https://api.github.com/users/wyg1997/events{/privacy}","received_events_url":"https://api.github.com/users/wyg1997/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":857671186,"node_id":"MDU6TGFiZWw4NTc2NzExODY=","url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels/feature","name":"feature","color":"b127e8","default":false,"description":""},{"id":2424339898,"node_id":"MDU6TGFiZWwyNDI0MzM5ODk4","url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels/system","name":"system","color":"26d6ad","default":false,"description":""}],"milestone":{"url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones/7","html_url":"https://github.com/Oneflow-Inc/oneflow/milestone/7","labels_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones/7/labels","id":6786003,"node_id":"MDk6TWlsZXN0b25lNjc4NjAwMw==","number":7,"title":"v0.5.0","description":"The first anniversary of open source of OneFlow.\r\n\r\nEager new features:\r\n- Support Eager **Mirror** paralllel strategy in multi-devices\r\n- Support Eager **Consistent** paralllel strategy in multi-devices\r\n- nn.Module add a lot of ops\r\n\r\nFully compatible with pytorch interface, just `import oneflow as torch`.\r\n- flow.experimental.xx -> flow.xx\r\n- Old versions of interfaces (like tensorflow 1.x) will no longer be supported\r\n- Run OneFlow Lazy Consistent distribute training with 1.0 interface API (flow.nn.Graph)\r\n","creator":{"login":"chengtbf","id":12186261,"node_id":"MDQ6VXNlcjEyMTg2MjYx","avatar_url":"https://avatars.githubusercontent.com/u/12186261?v=4","gravatar_id":"","url":"https://api.github.com/users/chengtbf","html_url":"https://github.com/chengtbf","followers_url":"https://api.github.com/users/chengtbf/followers","following_url":"https://api.github.com/users/chengtbf/following{/other_user}","gists_url":"https://api.github.com/users/chengtbf/gists{/gist_id}","starred_url":"https://api.github.com/users/chengtbf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chengtbf/subscriptions","organizations_url":"https://api.github.com/users/chengtbf/orgs","repos_url":"https://api.github.com/users/chengtbf/repos","events_url":"https://api.github.com/users/chengtbf/events{/privacy}","received_events_url":"https://api.github.com/users/chengtbf/received_events","type":"User","site_admin":false},"open_issues":2,"closed_issues":16,"state":"open","created_at":"2021-05-24T07:59:37Z","updated_at":"2021-07-20T11:14:50Z","due_on":"2021-07-31T07:00:00Z","closed_at":null},"draft":false,"commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/commits","review_comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/comments","review_comment_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533/comments","statuses_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","head":{"label":"Oneflow-Inc:fea/nn_graph/optimizer","ref":"fea/nn_graph/optimizer","sha":"fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","user":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"repo":{"id":81634683,"node_id":"MDEwOlJlcG9zaXRvcnk4MTYzNDY4Mw==","name":"oneflow","full_name":"Oneflow-Inc/oneflow","private":false,"owner":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Oneflow-Inc/oneflow","description":"OneFlow is a performance-centered and open-source deep learning framework.","fork":false,"url":"https://api.github.com/repos/Oneflow-Inc/oneflow","forks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/forks","keys_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/teams","hooks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/hooks","issue_events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/events{/number}","events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/events","assignees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/assignees{/user}","branches_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/branches{/branch}","tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/tags","blobs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/{sha}","languages_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/languages","stargazers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/stargazers","contributors_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contributors","subscribers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscribers","subscription_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscription","commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contents/{+path}","compare_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/merges","archive_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/downloads","issues_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues{/number}","pulls_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls{/number}","milestones_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones{/number}","notifications_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels{/name}","releases_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/releases{/id}","deployments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/deployments","created_at":"2017-02-11T06:09:53Z","updated_at":"2021-07-21T03:19:28Z","pushed_at":"2021-07-21T06:58:35Z","git_url":"git://github.com/Oneflow-Inc/oneflow.git","ssh_url":"git@github.com:Oneflow-Inc/oneflow.git","clone_url":"https://github.com/Oneflow-Inc/oneflow.git","svn_url":"https://github.com/Oneflow-Inc/oneflow","homepage":"http://www.oneflow.org","size":44881,"stargazers_count":2545,"watchers_count":2545,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":284,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":261,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":284,"open_issues":261,"watchers":2545,"default_branch":"master"}},"base":{"label":"Oneflow-Inc:fea/nn_graph/forward_graph","ref":"fea/nn_graph/forward_graph","sha":"b93ad178de46e2b81f83c5c645c6989f12dabbee","user":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"repo":{"id":81634683,"node_id":"MDEwOlJlcG9zaXRvcnk4MTYzNDY4Mw==","name":"oneflow","full_name":"Oneflow-Inc/oneflow","private":false,"owner":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Oneflow-Inc/oneflow","description":"OneFlow is a performance-centered and open-source deep learning framework.","fork":false,"url":"https://api.github.com/repos/Oneflow-Inc/oneflow","forks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/forks","keys_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/teams","hooks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/hooks","issue_events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/events{/number}","events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/events","assignees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/assignees{/user}","branches_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/branches{/branch}","tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/tags","blobs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/{sha}","languages_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/languages","stargazers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/stargazers","contributors_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contributors","subscribers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscribers","subscription_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscription","commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contents/{+path}","compare_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/merges","archive_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/downloads","issues_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues{/number}","pulls_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls{/number}","milestones_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones{/number}","notifications_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels{/name}","releases_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/releases{/id}","deployments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/deployments","created_at":"2017-02-11T06:09:53Z","updated_at":"2021-07-21T03:19:28Z","pushed_at":"2021-07-21T06:58:35Z","git_url":"git://github.com/Oneflow-Inc/oneflow.git","ssh_url":"git@github.com:Oneflow-Inc/oneflow.git","clone_url":"https://github.com/Oneflow-Inc/oneflow.git","svn_url":"https://github.com/Oneflow-Inc/oneflow","homepage":"http://www.oneflow.org","size":44881,"stargazers_count":2545,"watchers_count":2545,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":284,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":261,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":284,"open_issues":261,"watchers":2545,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533"},"html":{"href":"https://github.com/Oneflow-Inc/oneflow/pull/5533"},"issue":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533"},"comments":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533/comments"},"review_comments":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/comments"},"review_comment":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/commits"},"statuses":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/fce5c08c04e97e6eb4cc8ba5b97aed34472382b7"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":24632470,"login":"Oneflow-Inc","gravatar_id":"","url":"https://api.github.com/orgs/Oneflow-Inc","avatar_url":"https://avatars.githubusercontent.com/u/24632470?"}} +{"id":"17244792607","type":"PullRequestEvent","actor":{"id":1005065,"login":"DeepDiver1975","display_login":"DeepDiver1975","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","avatar_url":"https://avatars.githubusercontent.com/u/1005065?"},"repo":{"id":386559157,"name":"DeepDiver1975/laravel-cursor-paginator","url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator"},"payload":{"action":"closed","number":6,"pull_request":{"url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/6","id":694104352,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0MzUy","html_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator/pull/6","diff_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator/pull/6.diff","patch_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator/pull/6.patch","issue_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/6","number":6,"state":"closed","locked":false,"title":"Create dependabot.yml","user":{"login":"DeepDiver1975","id":1005065,"node_id":"MDQ6VXNlcjEwMDUwNjU=","avatar_url":"https://avatars.githubusercontent.com/u/1005065?v=4","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","html_url":"https://github.com/DeepDiver1975","followers_url":"https://api.github.com/users/DeepDiver1975/followers","following_url":"https://api.github.com/users/DeepDiver1975/following{/other_user}","gists_url":"https://api.github.com/users/DeepDiver1975/gists{/gist_id}","starred_url":"https://api.github.com/users/DeepDiver1975/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DeepDiver1975/subscriptions","organizations_url":"https://api.github.com/users/DeepDiver1975/orgs","repos_url":"https://api.github.com/users/DeepDiver1975/repos","events_url":"https://api.github.com/users/DeepDiver1975/events{/privacy}","received_events_url":"https://api.github.com/users/DeepDiver1975/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T06:59:02Z","updated_at":"2021-07-21T07:00:05Z","closed_at":"2021-07-21T07:00:05Z","merged_at":"2021-07-21T07:00:05Z","merge_commit_sha":"968ae80b6cb4f587eddbd69c4e8cbf7d0ac38ecb","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/6/commits","review_comments_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/6/comments","review_comment_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/comments{/number}","comments_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/6/comments","statuses_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/statuses/76945b443f10b16f5b98af9665023bcef65787cb","head":{"label":"DeepDiver1975:feat/dependabot","ref":"feat/dependabot","sha":"76945b443f10b16f5b98af9665023bcef65787cb","user":{"login":"DeepDiver1975","id":1005065,"node_id":"MDQ6VXNlcjEwMDUwNjU=","avatar_url":"https://avatars.githubusercontent.com/u/1005065?v=4","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","html_url":"https://github.com/DeepDiver1975","followers_url":"https://api.github.com/users/DeepDiver1975/followers","following_url":"https://api.github.com/users/DeepDiver1975/following{/other_user}","gists_url":"https://api.github.com/users/DeepDiver1975/gists{/gist_id}","starred_url":"https://api.github.com/users/DeepDiver1975/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DeepDiver1975/subscriptions","organizations_url":"https://api.github.com/users/DeepDiver1975/orgs","repos_url":"https://api.github.com/users/DeepDiver1975/repos","events_url":"https://api.github.com/users/DeepDiver1975/events{/privacy}","received_events_url":"https://api.github.com/users/DeepDiver1975/received_events","type":"User","site_admin":false},"repo":{"id":386559157,"node_id":"MDEwOlJlcG9zaXRvcnkzODY1NTkxNTc=","name":"laravel-cursor-paginator","full_name":"DeepDiver1975/laravel-cursor-paginator","private":false,"owner":{"login":"DeepDiver1975","id":1005065,"node_id":"MDQ6VXNlcjEwMDUwNjU=","avatar_url":"https://avatars.githubusercontent.com/u/1005065?v=4","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","html_url":"https://github.com/DeepDiver1975","followers_url":"https://api.github.com/users/DeepDiver1975/followers","following_url":"https://api.github.com/users/DeepDiver1975/following{/other_user}","gists_url":"https://api.github.com/users/DeepDiver1975/gists{/gist_id}","starred_url":"https://api.github.com/users/DeepDiver1975/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DeepDiver1975/subscriptions","organizations_url":"https://api.github.com/users/DeepDiver1975/orgs","repos_url":"https://api.github.com/users/DeepDiver1975/repos","events_url":"https://api.github.com/users/DeepDiver1975/events{/privacy}","received_events_url":"https://api.github.com/users/DeepDiver1975/received_events","type":"User","site_admin":false},"html_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator","description":"Cursor pagination for Laravel","fork":true,"url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator","forks_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/forks","keys_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/teams","hooks_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/hooks","issue_events_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/events{/number}","events_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/events","assignees_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/assignees{/user}","branches_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/branches{/branch}","tags_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/tags","blobs_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/refs{/sha}","trees_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/statuses/{sha}","languages_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/languages","stargazers_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/stargazers","contributors_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/contributors","subscribers_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/subscribers","subscription_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/subscription","commits_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/commits{/sha}","git_commits_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/commits{/sha}","comments_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/comments{/number}","issue_comment_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/comments{/number}","contents_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/contents/{+path}","compare_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/merges","archive_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/downloads","issues_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues{/number}","pulls_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls{/number}","milestones_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/milestones{/number}","notifications_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/labels{/name}","releases_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/releases{/id}","deployments_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/deployments","created_at":"2021-07-16T08:04:17Z","updated_at":"2021-07-16T10:15:05Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/DeepDiver1975/laravel-cursor-paginator.git","ssh_url":"git@github.com:DeepDiver1975/laravel-cursor-paginator.git","clone_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator.git","svn_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator","homepage":null,"size":197,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"DeepDiver1975:master","ref":"master","sha":"9d3751c0a369ca0f6435afb03eba7e175f83041a","user":{"login":"DeepDiver1975","id":1005065,"node_id":"MDQ6VXNlcjEwMDUwNjU=","avatar_url":"https://avatars.githubusercontent.com/u/1005065?v=4","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","html_url":"https://github.com/DeepDiver1975","followers_url":"https://api.github.com/users/DeepDiver1975/followers","following_url":"https://api.github.com/users/DeepDiver1975/following{/other_user}","gists_url":"https://api.github.com/users/DeepDiver1975/gists{/gist_id}","starred_url":"https://api.github.com/users/DeepDiver1975/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DeepDiver1975/subscriptions","organizations_url":"https://api.github.com/users/DeepDiver1975/orgs","repos_url":"https://api.github.com/users/DeepDiver1975/repos","events_url":"https://api.github.com/users/DeepDiver1975/events{/privacy}","received_events_url":"https://api.github.com/users/DeepDiver1975/received_events","type":"User","site_admin":false},"repo":{"id":386559157,"node_id":"MDEwOlJlcG9zaXRvcnkzODY1NTkxNTc=","name":"laravel-cursor-paginator","full_name":"DeepDiver1975/laravel-cursor-paginator","private":false,"owner":{"login":"DeepDiver1975","id":1005065,"node_id":"MDQ6VXNlcjEwMDUwNjU=","avatar_url":"https://avatars.githubusercontent.com/u/1005065?v=4","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","html_url":"https://github.com/DeepDiver1975","followers_url":"https://api.github.com/users/DeepDiver1975/followers","following_url":"https://api.github.com/users/DeepDiver1975/following{/other_user}","gists_url":"https://api.github.com/users/DeepDiver1975/gists{/gist_id}","starred_url":"https://api.github.com/users/DeepDiver1975/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DeepDiver1975/subscriptions","organizations_url":"https://api.github.com/users/DeepDiver1975/orgs","repos_url":"https://api.github.com/users/DeepDiver1975/repos","events_url":"https://api.github.com/users/DeepDiver1975/events{/privacy}","received_events_url":"https://api.github.com/users/DeepDiver1975/received_events","type":"User","site_admin":false},"html_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator","description":"Cursor pagination for Laravel","fork":true,"url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator","forks_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/forks","keys_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/teams","hooks_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/hooks","issue_events_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/events{/number}","events_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/events","assignees_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/assignees{/user}","branches_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/branches{/branch}","tags_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/tags","blobs_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/refs{/sha}","trees_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/statuses/{sha}","languages_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/languages","stargazers_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/stargazers","contributors_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/contributors","subscribers_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/subscribers","subscription_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/subscription","commits_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/commits{/sha}","git_commits_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/git/commits{/sha}","comments_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/comments{/number}","issue_comment_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/comments{/number}","contents_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/contents/{+path}","compare_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/merges","archive_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/downloads","issues_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues{/number}","pulls_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls{/number}","milestones_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/milestones{/number}","notifications_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/labels{/name}","releases_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/releases{/id}","deployments_url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/deployments","created_at":"2021-07-16T08:04:17Z","updated_at":"2021-07-16T10:15:05Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/DeepDiver1975/laravel-cursor-paginator.git","ssh_url":"git@github.com:DeepDiver1975/laravel-cursor-paginator.git","clone_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator.git","svn_url":"https://github.com/DeepDiver1975/laravel-cursor-paginator","homepage":null,"size":197,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/6"},"html":{"href":"https://github.com/DeepDiver1975/laravel-cursor-paginator/pull/6"},"issue":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/6"},"comments":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/issues/6/comments"},"review_comments":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/6/comments"},"review_comment":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/pulls/6/commits"},"statuses":{"href":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator/statuses/76945b443f10b16f5b98af9665023bcef65787cb"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"DeepDiver1975","id":1005065,"node_id":"MDQ6VXNlcjEwMDUwNjU=","avatar_url":"https://avatars.githubusercontent.com/u/1005065?v=4","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","html_url":"https://github.com/DeepDiver1975","followers_url":"https://api.github.com/users/DeepDiver1975/followers","following_url":"https://api.github.com/users/DeepDiver1975/following{/other_user}","gists_url":"https://api.github.com/users/DeepDiver1975/gists{/gist_id}","starred_url":"https://api.github.com/users/DeepDiver1975/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DeepDiver1975/subscriptions","organizations_url":"https://api.github.com/users/DeepDiver1975/orgs","repos_url":"https://api.github.com/users/DeepDiver1975/repos","events_url":"https://api.github.com/users/DeepDiver1975/events{/privacy}","received_events_url":"https://api.github.com/users/DeepDiver1975/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":6,"deletions":0,"changed_files":1}},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792608","type":"PullRequestReviewCommentEvent","actor":{"id":12186261,"login":"chengtbf","display_login":"chengtbf","gravatar_id":"","url":"https://api.github.com/users/chengtbf","avatar_url":"https://avatars.githubusercontent.com/u/12186261?"},"repo":{"id":81634683,"name":"Oneflow-Inc/oneflow","url":"https://api.github.com/repos/Oneflow-Inc/oneflow"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/comments/673711151","pull_request_review_id":711317403,"id":673711151,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3MzcxMTE1MQ==","diff_hunk":"@@ -189,6 +192,9 @@ class MirroredTensor final : public TensorIf,\n impl_->set_autograd_meta(autograd_meta);\n }\n \n+ // Tensor.backward() to lazy add_loss(Tensor)\n+ Maybe add_as_lazy_loss() const override { OF_UNIMPLEMENTED(); }","path":"oneflow/core/framework/tensor.h","position":15,"original_position":15,"commit_id":"fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","original_commit_id":"fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","user":{"login":"chengtbf","id":12186261,"node_id":"MDQ6VXNlcjEyMTg2MjYx","avatar_url":"https://avatars.githubusercontent.com/u/12186261?v=4","gravatar_id":"","url":"https://api.github.com/users/chengtbf","html_url":"https://github.com/chengtbf","followers_url":"https://api.github.com/users/chengtbf/followers","following_url":"https://api.github.com/users/chengtbf/following{/other_user}","gists_url":"https://api.github.com/users/chengtbf/gists{/gist_id}","starred_url":"https://api.github.com/users/chengtbf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chengtbf/subscriptions","organizations_url":"https://api.github.com/users/chengtbf/orgs","repos_url":"https://api.github.com/users/chengtbf/repos","events_url":"https://api.github.com/users/chengtbf/events{/privacy}","received_events_url":"https://api.github.com/users/chengtbf/received_events","type":"User","site_admin":false},"body":"这里没必要是只支持 Consistent Tensor 吧。Lazy 里的 Blob 是 Consistent 的,但我想 module 里的 LazyTensor 仍然允许是 Local 的,只是我 add op 的时候处理成 Consistent 就行了","created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","html_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533#discussion_r673711151","pull_request_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/comments/673711151"},"html":{"href":"https://github.com/Oneflow-Inc/oneflow/pull/5533#discussion_r673711151"},"pull_request":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533"}},"start_line":null,"original_start_line":null,"start_side":null,"line":196,"original_line":196,"side":"RIGHT"},"pull_request":{"url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533","id":692075999,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyMDc1OTk5","html_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533","diff_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533.diff","patch_url":"https://github.com/Oneflow-Inc/oneflow/pull/5533.patch","issue_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533","number":5533,"state":"open","locked":false,"title":"Fea/nn graph/optimizer","user":{"login":"strint","id":3468789,"node_id":"MDQ6VXNlcjM0Njg3ODk=","avatar_url":"https://avatars.githubusercontent.com/u/3468789?v=4","gravatar_id":"","url":"https://api.github.com/users/strint","html_url":"https://github.com/strint","followers_url":"https://api.github.com/users/strint/followers","following_url":"https://api.github.com/users/strint/following{/other_user}","gists_url":"https://api.github.com/users/strint/gists{/gist_id}","starred_url":"https://api.github.com/users/strint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/strint/subscriptions","organizations_url":"https://api.github.com/users/strint/orgs","repos_url":"https://api.github.com/users/strint/repos","events_url":"https://api.github.com/users/strint/events{/privacy}","received_events_url":"https://api.github.com/users/strint/received_events","type":"User","site_admin":false},"body":"- [x] add lazy optimizer\r\n- [x] add sgd optimizer\r\n- [x] eager tensor.backward() to lazy add_loss(tensor)\r\n\r\nsupprot l2:在adam + adamw optimizer的pr中处理,已经整理好了思路。\r\n\r\ng.config.proto\r\n```\r\njob_conf {\r\n job_name: \"CustomGraph0_0\"\r\n train_conf {\r\n optimizer_conf {\r\n variable_op_names: \"m.para0\" # variable op_name\r\n variable_op_names: \"m.para1\"\r\n base_learning_rate: 0.10000000149011612 # lr\r\n momentum_conf { # momentum\r\n beta: 0.20000000298023224 # beta\r\n }\r\n }\r\n optimizer_conf { # multi optimizer\r\n variable_op_names: \"m.para3\"\r\n base_learning_rate: 0.10000000149011612\r\n momentum_conf {\r\n beta: 0.20000000298023224\r\n }\r\n }\r\n optimizer_conf {\r\n variable_op_names: \"m.para4\"\r\n base_learning_rate: 0.10000000149011612\r\n momentum_conf {\r\n beta: 0.20000000298023224\r\n }\r\n }\r\n loss_lbn: \"input_0/out\" # losss\r\n loss_scale_factor: 0.30000001192092896 # scale\r\n }\r\n}\r\n```","created_at":"2021-07-18T11:47:06Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3bd4b8cfefed1200a2bc0de3654eddac0a9b0dd6","assignee":null,"assignees":[],"requested_reviewers":[{"login":"leaves-zwx","id":7133477,"node_id":"MDQ6VXNlcjcxMzM0Nzc=","avatar_url":"https://avatars.githubusercontent.com/u/7133477?v=4","gravatar_id":"","url":"https://api.github.com/users/leaves-zwx","html_url":"https://github.com/leaves-zwx","followers_url":"https://api.github.com/users/leaves-zwx/followers","following_url":"https://api.github.com/users/leaves-zwx/following{/other_user}","gists_url":"https://api.github.com/users/leaves-zwx/gists{/gist_id}","starred_url":"https://api.github.com/users/leaves-zwx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leaves-zwx/subscriptions","organizations_url":"https://api.github.com/users/leaves-zwx/orgs","repos_url":"https://api.github.com/users/leaves-zwx/repos","events_url":"https://api.github.com/users/leaves-zwx/events{/privacy}","received_events_url":"https://api.github.com/users/leaves-zwx/received_events","type":"User","site_admin":false},{"login":"wyg1997","id":39889784,"node_id":"MDQ6VXNlcjM5ODg5Nzg0","avatar_url":"https://avatars.githubusercontent.com/u/39889784?v=4","gravatar_id":"","url":"https://api.github.com/users/wyg1997","html_url":"https://github.com/wyg1997","followers_url":"https://api.github.com/users/wyg1997/followers","following_url":"https://api.github.com/users/wyg1997/following{/other_user}","gists_url":"https://api.github.com/users/wyg1997/gists{/gist_id}","starred_url":"https://api.github.com/users/wyg1997/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wyg1997/subscriptions","organizations_url":"https://api.github.com/users/wyg1997/orgs","repos_url":"https://api.github.com/users/wyg1997/repos","events_url":"https://api.github.com/users/wyg1997/events{/privacy}","received_events_url":"https://api.github.com/users/wyg1997/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":857671186,"node_id":"MDU6TGFiZWw4NTc2NzExODY=","url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels/feature","name":"feature","color":"b127e8","default":false,"description":""},{"id":2424339898,"node_id":"MDU6TGFiZWwyNDI0MzM5ODk4","url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels/system","name":"system","color":"26d6ad","default":false,"description":""}],"milestone":{"url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones/7","html_url":"https://github.com/Oneflow-Inc/oneflow/milestone/7","labels_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones/7/labels","id":6786003,"node_id":"MDk6TWlsZXN0b25lNjc4NjAwMw==","number":7,"title":"v0.5.0","description":"The first anniversary of open source of OneFlow.\r\n\r\nEager new features:\r\n- Support Eager **Mirror** paralllel strategy in multi-devices\r\n- Support Eager **Consistent** paralllel strategy in multi-devices\r\n- nn.Module add a lot of ops\r\n\r\nFully compatible with pytorch interface, just `import oneflow as torch`.\r\n- flow.experimental.xx -> flow.xx\r\n- Old versions of interfaces (like tensorflow 1.x) will no longer be supported\r\n- Run OneFlow Lazy Consistent distribute training with 1.0 interface API (flow.nn.Graph)\r\n","creator":{"login":"chengtbf","id":12186261,"node_id":"MDQ6VXNlcjEyMTg2MjYx","avatar_url":"https://avatars.githubusercontent.com/u/12186261?v=4","gravatar_id":"","url":"https://api.github.com/users/chengtbf","html_url":"https://github.com/chengtbf","followers_url":"https://api.github.com/users/chengtbf/followers","following_url":"https://api.github.com/users/chengtbf/following{/other_user}","gists_url":"https://api.github.com/users/chengtbf/gists{/gist_id}","starred_url":"https://api.github.com/users/chengtbf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chengtbf/subscriptions","organizations_url":"https://api.github.com/users/chengtbf/orgs","repos_url":"https://api.github.com/users/chengtbf/repos","events_url":"https://api.github.com/users/chengtbf/events{/privacy}","received_events_url":"https://api.github.com/users/chengtbf/received_events","type":"User","site_admin":false},"open_issues":2,"closed_issues":16,"state":"open","created_at":"2021-05-24T07:59:37Z","updated_at":"2021-07-20T11:14:50Z","due_on":"2021-07-31T07:00:00Z","closed_at":null},"draft":false,"commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/commits","review_comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/comments","review_comment_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533/comments","statuses_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","head":{"label":"Oneflow-Inc:fea/nn_graph/optimizer","ref":"fea/nn_graph/optimizer","sha":"fce5c08c04e97e6eb4cc8ba5b97aed34472382b7","user":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"repo":{"id":81634683,"node_id":"MDEwOlJlcG9zaXRvcnk4MTYzNDY4Mw==","name":"oneflow","full_name":"Oneflow-Inc/oneflow","private":false,"owner":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Oneflow-Inc/oneflow","description":"OneFlow is a performance-centered and open-source deep learning framework.","fork":false,"url":"https://api.github.com/repos/Oneflow-Inc/oneflow","forks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/forks","keys_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/teams","hooks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/hooks","issue_events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/events{/number}","events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/events","assignees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/assignees{/user}","branches_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/branches{/branch}","tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/tags","blobs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/{sha}","languages_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/languages","stargazers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/stargazers","contributors_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contributors","subscribers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscribers","subscription_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscription","commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contents/{+path}","compare_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/merges","archive_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/downloads","issues_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues{/number}","pulls_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls{/number}","milestones_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones{/number}","notifications_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels{/name}","releases_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/releases{/id}","deployments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/deployments","created_at":"2017-02-11T06:09:53Z","updated_at":"2021-07-21T03:19:28Z","pushed_at":"2021-07-21T06:58:35Z","git_url":"git://github.com/Oneflow-Inc/oneflow.git","ssh_url":"git@github.com:Oneflow-Inc/oneflow.git","clone_url":"https://github.com/Oneflow-Inc/oneflow.git","svn_url":"https://github.com/Oneflow-Inc/oneflow","homepage":"http://www.oneflow.org","size":44881,"stargazers_count":2545,"watchers_count":2545,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":284,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":261,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":284,"open_issues":261,"watchers":2545,"default_branch":"master"}},"base":{"label":"Oneflow-Inc:fea/nn_graph/forward_graph","ref":"fea/nn_graph/forward_graph","sha":"b93ad178de46e2b81f83c5c645c6989f12dabbee","user":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"repo":{"id":81634683,"node_id":"MDEwOlJlcG9zaXRvcnk4MTYzNDY4Mw==","name":"oneflow","full_name":"Oneflow-Inc/oneflow","private":false,"owner":{"login":"Oneflow-Inc","id":24632470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0NjMyNDcw","avatar_url":"https://avatars.githubusercontent.com/u/24632470?v=4","gravatar_id":"","url":"https://api.github.com/users/Oneflow-Inc","html_url":"https://github.com/Oneflow-Inc","followers_url":"https://api.github.com/users/Oneflow-Inc/followers","following_url":"https://api.github.com/users/Oneflow-Inc/following{/other_user}","gists_url":"https://api.github.com/users/Oneflow-Inc/gists{/gist_id}","starred_url":"https://api.github.com/users/Oneflow-Inc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Oneflow-Inc/subscriptions","organizations_url":"https://api.github.com/users/Oneflow-Inc/orgs","repos_url":"https://api.github.com/users/Oneflow-Inc/repos","events_url":"https://api.github.com/users/Oneflow-Inc/events{/privacy}","received_events_url":"https://api.github.com/users/Oneflow-Inc/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Oneflow-Inc/oneflow","description":"OneFlow is a performance-centered and open-source deep learning framework.","fork":false,"url":"https://api.github.com/repos/Oneflow-Inc/oneflow","forks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/forks","keys_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/teams","hooks_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/hooks","issue_events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/events{/number}","events_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/events","assignees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/assignees{/user}","branches_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/branches{/branch}","tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/tags","blobs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/{sha}","languages_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/languages","stargazers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/stargazers","contributors_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contributors","subscribers_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscribers","subscription_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/subscription","commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/contents/{+path}","compare_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/merges","archive_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/downloads","issues_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues{/number}","pulls_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls{/number}","milestones_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/milestones{/number}","notifications_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/labels{/name}","releases_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/releases{/id}","deployments_url":"https://api.github.com/repos/Oneflow-Inc/oneflow/deployments","created_at":"2017-02-11T06:09:53Z","updated_at":"2021-07-21T03:19:28Z","pushed_at":"2021-07-21T06:58:35Z","git_url":"git://github.com/Oneflow-Inc/oneflow.git","ssh_url":"git@github.com:Oneflow-Inc/oneflow.git","clone_url":"https://github.com/Oneflow-Inc/oneflow.git","svn_url":"https://github.com/Oneflow-Inc/oneflow","homepage":"http://www.oneflow.org","size":44881,"stargazers_count":2545,"watchers_count":2545,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":284,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":261,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":284,"open_issues":261,"watchers":2545,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533"},"html":{"href":"https://github.com/Oneflow-Inc/oneflow/pull/5533"},"issue":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533"},"comments":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/issues/5533/comments"},"review_comments":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/comments"},"review_comment":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/pulls/5533/commits"},"statuses":{"href":"https://api.github.com/repos/Oneflow-Inc/oneflow/statuses/fce5c08c04e97e6eb4cc8ba5b97aed34472382b7"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":24632470,"login":"Oneflow-Inc","gravatar_id":"","url":"https://api.github.com/orgs/Oneflow-Inc","avatar_url":"https://avatars.githubusercontent.com/u/24632470?"}} +{"id":"17244792612","type":"PushEvent","actor":{"id":22955941,"login":"Volodichev","display_login":"Volodichev","gravatar_id":"","url":"https://api.github.com/users/Volodichev","avatar_url":"https://avatars.githubusercontent.com/u/22955941?"},"repo":{"id":374556291,"name":"Volodichev/proxy-list","url":"https://api.github.com/repos/Volodichev/proxy-list"},"payload":{"push_id":7561706536,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"20b0d04f5f704554b8a0198a6de18175ff2af93b","before":"d88fed842c5252fff44229061355698c72c21a23","commits":[{"sha":"20b0d04f5f704554b8a0198a6de18175ff2af93b","author":{"name":"Alexander","email":"db02dbb7d5376400a8069895ae273c92e0d1f31d@gmail.com"},"message":"awm proxy 21.07.21 10:00:01","distinct":true,"url":"https://api.github.com/repos/Volodichev/proxy-list/commits/20b0d04f5f704554b8a0198a6de18175ff2af93b"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792613","type":"CreateEvent","actor":{"id":10321883,"login":"qin-guan","display_login":"qin-guan","gravatar_id":"","url":"https://api.github.com/users/qin-guan","avatar_url":"https://avatars.githubusercontent.com/u/10321883?"},"repo":{"id":387750388,"name":"sst-inc/help","url":"https://api.github.com/repos/sst-inc/help"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":51281060,"login":"sst-inc","gravatar_id":"","url":"https://api.github.com/orgs/sst-inc","avatar_url":"https://avatars.githubusercontent.com/u/51281060?"}} +{"id":"17244792615","type":"PushEvent","actor":{"id":66846932,"login":"resufer","display_login":"resufer","gravatar_id":"","url":"https://api.github.com/users/resufer","avatar_url":"https://avatars.githubusercontent.com/u/66846932?"},"repo":{"id":344154965,"name":"resufer/Miscellanea","url":"https://api.github.com/repos/resufer/Miscellanea"},"payload":{"push_id":7561706534,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f85e4c0a6013d5fff68e3a7f65f6d5097b7b8e6e","before":"5bb9253e263d5f71dbe494ea3e06497cdd733350","commits":[{"sha":"f85e4c0a6013d5fff68e3a7f65f6d5097b7b8e6e","author":{"name":"resufer","email":"598486b7100af8abb650bafeaa809e30ef0df506@users.noreply.github.com"},"message":"Create data.txt","distinct":true,"url":"https://api.github.com/repos/resufer/Miscellanea/commits/f85e4c0a6013d5fff68e3a7f65f6d5097b7b8e6e"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792617","type":"PushEvent","actor":{"id":68223916,"login":"June-Ruth","display_login":"June-Ruth","gravatar_id":"","url":"https://api.github.com/users/June-Ruth","avatar_url":"https://avatars.githubusercontent.com/u/68223916?"},"repo":{"id":368489955,"name":"June-Ruth/OC-Java-Projet8","url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8"},"payload":{"push_id":7561706541,"size":76,"distinct_size":1,"ref":"refs/heads/main","head":"c36cb8a0c99f924e94fa0c195645729b806a912d","before":"52520c99637be508e6722a9b35bf3d0c5caf3dc4","commits":[{"sha":"8a070824c6abb6930fabddaf40e8ef3a7472c063","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"add locale US to tests","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/8a070824c6abb6930fabddaf40e8ef3a7472c063"},{"sha":"b8227b6a2742d4066e7c6d7c381d72ade9a2ea0c","author":{"name":"June-Ruth","email":"a914dc489ccd9917cebd1df4d84d72f6764e162a@users.noreply.github.com"},"message":"Merge pull request #1 from June-Ruth/fix/NumberFormatException\n\nFix NumberFormatException","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/b8227b6a2742d4066e7c6d7c381d72ade9a2ea0c"},{"sha":"2021bcb4e34722a455845b549ebb7be5d3ddcb35","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"fix Concurrent ModificationException","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/2021bcb4e34722a455845b549ebb7be5d3ddcb35"},{"sha":"c177bd71c3478b3924898596cc49501636a64932","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"write all performance test step","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/c177bd71c3478b3924898596cc49501636a64932"},{"sha":"1376283922d80d6bbcfea7205b5acea6befaf3c2","author":{"name":"June-Ruth","email":"a914dc489ccd9917cebd1df4d84d72f6764e162a@users.noreply.github.com"},"message":"Merge pull request #2 from June-Ruth/fix/ConcurrentModificationException\n\nFix/concurrent modification exception","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/1376283922d80d6bbcfea7205b5acea6befaf3c2"},{"sha":"b0d56ea1d5df5f33ed57d724aad39907dd94cc7e","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"adapt syntax to Kts","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/b0d56ea1d5df5f33ed57d724aad39907dd94cc7e"},{"sha":"00a5374d67fd065b94b0d9e550ba1e2f4b7cf972","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"adapt jacoco part to kts","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/00a5374d67fd065b94b0d9e550ba1e2f4b7cf972"},{"sha":"de322de12ec8a5fe7b48139643be282522b2803f","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"upgrade gradle to 7.0.2","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/de322de12ec8a5fe7b48139643be282522b2803f"},{"sha":"cb4cb16384a68a501bd184d0e2183940cfa8d8fe","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"upgrade spring and junit","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/cb4cb16384a68a501bd184d0e2183940cfa8d8fe"},{"sha":"45f2d819453dd514d90f1b48c36299788e96e71c","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"migrate to Kts","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/45f2d819453dd514d90f1b48c36299788e96e71c"},{"sha":"9209c3ed094b5a50a8d31e302258a9ef9bbf88d0","author":{"name":"June-Ruth","email":"a914dc489ccd9917cebd1df4d84d72f6764e162a@users.noreply.github.com"},"message":"Merge pull request #3 from June-Ruth/feature/GradleKtsMigration\n\nGradle Migration to Kotlin Script","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/9209c3ed094b5a50a8d31e302258a9ef9bbf88d0"},{"sha":"eeb04f844f2455291e86fe50e4e497c1e8513923","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"create interface","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/eeb04f844f2455291e86fe50e4e497c1e8513923"},{"sha":"8c1170a7cc29fb106f1a159a732a9dabc3e325b9","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"adapt test to interface","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/8c1170a7cc29fb106f1a159a732a9dabc3e325b9"},{"sha":"9a712ef327e15d2dcf95ec773bfcb7a267ac79a1","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"clean model","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/9a712ef327e15d2dcf95ec773bfcb7a267ac79a1"},{"sha":"2f4d0d3d568f823da5710fef901ffd0a327a52a8","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"move methode from model to service","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/2f4d0d3d568f823da5710fef901ffd0a327a52a8"},{"sha":"98a5ac37f592a87eb38d4e08e9005ed9f38b533a","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"isolate test method","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/98a5ac37f592a87eb38d4e08e9005ed9f38b533a"},{"sha":"2d7ae9163ea9a90de712d3a30f4fb935b7a7adec","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"seperate controller with REST","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/2d7ae9163ea9a90de712d3a30f4fb935b7a7adec"},{"sha":"98998a7f1bb8df6650dd21558e3119467fcfb3e8","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"change tracker to be a runnable","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/98998a7f1bb8df6650dd21558e3119467fcfb3e8"},{"sha":"53114dc5387fef1f9db8a865eee73c721198c874","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"correct Tracker","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/53114dc5387fef1f9db8a865eee73c721198c874"},{"sha":"71cfe027d39ab6430d942649a724a31f1a816c82","author":{"name":"June-Ruth","email":"c23fd644484463c0addb7f0d93e482cb9f44de38@hotmail.fr"},"message":"modify controller division","distinct":false,"url":"https://api.github.com/repos/June-Ruth/OC-Java-Projet8/commits/71cfe027d39ab6430d942649a724a31f1a816c82"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792619","type":"PushEvent","actor":{"id":878058,"login":"ParkMinKyu","display_login":"ParkMinKyu","gravatar_id":"","url":"https://api.github.com/users/ParkMinKyu","avatar_url":"https://avatars.githubusercontent.com/u/878058?"},"repo":{"id":362471221,"name":"ApartMoney/Apartmoney.github.io","url":"https://api.github.com/repos/ApartMoney/Apartmoney.github.io"},"payload":{"push_id":7561706537,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"3ed91b3a8e61dc679c7baf56e58d84f4ddb9873b","before":"d0538453f335d7d5fbfda003156deace0144011d","commits":[{"sha":"3ed91b3a8e61dc679c7baf56e58d84f4ddb9873b","author":{"name":"ParkMinkyu","email":"f1d6fc1b4c5023402fddfd826b843346eec78543@naver.com"},"message":"update news data","distinct":true,"url":"https://api.github.com/repos/ApartMoney/Apartmoney.github.io/commits/3ed91b3a8e61dc679c7baf56e58d84f4ddb9873b"}]},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":60907646,"login":"ApartMoney","gravatar_id":"","url":"https://api.github.com/orgs/ApartMoney","avatar_url":"https://avatars.githubusercontent.com/u/60907646?"}} +{"id":"17244792620","type":"WatchEvent","actor":{"id":86179625,"login":"ssbuild","display_login":"ssbuild","gravatar_id":"","url":"https://api.github.com/users/ssbuild","avatar_url":"https://avatars.githubusercontent.com/u/86179625?"},"repo":{"id":273269597,"name":"passion765/fastlabel","url":"https://api.github.com/repos/passion765/fastlabel"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792622","type":"CreateEvent","actor":{"id":36655932,"login":"rahulun","display_login":"rahulun","gravatar_id":"","url":"https://api.github.com/users/rahulun","avatar_url":"https://avatars.githubusercontent.com/u/36655932?"},"repo":{"id":388024739,"name":"rahulun/real_estate","url":"https://api.github.com/repos/rahulun/real_estate"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792623","type":"PushEvent","actor":{"id":60849980,"login":"jordans-bot","display_login":"jordans-bot","gravatar_id":"","url":"https://api.github.com/users/jordans-bot","avatar_url":"https://avatars.githubusercontent.com/u/60849980?"},"repo":{"id":139190305,"name":"JTuwiner/bitcoinclock","url":"https://api.github.com/repos/JTuwiner/bitcoinclock"},"payload":{"push_id":7561706543,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"213401a08ef9f67cc8ca185d15989391f95ce951","before":"1f7dc900c38b8b88126eda98e81c61e07cc23d07","commits":[{"sha":"213401a08ef9f67cc8ca185d15989391f95ce951","author":{"name":"jordans-bot","email":"6323b4aabf9938a958e69e4471281fb61992bb8f@users.noreply.github.com"},"message":"Updated block height","distinct":true,"url":"https://api.github.com/repos/JTuwiner/bitcoinclock/commits/213401a08ef9f67cc8ca185d15989391f95ce951"}]},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792624","type":"IssuesEvent","actor":{"id":9405495,"login":"livio-a","display_login":"livio-a","gravatar_id":"","url":"https://api.github.com/users/livio-a","avatar_url":"https://avatars.githubusercontent.com/u/9405495?"},"repo":{"id":247714750,"name":"caos/zitadel","url":"https://api.github.com/repos/caos/zitadel"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/caos/zitadel/issues/2051","repository_url":"https://api.github.com/repos/caos/zitadel","labels_url":"https://api.github.com/repos/caos/zitadel/issues/2051/labels{/name}","comments_url":"https://api.github.com/repos/caos/zitadel/issues/2051/comments","events_url":"https://api.github.com/repos/caos/zitadel/issues/2051/events","html_url":"https://github.com/caos/zitadel/issues/2051","id":949359759,"node_id":"MDU6SXNzdWU5NDkzNTk3NTk=","number":2051,"title":"userinfo attributs missing in id_token when requesting JWT access token with roles","user":{"login":"livio-a","id":9405495,"node_id":"MDQ6VXNlcjk0MDU0OTU=","avatar_url":"https://avatars.githubusercontent.com/u/9405495?v=4","gravatar_id":"","url":"https://api.github.com/users/livio-a","html_url":"https://github.com/livio-a","followers_url":"https://api.github.com/users/livio-a/followers","following_url":"https://api.github.com/users/livio-a/following{/other_user}","gists_url":"https://api.github.com/users/livio-a/gists{/gist_id}","starred_url":"https://api.github.com/users/livio-a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/livio-a/subscriptions","organizations_url":"https://api.github.com/users/livio-a/orgs","repos_url":"https://api.github.com/users/livio-a/repos","events_url":"https://api.github.com/users/livio-a/events{/privacy}","received_events_url":"https://api.github.com/users/livio-a/received_events","type":"User","site_admin":false},"labels":[{"id":1913182587,"node_id":"MDU6TGFiZWwxOTEzMTgyNTg3","url":"https://api.github.com/repos/caos/zitadel/labels/bug","name":"bug","color":"d73a4a","default":true,"description":"Something isn't working"}],"state":"closed","locked":false,"assignee":{"login":"livio-a","id":9405495,"node_id":"MDQ6VXNlcjk0MDU0OTU=","avatar_url":"https://avatars.githubusercontent.com/u/9405495?v=4","gravatar_id":"","url":"https://api.github.com/users/livio-a","html_url":"https://github.com/livio-a","followers_url":"https://api.github.com/users/livio-a/followers","following_url":"https://api.github.com/users/livio-a/following{/other_user}","gists_url":"https://api.github.com/users/livio-a/gists{/gist_id}","starred_url":"https://api.github.com/users/livio-a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/livio-a/subscriptions","organizations_url":"https://api.github.com/users/livio-a/orgs","repos_url":"https://api.github.com/users/livio-a/repos","events_url":"https://api.github.com/users/livio-a/events{/privacy}","received_events_url":"https://api.github.com/users/livio-a/received_events","type":"User","site_admin":false},"assignees":[{"login":"livio-a","id":9405495,"node_id":"MDQ6VXNlcjk0MDU0OTU=","avatar_url":"https://avatars.githubusercontent.com/u/9405495?v=4","gravatar_id":"","url":"https://api.github.com/users/livio-a","html_url":"https://github.com/livio-a","followers_url":"https://api.github.com/users/livio-a/followers","following_url":"https://api.github.com/users/livio-a/following{/other_user}","gists_url":"https://api.github.com/users/livio-a/gists{/gist_id}","starred_url":"https://api.github.com/users/livio-a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/livio-a/subscriptions","organizations_url":"https://api.github.com/users/livio-a/orgs","repos_url":"https://api.github.com/users/livio-a/repos","events_url":"https://api.github.com/users/livio-a/events{/privacy}","received_events_url":"https://api.github.com/users/livio-a/received_events","type":"User","site_admin":false}],"milestone":null,"comments":1,"created_at":"2021-07-21T06:05:55Z","updated_at":"2021-07-21T07:00:05Z","closed_at":"2021-07-21T07:00:05Z","author_association":"MEMBER","active_lock_reason":null,"body":"### Discussed in https://github.com/caos/zitadel/discussions/2050\r\n\r\n
    \r\n\r\nOriginally posted by **astriffe** July 20, 2021\r\nHi caos!\r\n\r\nMight it be that the preferred email vanished from the token, while the checkbox 'User Info im ID Token' is still set?\r\n\r\nOf course I might be wrong, but I can't find the email claim on any of the tokens I receive :)\r\n\r\nCheers, Alex
    ","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":48989016,"login":"caos","gravatar_id":"","url":"https://api.github.com/orgs/caos","avatar_url":"https://avatars.githubusercontent.com/u/48989016?"}} +{"id":"17244792628","type":"PullRequestEvent","actor":{"id":39814207,"login":"pull[bot]","display_login":"pull","gravatar_id":"","url":"https://api.github.com/users/pull[bot]","avatar_url":"https://avatars.githubusercontent.com/u/39814207?"},"repo":{"id":232690151,"name":"devilkun/awesome-mac","url":"https://api.github.com/repos/devilkun/awesome-mac"},"payload":{"action":"opened","number":94,"pull_request":{"url":"https://api.github.com/repos/devilkun/awesome-mac/pulls/94","id":694104905,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0OTA1","html_url":"https://github.com/devilkun/awesome-mac/pull/94","diff_url":"https://github.com/devilkun/awesome-mac/pull/94.diff","patch_url":"https://github.com/devilkun/awesome-mac/pull/94.patch","issue_url":"https://api.github.com/repos/devilkun/awesome-mac/issues/94","number":94,"state":"open","locked":false,"title":"[pull] master from jaywcjlove:master","user":{"login":"pull[bot]","id":39814207,"node_id":"MDM6Qm90Mzk4MTQyMDc=","avatar_url":"https://avatars.githubusercontent.com/in/12910?v=4","gravatar_id":"","url":"https://api.github.com/users/pull%5Bbot%5D","html_url":"https://github.com/apps/pull","followers_url":"https://api.github.com/users/pull%5Bbot%5D/followers","following_url":"https://api.github.com/users/pull%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/pull%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/pull%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pull%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/pull%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/pull%5Bbot%5D/repos","events_url":"https://api.github.com/users/pull%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/pull%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"See Commits and Changes for more details.\n\n-----\nCreated by [ **pull[bot]**](https://github.com/wei/pull)\n\n_Can you help keep this open source service alive? **[💖 Please sponsor : )](https://prod.download/pull-pr-sponsor)**_","created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/devilkun/awesome-mac/pulls/94/commits","review_comments_url":"https://api.github.com/repos/devilkun/awesome-mac/pulls/94/comments","review_comment_url":"https://api.github.com/repos/devilkun/awesome-mac/pulls/comments{/number}","comments_url":"https://api.github.com/repos/devilkun/awesome-mac/issues/94/comments","statuses_url":"https://api.github.com/repos/devilkun/awesome-mac/statuses/694ebf8d5e526b61641ac444ea9084bee7f051c6","head":{"label":"jaywcjlove:master","ref":"master","sha":"694ebf8d5e526b61641ac444ea9084bee7f051c6","user":{"login":"jaywcjlove","id":1680273,"node_id":"MDQ6VXNlcjE2ODAyNzM=","avatar_url":"https://avatars.githubusercontent.com/u/1680273?v=4","gravatar_id":"","url":"https://api.github.com/users/jaywcjlove","html_url":"https://github.com/jaywcjlove","followers_url":"https://api.github.com/users/jaywcjlove/followers","following_url":"https://api.github.com/users/jaywcjlove/following{/other_user}","gists_url":"https://api.github.com/users/jaywcjlove/gists{/gist_id}","starred_url":"https://api.github.com/users/jaywcjlove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaywcjlove/subscriptions","organizations_url":"https://api.github.com/users/jaywcjlove/orgs","repos_url":"https://api.github.com/users/jaywcjlove/repos","events_url":"https://api.github.com/users/jaywcjlove/events{/privacy}","received_events_url":"https://api.github.com/users/jaywcjlove/received_events","type":"User","site_admin":false},"repo":{"id":63539055,"node_id":"MDEwOlJlcG9zaXRvcnk2MzUzOTA1NQ==","name":"awesome-mac","full_name":"jaywcjlove/awesome-mac","private":false,"owner":{"login":"jaywcjlove","id":1680273,"node_id":"MDQ6VXNlcjE2ODAyNzM=","avatar_url":"https://avatars.githubusercontent.com/u/1680273?v=4","gravatar_id":"","url":"https://api.github.com/users/jaywcjlove","html_url":"https://github.com/jaywcjlove","followers_url":"https://api.github.com/users/jaywcjlove/followers","following_url":"https://api.github.com/users/jaywcjlove/following{/other_user}","gists_url":"https://api.github.com/users/jaywcjlove/gists{/gist_id}","starred_url":"https://api.github.com/users/jaywcjlove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaywcjlove/subscriptions","organizations_url":"https://api.github.com/users/jaywcjlove/orgs","repos_url":"https://api.github.com/users/jaywcjlove/repos","events_url":"https://api.github.com/users/jaywcjlove/events{/privacy}","received_events_url":"https://api.github.com/users/jaywcjlove/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jaywcjlove/awesome-mac","description":" Now we have become very big, Different from the original idea. Collect premium software in various categories.","fork":false,"url":"https://api.github.com/repos/jaywcjlove/awesome-mac","forks_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/forks","keys_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/teams","hooks_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/hooks","issue_events_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/issues/events{/number}","events_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/events","assignees_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/assignees{/user}","branches_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/branches{/branch}","tags_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/tags","blobs_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/git/refs{/sha}","trees_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/statuses/{sha}","languages_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/languages","stargazers_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/stargazers","contributors_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/contributors","subscribers_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/subscribers","subscription_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/subscription","commits_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/commits{/sha}","git_commits_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/git/commits{/sha}","comments_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/comments{/number}","issue_comment_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/issues/comments{/number}","contents_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/contents/{+path}","compare_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/merges","archive_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/downloads","issues_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/issues{/number}","pulls_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/pulls{/number}","milestones_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/milestones{/number}","notifications_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/labels{/name}","releases_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/releases{/id}","deployments_url":"https://api.github.com/repos/jaywcjlove/awesome-mac/deployments","created_at":"2016-07-17T15:33:47Z","updated_at":"2021-07-21T06:55:26Z","pushed_at":"2021-07-21T06:29:23Z","git_url":"git://github.com/jaywcjlove/awesome-mac.git","ssh_url":"git@github.com:jaywcjlove/awesome-mac.git","clone_url":"https://github.com/jaywcjlove/awesome-mac.git","svn_url":"https://github.com/jaywcjlove/awesome-mac","homepage":"https://git.io/macx","size":4507,"stargazers_count":44680,"watchers_count":44680,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4871,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":111,"license":{"key":"cc0-1.0","name":"Creative Commons Zero v1.0 Universal","spdx_id":"CC0-1.0","url":"https://api.github.com/licenses/cc0-1.0","node_id":"MDc6TGljZW5zZTY="},"forks":4871,"open_issues":111,"watchers":44680,"default_branch":"master"}},"base":{"label":"devilkun:master","ref":"master","sha":"394013d3def085340f9d329539b903bf169516c1","user":{"login":"devilkun","id":20056743,"node_id":"MDQ6VXNlcjIwMDU2NzQz","avatar_url":"https://avatars.githubusercontent.com/u/20056743?v=4","gravatar_id":"","url":"https://api.github.com/users/devilkun","html_url":"https://github.com/devilkun","followers_url":"https://api.github.com/users/devilkun/followers","following_url":"https://api.github.com/users/devilkun/following{/other_user}","gists_url":"https://api.github.com/users/devilkun/gists{/gist_id}","starred_url":"https://api.github.com/users/devilkun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/devilkun/subscriptions","organizations_url":"https://api.github.com/users/devilkun/orgs","repos_url":"https://api.github.com/users/devilkun/repos","events_url":"https://api.github.com/users/devilkun/events{/privacy}","received_events_url":"https://api.github.com/users/devilkun/received_events","type":"User","site_admin":false},"repo":{"id":232690151,"node_id":"MDEwOlJlcG9zaXRvcnkyMzI2OTAxNTE=","name":"awesome-mac","full_name":"devilkun/awesome-mac","private":false,"owner":{"login":"devilkun","id":20056743,"node_id":"MDQ6VXNlcjIwMDU2NzQz","avatar_url":"https://avatars.githubusercontent.com/u/20056743?v=4","gravatar_id":"","url":"https://api.github.com/users/devilkun","html_url":"https://github.com/devilkun","followers_url":"https://api.github.com/users/devilkun/followers","following_url":"https://api.github.com/users/devilkun/following{/other_user}","gists_url":"https://api.github.com/users/devilkun/gists{/gist_id}","starred_url":"https://api.github.com/users/devilkun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/devilkun/subscriptions","organizations_url":"https://api.github.com/users/devilkun/orgs","repos_url":"https://api.github.com/users/devilkun/repos","events_url":"https://api.github.com/users/devilkun/events{/privacy}","received_events_url":"https://api.github.com/users/devilkun/received_events","type":"User","site_admin":false},"html_url":"https://github.com/devilkun/awesome-mac","description":" Now we have become very big, Different from the original idea. Collect premium software in various categories.","fork":true,"url":"https://api.github.com/repos/devilkun/awesome-mac","forks_url":"https://api.github.com/repos/devilkun/awesome-mac/forks","keys_url":"https://api.github.com/repos/devilkun/awesome-mac/keys{/key_id}","collaborators_url":"https://api.github.com/repos/devilkun/awesome-mac/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/devilkun/awesome-mac/teams","hooks_url":"https://api.github.com/repos/devilkun/awesome-mac/hooks","issue_events_url":"https://api.github.com/repos/devilkun/awesome-mac/issues/events{/number}","events_url":"https://api.github.com/repos/devilkun/awesome-mac/events","assignees_url":"https://api.github.com/repos/devilkun/awesome-mac/assignees{/user}","branches_url":"https://api.github.com/repos/devilkun/awesome-mac/branches{/branch}","tags_url":"https://api.github.com/repos/devilkun/awesome-mac/tags","blobs_url":"https://api.github.com/repos/devilkun/awesome-mac/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/devilkun/awesome-mac/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/devilkun/awesome-mac/git/refs{/sha}","trees_url":"https://api.github.com/repos/devilkun/awesome-mac/git/trees{/sha}","statuses_url":"https://api.github.com/repos/devilkun/awesome-mac/statuses/{sha}","languages_url":"https://api.github.com/repos/devilkun/awesome-mac/languages","stargazers_url":"https://api.github.com/repos/devilkun/awesome-mac/stargazers","contributors_url":"https://api.github.com/repos/devilkun/awesome-mac/contributors","subscribers_url":"https://api.github.com/repos/devilkun/awesome-mac/subscribers","subscription_url":"https://api.github.com/repos/devilkun/awesome-mac/subscription","commits_url":"https://api.github.com/repos/devilkun/awesome-mac/commits{/sha}","git_commits_url":"https://api.github.com/repos/devilkun/awesome-mac/git/commits{/sha}","comments_url":"https://api.github.com/repos/devilkun/awesome-mac/comments{/number}","issue_comment_url":"https://api.github.com/repos/devilkun/awesome-mac/issues/comments{/number}","contents_url":"https://api.github.com/repos/devilkun/awesome-mac/contents/{+path}","compare_url":"https://api.github.com/repos/devilkun/awesome-mac/compare/{base}...{head}","merges_url":"https://api.github.com/repos/devilkun/awesome-mac/merges","archive_url":"https://api.github.com/repos/devilkun/awesome-mac/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/devilkun/awesome-mac/downloads","issues_url":"https://api.github.com/repos/devilkun/awesome-mac/issues{/number}","pulls_url":"https://api.github.com/repos/devilkun/awesome-mac/pulls{/number}","milestones_url":"https://api.github.com/repos/devilkun/awesome-mac/milestones{/number}","notifications_url":"https://api.github.com/repos/devilkun/awesome-mac/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/devilkun/awesome-mac/labels{/name}","releases_url":"https://api.github.com/repos/devilkun/awesome-mac/releases{/id}","deployments_url":"https://api.github.com/repos/devilkun/awesome-mac/deployments","created_at":"2020-01-09T00:51:49Z","updated_at":"2021-07-20T08:44:31Z","pushed_at":"2021-07-20T08:44:21Z","git_url":"git://github.com/devilkun/awesome-mac.git","ssh_url":"git@github.com:devilkun/awesome-mac.git","clone_url":"https://github.com/devilkun/awesome-mac.git","svn_url":"https://github.com/devilkun/awesome-mac","homepage":"https://git.io/macx","size":3229,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"cc0-1.0","name":"Creative Commons Zero v1.0 Universal","spdx_id":"CC0-1.0","url":"https://api.github.com/licenses/cc0-1.0","node_id":"MDc6TGljZW5zZTY="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/devilkun/awesome-mac/pulls/94"},"html":{"href":"https://github.com/devilkun/awesome-mac/pull/94"},"issue":{"href":"https://api.github.com/repos/devilkun/awesome-mac/issues/94"},"comments":{"href":"https://api.github.com/repos/devilkun/awesome-mac/issues/94/comments"},"review_comments":{"href":"https://api.github.com/repos/devilkun/awesome-mac/pulls/94/comments"},"review_comment":{"href":"https://api.github.com/repos/devilkun/awesome-mac/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/devilkun/awesome-mac/pulls/94/commits"},"statuses":{"href":"https://api.github.com/repos/devilkun/awesome-mac/statuses/694ebf8d5e526b61641ac444ea9084bee7f051c6"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":2,"deletions":0,"changed_files":2}},"public":true,"created_at":"2021-07-21T07:00:05Z"} +{"id":"17244792694","type":"PullRequestReviewCommentEvent","actor":{"id":5922961,"login":"facewindu","display_login":"facewindu","gravatar_id":"","url":"https://api.github.com/users/facewindu","avatar_url":"https://avatars.githubusercontent.com/u/5922961?"},"repo":{"id":174415874,"name":"gradle/gradle-enterprise-build-config-samples","url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/comments/673711156","pull_request_review_id":711317411,"id":673711156,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3MzcxMTE1Ng==","diff_hunk":"@@ -0,0 +1,37 @@\n+package com.gradle.maven.common.user.data.extension;\n+\n+import org.apache.maven.eventspy.AbstractEventSpy;\n+import org.apache.maven.eventspy.EventSpy;\n+import org.codehaus.plexus.PlexusContainer;\n+import org.codehaus.plexus.component.annotations.Component;\n+import org.codehaus.plexus.logging.Logger;\n+\n+import javax.inject.Inject;\n+\n+@Component(","path":"common-custom-user-data-maven-extension/src/main/java/com/gradle/maven/common/user/data/extension/CommonCustomUserDataMavenExtensionEventSpy.java","position":11,"original_position":11,"commit_id":"ea2eb525f300c91f9228a4ddeb3fa83296316b9b","original_commit_id":"ea2eb525f300c91f9228a4ddeb3fa83296316b9b","user":{"login":"facewindu","id":5922961,"node_id":"MDQ6VXNlcjU5MjI5NjE=","avatar_url":"https://avatars.githubusercontent.com/u/5922961?v=4","gravatar_id":"","url":"https://api.github.com/users/facewindu","html_url":"https://github.com/facewindu","followers_url":"https://api.github.com/users/facewindu/followers","following_url":"https://api.github.com/users/facewindu/following{/other_user}","gists_url":"https://api.github.com/users/facewindu/gists{/gist_id}","starred_url":"https://api.github.com/users/facewindu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/facewindu/subscriptions","organizations_url":"https://api.github.com/users/facewindu/orgs","repos_url":"https://api.github.com/users/facewindu/repos","events_url":"https://api.github.com/users/facewindu/events{/privacy}","received_events_url":"https://api.github.com/users/facewindu/received_events","type":"User","site_admin":false},"body":"There are cases where the `CommonCustomUserDataGradleEnterpriseListener` is not constructed, even if marked with the `@Component` annotation.\r\nBut even there, it needs the `GradleEnterpriseListener` interface at runtime when constructed, so the interface needs to be available in its realm when constructed.(and luckily, it's constructed after `EventSpy#init` is called)","created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","html_url":"https://github.com/gradle/gradle-enterprise-build-config-samples/pull/115#discussion_r673711156","pull_request_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/comments/673711156"},"html":{"href":"https://github.com/gradle/gradle-enterprise-build-config-samples/pull/115#discussion_r673711156"},"pull_request":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115"}},"start_line":null,"original_start_line":null,"start_side":null,"line":11,"original_line":11,"side":"RIGHT","in_reply_to_id":673242063},"pull_request":{"url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115","id":693559121,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNTU5MTIx","html_url":"https://github.com/gradle/gradle-enterprise-build-config-samples/pull/115","diff_url":"https://github.com/gradle/gradle-enterprise-build-config-samples/pull/115.diff","patch_url":"https://github.com/gradle/gradle-enterprise-build-config-samples/pull/115.patch","issue_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/115","number":115,"state":"open","locked":false,"title":"Use the new GradleEnterpriseListener interface","user":{"login":"facewindu","id":5922961,"node_id":"MDQ6VXNlcjU5MjI5NjE=","avatar_url":"https://avatars.githubusercontent.com/u/5922961?v=4","gravatar_id":"","url":"https://api.github.com/users/facewindu","html_url":"https://github.com/facewindu","followers_url":"https://api.github.com/users/facewindu/followers","following_url":"https://api.github.com/users/facewindu/following{/other_user}","gists_url":"https://api.github.com/users/facewindu/gists{/gist_id}","starred_url":"https://api.github.com/users/facewindu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/facewindu/subscriptions","organizations_url":"https://api.github.com/users/facewindu/orgs","repos_url":"https://api.github.com/users/facewindu/repos","events_url":"https://api.github.com/users/facewindu/events{/privacy}","received_events_url":"https://api.github.com/users/facewindu/received_events","type":"User","site_admin":false},"body":"This PR changes quite a bit the structure of the CCUDME project.\r\nIt is now an EventSpy that register very early (in its init callback) a custom listener implementing the new `GradleEnterpriseListener` interface. This allows us to get rid of Maven callbacks quirks and totally control what's going on here (instead of e.g. relying on AbstractMavenLifecycleParticipant callbacks being fired 'at the right time')","created_at":"2021-07-20T15:22:01Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e79fe4577a0e48b9e0e7720c27de5a72e40c6ed7","assignee":{"login":"facewindu","id":5922961,"node_id":"MDQ6VXNlcjU5MjI5NjE=","avatar_url":"https://avatars.githubusercontent.com/u/5922961?v=4","gravatar_id":"","url":"https://api.github.com/users/facewindu","html_url":"https://github.com/facewindu","followers_url":"https://api.github.com/users/facewindu/followers","following_url":"https://api.github.com/users/facewindu/following{/other_user}","gists_url":"https://api.github.com/users/facewindu/gists{/gist_id}","starred_url":"https://api.github.com/users/facewindu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/facewindu/subscriptions","organizations_url":"https://api.github.com/users/facewindu/orgs","repos_url":"https://api.github.com/users/facewindu/repos","events_url":"https://api.github.com/users/facewindu/events{/privacy}","received_events_url":"https://api.github.com/users/facewindu/received_events","type":"User","site_admin":false},"assignees":[{"login":"facewindu","id":5922961,"node_id":"MDQ6VXNlcjU5MjI5NjE=","avatar_url":"https://avatars.githubusercontent.com/u/5922961?v=4","gravatar_id":"","url":"https://api.github.com/users/facewindu","html_url":"https://github.com/facewindu","followers_url":"https://api.github.com/users/facewindu/followers","following_url":"https://api.github.com/users/facewindu/following{/other_user}","gists_url":"https://api.github.com/users/facewindu/gists{/gist_id}","starred_url":"https://api.github.com/users/facewindu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/facewindu/subscriptions","organizations_url":"https://api.github.com/users/facewindu/orgs","repos_url":"https://api.github.com/users/facewindu/repos","events_url":"https://api.github.com/users/facewindu/events{/privacy}","received_events_url":"https://api.github.com/users/facewindu/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"marcphilipp","id":214207,"node_id":"MDQ6VXNlcjIxNDIwNw==","avatar_url":"https://avatars.githubusercontent.com/u/214207?v=4","gravatar_id":"","url":"https://api.github.com/users/marcphilipp","html_url":"https://github.com/marcphilipp","followers_url":"https://api.github.com/users/marcphilipp/followers","following_url":"https://api.github.com/users/marcphilipp/following{/other_user}","gists_url":"https://api.github.com/users/marcphilipp/gists{/gist_id}","starred_url":"https://api.github.com/users/marcphilipp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marcphilipp/subscriptions","organizations_url":"https://api.github.com/users/marcphilipp/orgs","repos_url":"https://api.github.com/users/marcphilipp/repos","events_url":"https://api.github.com/users/marcphilipp/events{/privacy}","received_events_url":"https://api.github.com/users/marcphilipp/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115/commits","review_comments_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115/comments","review_comment_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/115/comments","statuses_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/statuses/ea2eb525f300c91f9228a4ddeb3fa83296316b9b","head":{"label":"gradle:francois/ge-listener","ref":"francois/ge-listener","sha":"ea2eb525f300c91f9228a4ddeb3fa83296316b9b","user":{"login":"gradle","id":124156,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyNDE1Ng==","avatar_url":"https://avatars.githubusercontent.com/u/124156?v=4","gravatar_id":"","url":"https://api.github.com/users/gradle","html_url":"https://github.com/gradle","followers_url":"https://api.github.com/users/gradle/followers","following_url":"https://api.github.com/users/gradle/following{/other_user}","gists_url":"https://api.github.com/users/gradle/gists{/gist_id}","starred_url":"https://api.github.com/users/gradle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gradle/subscriptions","organizations_url":"https://api.github.com/users/gradle/orgs","repos_url":"https://api.github.com/users/gradle/repos","events_url":"https://api.github.com/users/gradle/events{/privacy}","received_events_url":"https://api.github.com/users/gradle/received_events","type":"Organization","site_admin":false},"repo":{"id":174415874,"node_id":"MDEwOlJlcG9zaXRvcnkxNzQ0MTU4NzQ=","name":"gradle-enterprise-build-config-samples","full_name":"gradle/gradle-enterprise-build-config-samples","private":false,"owner":{"login":"gradle","id":124156,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyNDE1Ng==","avatar_url":"https://avatars.githubusercontent.com/u/124156?v=4","gravatar_id":"","url":"https://api.github.com/users/gradle","html_url":"https://github.com/gradle","followers_url":"https://api.github.com/users/gradle/followers","following_url":"https://api.github.com/users/gradle/following{/other_user}","gists_url":"https://api.github.com/users/gradle/gists{/gist_id}","starred_url":"https://api.github.com/users/gradle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gradle/subscriptions","organizations_url":"https://api.github.com/users/gradle/orgs","repos_url":"https://api.github.com/users/gradle/repos","events_url":"https://api.github.com/users/gradle/events{/privacy}","received_events_url":"https://api.github.com/users/gradle/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/gradle/gradle-enterprise-build-config-samples","description":"Code samples that demonstrate how to customize your Gradle Enterprise build configuration using Gradle or Maven.","fork":false,"url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples","forks_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/forks","keys_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/teams","hooks_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/hooks","issue_events_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/events{/number}","events_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/events","assignees_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/assignees{/user}","branches_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/branches{/branch}","tags_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/tags","blobs_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/refs{/sha}","trees_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/statuses/{sha}","languages_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/languages","stargazers_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/stargazers","contributors_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/contributors","subscribers_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/subscribers","subscription_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/subscription","commits_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/commits{/sha}","git_commits_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/commits{/sha}","comments_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/comments{/number}","issue_comment_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/comments{/number}","contents_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/contents/{+path}","compare_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/merges","archive_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/downloads","issues_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues{/number}","pulls_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls{/number}","milestones_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/milestones{/number}","notifications_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/labels{/name}","releases_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/releases{/id}","deployments_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/deployments","created_at":"2019-03-07T20:33:16Z","updated_at":"2021-07-20T18:57:38Z","pushed_at":"2021-07-20T21:33:01Z","git_url":"git://github.com/gradle/gradle-enterprise-build-config-samples.git","ssh_url":"git@github.com:gradle/gradle-enterprise-build-config-samples.git","clone_url":"https://github.com/gradle/gradle-enterprise-build-config-samples.git","svn_url":"https://github.com/gradle/gradle-enterprise-build-config-samples","homepage":"https://gradle.com/enterprise","size":20749,"stargazers_count":34,"watchers_count":34,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":17,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":17,"open_issues":3,"watchers":34,"default_branch":"master"}},"base":{"label":"gradle:master","ref":"master","sha":"f67b09892daa3fbb9646b5bb6076d29ce92e0964","user":{"login":"gradle","id":124156,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyNDE1Ng==","avatar_url":"https://avatars.githubusercontent.com/u/124156?v=4","gravatar_id":"","url":"https://api.github.com/users/gradle","html_url":"https://github.com/gradle","followers_url":"https://api.github.com/users/gradle/followers","following_url":"https://api.github.com/users/gradle/following{/other_user}","gists_url":"https://api.github.com/users/gradle/gists{/gist_id}","starred_url":"https://api.github.com/users/gradle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gradle/subscriptions","organizations_url":"https://api.github.com/users/gradle/orgs","repos_url":"https://api.github.com/users/gradle/repos","events_url":"https://api.github.com/users/gradle/events{/privacy}","received_events_url":"https://api.github.com/users/gradle/received_events","type":"Organization","site_admin":false},"repo":{"id":174415874,"node_id":"MDEwOlJlcG9zaXRvcnkxNzQ0MTU4NzQ=","name":"gradle-enterprise-build-config-samples","full_name":"gradle/gradle-enterprise-build-config-samples","private":false,"owner":{"login":"gradle","id":124156,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyNDE1Ng==","avatar_url":"https://avatars.githubusercontent.com/u/124156?v=4","gravatar_id":"","url":"https://api.github.com/users/gradle","html_url":"https://github.com/gradle","followers_url":"https://api.github.com/users/gradle/followers","following_url":"https://api.github.com/users/gradle/following{/other_user}","gists_url":"https://api.github.com/users/gradle/gists{/gist_id}","starred_url":"https://api.github.com/users/gradle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gradle/subscriptions","organizations_url":"https://api.github.com/users/gradle/orgs","repos_url":"https://api.github.com/users/gradle/repos","events_url":"https://api.github.com/users/gradle/events{/privacy}","received_events_url":"https://api.github.com/users/gradle/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/gradle/gradle-enterprise-build-config-samples","description":"Code samples that demonstrate how to customize your Gradle Enterprise build configuration using Gradle or Maven.","fork":false,"url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples","forks_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/forks","keys_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/teams","hooks_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/hooks","issue_events_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/events{/number}","events_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/events","assignees_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/assignees{/user}","branches_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/branches{/branch}","tags_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/tags","blobs_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/refs{/sha}","trees_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/statuses/{sha}","languages_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/languages","stargazers_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/stargazers","contributors_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/contributors","subscribers_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/subscribers","subscription_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/subscription","commits_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/commits{/sha}","git_commits_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/git/commits{/sha}","comments_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/comments{/number}","issue_comment_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/comments{/number}","contents_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/contents/{+path}","compare_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/merges","archive_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/downloads","issues_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues{/number}","pulls_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls{/number}","milestones_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/milestones{/number}","notifications_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/labels{/name}","releases_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/releases{/id}","deployments_url":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/deployments","created_at":"2019-03-07T20:33:16Z","updated_at":"2021-07-20T18:57:38Z","pushed_at":"2021-07-20T21:33:01Z","git_url":"git://github.com/gradle/gradle-enterprise-build-config-samples.git","ssh_url":"git@github.com:gradle/gradle-enterprise-build-config-samples.git","clone_url":"https://github.com/gradle/gradle-enterprise-build-config-samples.git","svn_url":"https://github.com/gradle/gradle-enterprise-build-config-samples","homepage":"https://gradle.com/enterprise","size":20749,"stargazers_count":34,"watchers_count":34,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":17,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":17,"open_issues":3,"watchers":34,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115"},"html":{"href":"https://github.com/gradle/gradle-enterprise-build-config-samples/pull/115"},"issue":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/115"},"comments":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/issues/115/comments"},"review_comments":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115/comments"},"review_comment":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/pulls/115/commits"},"statuses":{"href":"https://api.github.com/repos/gradle/gradle-enterprise-build-config-samples/statuses/ea2eb525f300c91f9228a4ddeb3fa83296316b9b"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":124156,"login":"gradle","gravatar_id":"","url":"https://api.github.com/orgs/gradle","avatar_url":"https://avatars.githubusercontent.com/u/124156?"}} +{"id":"17244792711","type":"PullRequestReviewCommentEvent","actor":{"id":2418812,"login":"Smurf-IV","display_login":"Smurf-IV","gravatar_id":"","url":"https://api.github.com/users/Smurf-IV","avatar_url":"https://avatars.githubusercontent.com/u/2418812?"},"repo":{"id":243247330,"name":"Krypton-Suite/Standard-Toolkit","url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/comments/673711157","pull_request_review_id":711317412,"id":673711157,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3MzcxMTE1Nw==","diff_hunk":"@@ -130,36 +133,40 @@ public override void Layout(ViewLayoutContext context)\n // Find the rectangle available to each child by removing the rounding\n Rectangle childRect = ClientRectangle;\n \n- // Find the amount of rounding to apply\n- int tempRounding = Convert.ToInt32(Rounding);\n+ RectangleF childRectF;","path":"Source/Krypton Components/Krypton.Navigator/View Layout/ViewLayoutInsetOverlap.cs","position":37,"original_position":24,"commit_id":"6045c9a6424e198f542c84a97ff094a4952c866d","original_commit_id":"e174641d5578c95967e8df67aea55a15ad919ac7","user":{"login":"Smurf-IV","id":2418812,"node_id":"MDQ6VXNlcjI0MTg4MTI=","avatar_url":"https://avatars.githubusercontent.com/u/2418812?v=4","gravatar_id":"","url":"https://api.github.com/users/Smurf-IV","html_url":"https://github.com/Smurf-IV","followers_url":"https://api.github.com/users/Smurf-IV/followers","following_url":"https://api.github.com/users/Smurf-IV/following{/other_user}","gists_url":"https://api.github.com/users/Smurf-IV/gists{/gist_id}","starred_url":"https://api.github.com/users/Smurf-IV/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Smurf-IV/subscriptions","organizations_url":"https://api.github.com/users/Smurf-IV/orgs","repos_url":"https://api.github.com/users/Smurf-IV/repos","events_url":"https://api.github.com/users/Smurf-IV/events{/privacy}","received_events_url":"https://api.github.com/users/Smurf-IV/received_events","type":"User","site_admin":false},"body":"Assign to ClientREctangle here","created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:06Z","html_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199#discussion_r673711157","pull_request_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/comments/673711157"},"html":{"href":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199#discussion_r673711157"},"pull_request":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199"}},"start_line":null,"original_start_line":null,"start_side":null,"line":136,"original_line":136,"side":"RIGHT"},"pull_request":{"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199","id":692062634,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyMDYyNjM0","html_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199","diff_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199.diff","patch_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199.patch","issue_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199","number":199,"state":"open","locked":false,"title":"Alpha fr197","user":{"login":"Wagnerp","id":949607,"node_id":"MDQ6VXNlcjk0OTYwNw==","avatar_url":"https://avatars.githubusercontent.com/u/949607?v=4","gravatar_id":"","url":"https://api.github.com/users/Wagnerp","html_url":"https://github.com/Wagnerp","followers_url":"https://api.github.com/users/Wagnerp/followers","following_url":"https://api.github.com/users/Wagnerp/following{/other_user}","gists_url":"https://api.github.com/users/Wagnerp/gists{/gist_id}","starred_url":"https://api.github.com/users/Wagnerp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Wagnerp/subscriptions","organizations_url":"https://api.github.com/users/Wagnerp/orgs","repos_url":"https://api.github.com/users/Wagnerp/repos","events_url":"https://api.github.com/users/Wagnerp/events{/privacy}","received_events_url":"https://api.github.com/users/Wagnerp/received_events","type":"User","site_admin":false},"body":"#197 pt. 1","created_at":"2021-07-18T09:59:40Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e7e5bc21581f2760133e1342dc94bf883113274a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/commits","review_comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/comments","review_comment_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199/comments","statuses_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/6045c9a6424e198f542c84a97ff094a4952c866d","head":{"label":"Krypton-Suite:alpha-fr197","ref":"alpha-fr197","sha":"6045c9a6424e198f542c84a97ff094a4952c866d","user":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"repo":{"id":243247330,"node_id":"MDEwOlJlcG9zaXRvcnkyNDMyNDczMzA=","name":"Standard-Toolkit","full_name":"Krypton-Suite/Standard-Toolkit","private":false,"owner":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Krypton-Suite/Standard-Toolkit","description":"An update to Component factory's krypton toolkit to support .NET Framework 3.5 to .NET Core/.NET","fork":false,"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit","forks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/forks","keys_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/teams","hooks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/hooks","issue_events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/events{/number}","events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/events","assignees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/assignees{/user}","branches_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/branches{/branch}","tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/tags","blobs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/{sha}","languages_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/languages","stargazers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/stargazers","contributors_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contributors","subscribers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscribers","subscription_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscription","commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contents/{+path}","compare_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/merges","archive_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/downloads","issues_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues{/number}","pulls_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls{/number}","milestones_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/milestones{/number}","notifications_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/labels{/name}","releases_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/releases{/id}","deployments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/deployments","created_at":"2020-02-26T11:33:33Z","updated_at":"2021-07-20T17:53:46Z","pushed_at":"2021-07-20T17:47:02Z","git_url":"git://github.com/Krypton-Suite/Standard-Toolkit.git","ssh_url":"git@github.com:Krypton-Suite/Standard-Toolkit.git","clone_url":"https://github.com/Krypton-Suite/Standard-Toolkit.git","svn_url":"https://github.com/Krypton-Suite/Standard-Toolkit","homepage":"","size":12480,"stargazers_count":84,"watchers_count":84,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":59,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":12,"open_issues":59,"watchers":84,"default_branch":"master"}},"base":{"label":"Krypton-Suite:alpha","ref":"alpha","sha":"4c6b6a3d8990d1bc888f01369b2348bd909dda72","user":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"repo":{"id":243247330,"node_id":"MDEwOlJlcG9zaXRvcnkyNDMyNDczMzA=","name":"Standard-Toolkit","full_name":"Krypton-Suite/Standard-Toolkit","private":false,"owner":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Krypton-Suite/Standard-Toolkit","description":"An update to Component factory's krypton toolkit to support .NET Framework 3.5 to .NET Core/.NET","fork":false,"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit","forks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/forks","keys_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/teams","hooks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/hooks","issue_events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/events{/number}","events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/events","assignees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/assignees{/user}","branches_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/branches{/branch}","tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/tags","blobs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/{sha}","languages_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/languages","stargazers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/stargazers","contributors_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contributors","subscribers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscribers","subscription_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscription","commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contents/{+path}","compare_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/merges","archive_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/downloads","issues_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues{/number}","pulls_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls{/number}","milestones_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/milestones{/number}","notifications_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/labels{/name}","releases_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/releases{/id}","deployments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/deployments","created_at":"2020-02-26T11:33:33Z","updated_at":"2021-07-20T17:53:46Z","pushed_at":"2021-07-20T17:47:02Z","git_url":"git://github.com/Krypton-Suite/Standard-Toolkit.git","ssh_url":"git@github.com:Krypton-Suite/Standard-Toolkit.git","clone_url":"https://github.com/Krypton-Suite/Standard-Toolkit.git","svn_url":"https://github.com/Krypton-Suite/Standard-Toolkit","homepage":"","size":12480,"stargazers_count":84,"watchers_count":84,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":59,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":12,"open_issues":59,"watchers":84,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199"},"html":{"href":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199"},"issue":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199"},"comments":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199/comments"},"review_comments":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/comments"},"review_comment":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/commits"},"statuses":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/6045c9a6424e198f542c84a97ff094a4952c866d"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:05Z","org":{"id":61501509,"login":"Krypton-Suite","gravatar_id":"","url":"https://api.github.com/orgs/Krypton-Suite","avatar_url":"https://avatars.githubusercontent.com/u/61501509?"}} +{"id":"17244792635","type":"IssuesEvent","actor":{"id":3520788,"login":"arnosthavelka","display_login":"arnosthavelka","gravatar_id":"","url":"https://api.github.com/users/arnosthavelka","avatar_url":"https://avatars.githubusercontent.com/u/3520788?"},"repo":{"id":80031573,"name":"arnosthavelka/junit-poc","url":"https://api.github.com/repos/arnosthavelka/junit-poc"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37","repository_url":"https://api.github.com/repos/arnosthavelka/junit-poc","labels_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37/labels{/name}","comments_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37/comments","events_url":"https://api.github.com/repos/arnosthavelka/junit-poc/issues/37/events","html_url":"https://github.com/arnosthavelka/junit-poc/issues/37","id":664238030,"node_id":"MDU6SXNzdWU2NjQyMzgwMzA=","number":37,"title":"Maven profile for JDK previews","user":{"login":"arnosthavelka","id":3520788,"node_id":"MDQ6VXNlcjM1MjA3ODg=","avatar_url":"https://avatars.githubusercontent.com/u/3520788?v=4","gravatar_id":"","url":"https://api.github.com/users/arnosthavelka","html_url":"https://github.com/arnosthavelka","followers_url":"https://api.github.com/users/arnosthavelka/followers","following_url":"https://api.github.com/users/arnosthavelka/following{/other_user}","gists_url":"https://api.github.com/users/arnosthavelka/gists{/gist_id}","starred_url":"https://api.github.com/users/arnosthavelka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arnosthavelka/subscriptions","organizations_url":"https://api.github.com/users/arnosthavelka/orgs","repos_url":"https://api.github.com/users/arnosthavelka/repos","events_url":"https://api.github.com/users/arnosthavelka/events{/privacy}","received_events_url":"https://api.github.com/users/arnosthavelka/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2020-07-23T06:40:56Z","updated_at":"2021-07-21T07:00:05Z","closed_at":"2021-07-21T07:00:05Z","author_association":"OWNER","active_lock_reason":null,"body":"The preview features are enabled by flag `--enable-preview`. This option has to be added to these maven plugins:\r\n- `maven-compiler-plugin`\r\n- `maven-surefire-plugin`\r\n- `maven-failsafe-plugin`","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792638","type":"CreateEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388024741,"name":"thatjohn01/721001687","url":"https://api.github.com/repos/thatjohn01/721001687"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":"中国有个毛泽东-少年版_PDF下载_本书编委会编写,胡月生,林学勤执笔","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792639","type":"DeleteEvent","actor":{"id":26302304,"login":"Avokadoen","display_login":"Avokadoen","gravatar_id":"","url":"https://api.github.com/users/Avokadoen","avatar_url":"https://avatars.githubusercontent.com/u/26302304?"},"repo":{"id":340032111,"name":"Avokadoen/gowarcserver","url":"https://api.github.com/repos/Avokadoen/gowarcserver"},"payload":{"ref":"extend-lint","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792642","type":"PushEvent","actor":{"id":39609219,"login":"lyongchang","display_login":"lyongchang","gravatar_id":"","url":"https://api.github.com/users/lyongchang","avatar_url":"https://avatars.githubusercontent.com/u/39609219?"},"repo":{"id":322192101,"name":"lyongchang/blog-imgbed","url":"https://api.github.com/repos/lyongchang/blog-imgbed"},"payload":{"push_id":7561706548,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"866a14452984bd47dddb73862e37d0e79666ac08","before":"31a31b863a4a497d3ae32044544e87e2fa37fca8","commits":[{"sha":"866a14452984bd47dddb73862e37d0e79666ac08","author":{"name":"lyongchang","email":"2feb1c16820736bf33e9bb1397bb3292362c7d15@users.noreply.github.com"},"message":"Upload by PicGo","distinct":true,"url":"https://api.github.com/repos/lyongchang/blog-imgbed/commits/866a14452984bd47dddb73862e37d0e79666ac08"}]},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792643","type":"CreateEvent","actor":{"id":82650311,"login":"Niharika-Trivedi","display_login":"Niharika-Trivedi","gravatar_id":"","url":"https://api.github.com/users/Niharika-Trivedi","avatar_url":"https://avatars.githubusercontent.com/u/82650311?"},"repo":{"id":388024740,"name":"Niharika-Trivedi/Project-22-Bouncing-Ball-By-Niharika-Trivedi-","url":"https://api.github.com/repos/Niharika-Trivedi/Project-22-Bouncing-Ball-By-Niharika-Trivedi-"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792644","type":"IssueCommentEvent","actor":{"id":23638133,"login":"LaoXiZi","display_login":"LaoXiZi","gravatar_id":"","url":"https://api.github.com/users/LaoXiZi","avatar_url":"https://avatars.githubusercontent.com/u/23638133?"},"repo":{"id":28550872,"name":"Bigkoo/Android-PickerView","url":"https://api.github.com/repos/Bigkoo/Android-PickerView"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/Bigkoo/Android-PickerView/issues/823","repository_url":"https://api.github.com/repos/Bigkoo/Android-PickerView","labels_url":"https://api.github.com/repos/Bigkoo/Android-PickerView/issues/823/labels{/name}","comments_url":"https://api.github.com/repos/Bigkoo/Android-PickerView/issues/823/comments","events_url":"https://api.github.com/repos/Bigkoo/Android-PickerView/issues/823/events","html_url":"https://github.com/Bigkoo/Android-PickerView/issues/823","id":505028039,"node_id":"MDU6SXNzdWU1MDUwMjgwMzk=","number":823,"title":"时间倒序展示问题","user":{"login":"wojiaoyanxin","id":23535341,"node_id":"MDQ6VXNlcjIzNTM1MzQx","avatar_url":"https://avatars.githubusercontent.com/u/23535341?v=4","gravatar_id":"","url":"https://api.github.com/users/wojiaoyanxin","html_url":"https://github.com/wojiaoyanxin","followers_url":"https://api.github.com/users/wojiaoyanxin/followers","following_url":"https://api.github.com/users/wojiaoyanxin/following{/other_user}","gists_url":"https://api.github.com/users/wojiaoyanxin/gists{/gist_id}","starred_url":"https://api.github.com/users/wojiaoyanxin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wojiaoyanxin/subscriptions","organizations_url":"https://api.github.com/users/wojiaoyanxin/orgs","repos_url":"https://api.github.com/users/wojiaoyanxin/repos","events_url":"https://api.github.com/users/wojiaoyanxin/events{/privacy}","received_events_url":"https://api.github.com/users/wojiaoyanxin/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2019-10-10T04:35:09Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"目前展示 : 1980 - 2019 需求: 2019 -1980 ","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/Bigkoo/Android-PickerView/issues/comments/883942820","html_url":"https://github.com/Bigkoo/Android-PickerView/issues/823#issuecomment-883942820","issue_url":"https://api.github.com/repos/Bigkoo/Android-PickerView/issues/823","id":883942820,"node_id":"IC_kwDOAbOm2M40r-Wk","user":{"login":"LaoXiZi","id":23638133,"node_id":"MDQ6VXNlcjIzNjM4MTMz","avatar_url":"https://avatars.githubusercontent.com/u/23638133?v=4","gravatar_id":"","url":"https://api.github.com/users/LaoXiZi","html_url":"https://github.com/LaoXiZi","followers_url":"https://api.github.com/users/LaoXiZi/followers","following_url":"https://api.github.com/users/LaoXiZi/following{/other_user}","gists_url":"https://api.github.com/users/LaoXiZi/gists{/gist_id}","starred_url":"https://api.github.com/users/LaoXiZi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LaoXiZi/subscriptions","organizations_url":"https://api.github.com/users/LaoXiZi/orgs","repos_url":"https://api.github.com/users/LaoXiZi/repos","events_url":"https://api.github.com/users/LaoXiZi/events{/privacy}","received_events_url":"https://api.github.com/users/LaoXiZi/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","author_association":"NONE","body":"有支持倒序的Api吗?","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T07:00:06Z","org":{"id":25974450,"login":"Bigkoo","gravatar_id":"","url":"https://api.github.com/orgs/Bigkoo","avatar_url":"https://avatars.githubusercontent.com/u/25974450?"}} +{"id":"17244792647","type":"PushEvent","actor":{"id":49699333,"login":"dependabot[bot]","display_login":"dependabot","gravatar_id":"","url":"https://api.github.com/users/dependabot[bot]","avatar_url":"https://avatars.githubusercontent.com/u/49699333?"},"repo":{"id":252650570,"name":"kolebjak/game-of-clicks","url":"https://api.github.com/repos/kolebjak/game-of-clicks"},"payload":{"push_id":7561706546,"size":3,"distinct_size":1,"ref":"refs/heads/dependabot/npm_and_yarn/frontend/merge-deep-3.0.3","head":"7e11c2b5fc310c2ce6b5b0fdb496380182700bcb","before":"ef4224393cf98672b6a1efb2a540605e2bf8aa09","commits":[{"sha":"7f40b43c11a19b234183a48c9ec8bc65872b99b0","author":{"name":"dependabot-preview[bot]","email":"6a4c1c4838f800d1998274cd5234e1f65c55e90c@users.noreply.github.com"},"message":"Bump @babel/core from 7.14.6 to 7.14.8 in /server\n\nBumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.14.6 to 7.14.8.\n- [Release notes](https://github.com/babel/babel/releases)\n- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/babel/babel/commits/v7.14.8/packages/babel-core)\n\nSigned-off-by: dependabot-preview[bot] ","distinct":false,"url":"https://api.github.com/repos/kolebjak/game-of-clicks/commits/7f40b43c11a19b234183a48c9ec8bc65872b99b0"},{"sha":"4a363c59f67b335fa6d3bfa1b865d72b7329934b","author":{"name":"dependabot-preview[bot]","email":"6a4c1c4838f800d1998274cd5234e1f65c55e90c@users.noreply.github.com"},"message":"Merge pull request #722 from kolebjak/dependabot/npm_and_yarn/server/babel/core-7.14.8","distinct":false,"url":"https://api.github.com/repos/kolebjak/game-of-clicks/commits/4a363c59f67b335fa6d3bfa1b865d72b7329934b"},{"sha":"7e11c2b5fc310c2ce6b5b0fdb496380182700bcb","author":{"name":"dependabot[bot]","email":"1c358da00a777d4e9898c1280ab801e2df165188@users.noreply.github.com"},"message":"Bump merge-deep from 3.0.2 to 3.0.3 in /frontend\n\nBumps [merge-deep](https://github.com/jonschlinkert/merge-deep) from 3.0.2 to 3.0.3.\n- [Release notes](https://github.com/jonschlinkert/merge-deep/releases)\n- [Commits](https://github.com/jonschlinkert/merge-deep/compare/3.0.2...3.0.3)\n\n---\nupdated-dependencies:\n- dependency-name: merge-deep\n dependency-type: indirect\n...\n\nSigned-off-by: dependabot[bot] ","distinct":true,"url":"https://api.github.com/repos/kolebjak/game-of-clicks/commits/7e11c2b5fc310c2ce6b5b0fdb496380182700bcb"}]},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792652","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7561706556,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8c098f0de3a8250482ca67affcb8b58d25480642","before":"fd13b1c5319c1f9ad4a95ec828ec6c11d514f219","commits":[{"sha":"8c098f0de3a8250482ca67affcb8b58d25480642","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/8c098f0de3a8250482ca67affcb8b58d25480642"}]},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792672","type":"PullRequestEvent","actor":{"id":9405495,"login":"livio-a","display_login":"livio-a","gravatar_id":"","url":"https://api.github.com/users/livio-a","avatar_url":"https://avatars.githubusercontent.com/u/9405495?"},"repo":{"id":247714750,"name":"caos/zitadel","url":"https://api.github.com/repos/caos/zitadel"},"payload":{"action":"closed","number":2052,"pull_request":{"url":"https://api.github.com/repos/caos/zitadel/pulls/2052","id":694094797,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MDk0Nzk3","html_url":"https://github.com/caos/zitadel/pull/2052","diff_url":"https://github.com/caos/zitadel/pull/2052.diff","patch_url":"https://github.com/caos/zitadel/pull/2052.patch","issue_url":"https://api.github.com/repos/caos/zitadel/issues/2052","number":2052,"state":"closed","locked":false,"title":"fix: update oidc lib to fix userinfo claims in id_token","user":{"login":"livio-a","id":9405495,"node_id":"MDQ6VXNlcjk0MDU0OTU=","avatar_url":"https://avatars.githubusercontent.com/u/9405495?v=4","gravatar_id":"","url":"https://api.github.com/users/livio-a","html_url":"https://github.com/livio-a","followers_url":"https://api.github.com/users/livio-a/followers","following_url":"https://api.github.com/users/livio-a/following{/other_user}","gists_url":"https://api.github.com/users/livio-a/gists{/gist_id}","starred_url":"https://api.github.com/users/livio-a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/livio-a/subscriptions","organizations_url":"https://api.github.com/users/livio-a/orgs","repos_url":"https://api.github.com/users/livio-a/repos","events_url":"https://api.github.com/users/livio-a/events{/privacy}","received_events_url":"https://api.github.com/users/livio-a/received_events","type":"User","site_admin":false},"body":"/closes #2051 ","created_at":"2021-07-21T06:40:12Z","updated_at":"2021-07-21T07:00:05Z","closed_at":"2021-07-21T07:00:05Z","merged_at":"2021-07-21T07:00:05Z","merge_commit_sha":"21001862ae749f8cc67bc308b9c6d68d3f97b76f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/caos/zitadel/pulls/2052/commits","review_comments_url":"https://api.github.com/repos/caos/zitadel/pulls/2052/comments","review_comment_url":"https://api.github.com/repos/caos/zitadel/pulls/comments{/number}","comments_url":"https://api.github.com/repos/caos/zitadel/issues/2052/comments","statuses_url":"https://api.github.com/repos/caos/zitadel/statuses/791b7df747836965c0e1aa879753edd04072034b","head":{"label":"caos:userinfo-claims","ref":"userinfo-claims","sha":"791b7df747836965c0e1aa879753edd04072034b","user":{"login":"caos","id":48989016,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ4OTg5MDE2","avatar_url":"https://avatars.githubusercontent.com/u/48989016?v=4","gravatar_id":"","url":"https://api.github.com/users/caos","html_url":"https://github.com/caos","followers_url":"https://api.github.com/users/caos/followers","following_url":"https://api.github.com/users/caos/following{/other_user}","gists_url":"https://api.github.com/users/caos/gists{/gist_id}","starred_url":"https://api.github.com/users/caos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caos/subscriptions","organizations_url":"https://api.github.com/users/caos/orgs","repos_url":"https://api.github.com/users/caos/repos","events_url":"https://api.github.com/users/caos/events{/privacy}","received_events_url":"https://api.github.com/users/caos/received_events","type":"Organization","site_admin":false},"repo":{"id":247714750,"node_id":"MDEwOlJlcG9zaXRvcnkyNDc3MTQ3NTA=","name":"zitadel","full_name":"caos/zitadel","private":false,"owner":{"login":"caos","id":48989016,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ4OTg5MDE2","avatar_url":"https://avatars.githubusercontent.com/u/48989016?v=4","gravatar_id":"","url":"https://api.github.com/users/caos","html_url":"https://github.com/caos","followers_url":"https://api.github.com/users/caos/followers","following_url":"https://api.github.com/users/caos/following{/other_user}","gists_url":"https://api.github.com/users/caos/gists{/gist_id}","starred_url":"https://api.github.com/users/caos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caos/subscriptions","organizations_url":"https://api.github.com/users/caos/orgs","repos_url":"https://api.github.com/users/caos/repos","events_url":"https://api.github.com/users/caos/events{/privacy}","received_events_url":"https://api.github.com/users/caos/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/caos/zitadel","description":"ZITADEL - Cloud Native Identity and Access Management","fork":false,"url":"https://api.github.com/repos/caos/zitadel","forks_url":"https://api.github.com/repos/caos/zitadel/forks","keys_url":"https://api.github.com/repos/caos/zitadel/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caos/zitadel/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caos/zitadel/teams","hooks_url":"https://api.github.com/repos/caos/zitadel/hooks","issue_events_url":"https://api.github.com/repos/caos/zitadel/issues/events{/number}","events_url":"https://api.github.com/repos/caos/zitadel/events","assignees_url":"https://api.github.com/repos/caos/zitadel/assignees{/user}","branches_url":"https://api.github.com/repos/caos/zitadel/branches{/branch}","tags_url":"https://api.github.com/repos/caos/zitadel/tags","blobs_url":"https://api.github.com/repos/caos/zitadel/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caos/zitadel/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caos/zitadel/git/refs{/sha}","trees_url":"https://api.github.com/repos/caos/zitadel/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caos/zitadel/statuses/{sha}","languages_url":"https://api.github.com/repos/caos/zitadel/languages","stargazers_url":"https://api.github.com/repos/caos/zitadel/stargazers","contributors_url":"https://api.github.com/repos/caos/zitadel/contributors","subscribers_url":"https://api.github.com/repos/caos/zitadel/subscribers","subscription_url":"https://api.github.com/repos/caos/zitadel/subscription","commits_url":"https://api.github.com/repos/caos/zitadel/commits{/sha}","git_commits_url":"https://api.github.com/repos/caos/zitadel/git/commits{/sha}","comments_url":"https://api.github.com/repos/caos/zitadel/comments{/number}","issue_comment_url":"https://api.github.com/repos/caos/zitadel/issues/comments{/number}","contents_url":"https://api.github.com/repos/caos/zitadel/contents/{+path}","compare_url":"https://api.github.com/repos/caos/zitadel/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caos/zitadel/merges","archive_url":"https://api.github.com/repos/caos/zitadel/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caos/zitadel/downloads","issues_url":"https://api.github.com/repos/caos/zitadel/issues{/number}","pulls_url":"https://api.github.com/repos/caos/zitadel/pulls{/number}","milestones_url":"https://api.github.com/repos/caos/zitadel/milestones{/number}","notifications_url":"https://api.github.com/repos/caos/zitadel/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caos/zitadel/labels{/name}","releases_url":"https://api.github.com/repos/caos/zitadel/releases{/id}","deployments_url":"https://api.github.com/repos/caos/zitadel/deployments","created_at":"2020-03-16T13:51:31Z","updated_at":"2021-07-20T13:47:52Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/caos/zitadel.git","ssh_url":"git@github.com:caos/zitadel.git","clone_url":"https://github.com/caos/zitadel.git","svn_url":"https://github.com/caos/zitadel","homepage":"https://zitadel.ch","size":192076,"stargazers_count":168,"watchers_count":168,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":15,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":93,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":15,"open_issues":93,"watchers":168,"default_branch":"main"}},"base":{"label":"caos:main","ref":"main","sha":"25c9d7371d7442401f837ff60c82b27b7acaef1c","user":{"login":"caos","id":48989016,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ4OTg5MDE2","avatar_url":"https://avatars.githubusercontent.com/u/48989016?v=4","gravatar_id":"","url":"https://api.github.com/users/caos","html_url":"https://github.com/caos","followers_url":"https://api.github.com/users/caos/followers","following_url":"https://api.github.com/users/caos/following{/other_user}","gists_url":"https://api.github.com/users/caos/gists{/gist_id}","starred_url":"https://api.github.com/users/caos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caos/subscriptions","organizations_url":"https://api.github.com/users/caos/orgs","repos_url":"https://api.github.com/users/caos/repos","events_url":"https://api.github.com/users/caos/events{/privacy}","received_events_url":"https://api.github.com/users/caos/received_events","type":"Organization","site_admin":false},"repo":{"id":247714750,"node_id":"MDEwOlJlcG9zaXRvcnkyNDc3MTQ3NTA=","name":"zitadel","full_name":"caos/zitadel","private":false,"owner":{"login":"caos","id":48989016,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ4OTg5MDE2","avatar_url":"https://avatars.githubusercontent.com/u/48989016?v=4","gravatar_id":"","url":"https://api.github.com/users/caos","html_url":"https://github.com/caos","followers_url":"https://api.github.com/users/caos/followers","following_url":"https://api.github.com/users/caos/following{/other_user}","gists_url":"https://api.github.com/users/caos/gists{/gist_id}","starred_url":"https://api.github.com/users/caos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caos/subscriptions","organizations_url":"https://api.github.com/users/caos/orgs","repos_url":"https://api.github.com/users/caos/repos","events_url":"https://api.github.com/users/caos/events{/privacy}","received_events_url":"https://api.github.com/users/caos/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/caos/zitadel","description":"ZITADEL - Cloud Native Identity and Access Management","fork":false,"url":"https://api.github.com/repos/caos/zitadel","forks_url":"https://api.github.com/repos/caos/zitadel/forks","keys_url":"https://api.github.com/repos/caos/zitadel/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caos/zitadel/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caos/zitadel/teams","hooks_url":"https://api.github.com/repos/caos/zitadel/hooks","issue_events_url":"https://api.github.com/repos/caos/zitadel/issues/events{/number}","events_url":"https://api.github.com/repos/caos/zitadel/events","assignees_url":"https://api.github.com/repos/caos/zitadel/assignees{/user}","branches_url":"https://api.github.com/repos/caos/zitadel/branches{/branch}","tags_url":"https://api.github.com/repos/caos/zitadel/tags","blobs_url":"https://api.github.com/repos/caos/zitadel/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caos/zitadel/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caos/zitadel/git/refs{/sha}","trees_url":"https://api.github.com/repos/caos/zitadel/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caos/zitadel/statuses/{sha}","languages_url":"https://api.github.com/repos/caos/zitadel/languages","stargazers_url":"https://api.github.com/repos/caos/zitadel/stargazers","contributors_url":"https://api.github.com/repos/caos/zitadel/contributors","subscribers_url":"https://api.github.com/repos/caos/zitadel/subscribers","subscription_url":"https://api.github.com/repos/caos/zitadel/subscription","commits_url":"https://api.github.com/repos/caos/zitadel/commits{/sha}","git_commits_url":"https://api.github.com/repos/caos/zitadel/git/commits{/sha}","comments_url":"https://api.github.com/repos/caos/zitadel/comments{/number}","issue_comment_url":"https://api.github.com/repos/caos/zitadel/issues/comments{/number}","contents_url":"https://api.github.com/repos/caos/zitadel/contents/{+path}","compare_url":"https://api.github.com/repos/caos/zitadel/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caos/zitadel/merges","archive_url":"https://api.github.com/repos/caos/zitadel/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caos/zitadel/downloads","issues_url":"https://api.github.com/repos/caos/zitadel/issues{/number}","pulls_url":"https://api.github.com/repos/caos/zitadel/pulls{/number}","milestones_url":"https://api.github.com/repos/caos/zitadel/milestones{/number}","notifications_url":"https://api.github.com/repos/caos/zitadel/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caos/zitadel/labels{/name}","releases_url":"https://api.github.com/repos/caos/zitadel/releases{/id}","deployments_url":"https://api.github.com/repos/caos/zitadel/deployments","created_at":"2020-03-16T13:51:31Z","updated_at":"2021-07-20T13:47:52Z","pushed_at":"2021-07-21T07:00:05Z","git_url":"git://github.com/caos/zitadel.git","ssh_url":"git@github.com:caos/zitadel.git","clone_url":"https://github.com/caos/zitadel.git","svn_url":"https://github.com/caos/zitadel","homepage":"https://zitadel.ch","size":192076,"stargazers_count":168,"watchers_count":168,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":15,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":93,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":15,"open_issues":93,"watchers":168,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/caos/zitadel/pulls/2052"},"html":{"href":"https://github.com/caos/zitadel/pull/2052"},"issue":{"href":"https://api.github.com/repos/caos/zitadel/issues/2052"},"comments":{"href":"https://api.github.com/repos/caos/zitadel/issues/2052/comments"},"review_comments":{"href":"https://api.github.com/repos/caos/zitadel/pulls/2052/comments"},"review_comment":{"href":"https://api.github.com/repos/caos/zitadel/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/caos/zitadel/pulls/2052/commits"},"statuses":{"href":"https://api.github.com/repos/caos/zitadel/statuses/791b7df747836965c0e1aa879753edd04072034b"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"livio-a","id":9405495,"node_id":"MDQ6VXNlcjk0MDU0OTU=","avatar_url":"https://avatars.githubusercontent.com/u/9405495?v=4","gravatar_id":"","url":"https://api.github.com/users/livio-a","html_url":"https://github.com/livio-a","followers_url":"https://api.github.com/users/livio-a/followers","following_url":"https://api.github.com/users/livio-a/following{/other_user}","gists_url":"https://api.github.com/users/livio-a/gists{/gist_id}","starred_url":"https://api.github.com/users/livio-a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/livio-a/subscriptions","organizations_url":"https://api.github.com/users/livio-a/orgs","repos_url":"https://api.github.com/users/livio-a/repos","events_url":"https://api.github.com/users/livio-a/events{/privacy}","received_events_url":"https://api.github.com/users/livio-a/received_events","type":"User","site_admin":false},"comments":2,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":3,"deletions":3,"changed_files":2}},"public":true,"created_at":"2021-07-21T07:00:06Z","org":{"id":48989016,"login":"caos","gravatar_id":"","url":"https://api.github.com/orgs/caos","avatar_url":"https://avatars.githubusercontent.com/u/48989016?"}} +{"id":"17244792673","type":"PushEvent","actor":{"id":47466906,"login":"akjadon","display_login":"akjadon","gravatar_id":"","url":"https://api.github.com/users/akjadon","avatar_url":"https://avatars.githubusercontent.com/u/47466906?"},"repo":{"id":191207450,"name":"akjadon/HH","url":"https://api.github.com/repos/akjadon/HH"},"payload":{"push_id":7561706551,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"41d7ff8d4d593888363ba46dd31c357a74252e97","before":"9da22a1e1b74a25956bd3f2ab1f090840f2d9c90","commits":[{"sha":"41d7ff8d4d593888363ba46dd31c357a74252e97","author":{"name":"Anil Jadon","email":"ca0c2591d573677204b744617f3dee79ed1b3452@users.noreply.github.com"},"message":"Rename class_demo/w0.0/827_m1_demo4_v1.0.ipynb to class_demo/w0.0/ex0.0/827_m1_demo4_v1.0.ipynb","distinct":true,"url":"https://api.github.com/repos/akjadon/HH/commits/41d7ff8d4d593888363ba46dd31c357a74252e97"}]},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792677","type":"PushEvent","actor":{"id":3379460,"login":"beanslee2012","display_login":"beanslee2012","gravatar_id":"","url":"https://api.github.com/users/beanslee2012","avatar_url":"https://avatars.githubusercontent.com/u/3379460?"},"repo":{"id":215003385,"name":"beanslee2012/games","url":"https://api.github.com/repos/beanslee2012/games"},"payload":{"push_id":7561706562,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"ae21fd47cc607d28e3379bc786e8635e0c4ef896","before":"d597a1ea1c79280206699e944c9b1dd315585ded","commits":[{"sha":"ae21fd47cc607d28e3379bc786e8635e0c4ef896","author":{"name":"beanslee2012","email":"9aadcc12fe6ab8c01e6245204a102e9e34317220@gmail.com"},"message":"daily update","distinct":true,"url":"https://api.github.com/repos/beanslee2012/games/commits/ae21fd47cc607d28e3379bc786e8635e0c4ef896"}]},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792680","type":"CreateEvent","actor":{"id":791757,"login":"jmirtsch","display_login":"jmirtsch","gravatar_id":"","url":"https://api.github.com/users/jmirtsch","avatar_url":"https://avatars.githubusercontent.com/u/791757?"},"repo":{"id":346959145,"name":"jmirtsch/IFC-Specification","url":"https://api.github.com/repos/jmirtsch/IFC-Specification"},"payload":{"ref":"RailProperties_IfcFlowInstrument","ref_type":"branch","master_branch":"main","description":"IfcDoc baseline to produce the documentation","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792686","type":"DeleteEvent","actor":{"id":1005065,"login":"DeepDiver1975","display_login":"DeepDiver1975","gravatar_id":"","url":"https://api.github.com/users/DeepDiver1975","avatar_url":"https://avatars.githubusercontent.com/u/1005065?"},"repo":{"id":386559157,"name":"DeepDiver1975/laravel-cursor-paginator","url":"https://api.github.com/repos/DeepDiver1975/laravel-cursor-paginator"},"payload":{"ref":"feat/dependabot","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792688","type":"WatchEvent","actor":{"id":10879414,"login":"NagleZhang","display_login":"NagleZhang","gravatar_id":"","url":"https://api.github.com/users/NagleZhang","avatar_url":"https://avatars.githubusercontent.com/u/10879414?"},"repo":{"id":211085593,"name":"redguardtoo/company-ctags","url":"https://api.github.com/repos/redguardtoo/company-ctags"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792692","type":"PullRequestReviewEvent","actor":{"id":2418812,"login":"Smurf-IV","display_login":"Smurf-IV","gravatar_id":"","url":"https://api.github.com/users/Smurf-IV","avatar_url":"https://avatars.githubusercontent.com/u/2418812?"},"repo":{"id":243247330,"name":"Krypton-Suite/Standard-Toolkit","url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit"},"payload":{"action":"created","review":{"id":711317412,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzE3NDEy","user":{"login":"Smurf-IV","id":2418812,"node_id":"MDQ6VXNlcjI0MTg4MTI=","avatar_url":"https://avatars.githubusercontent.com/u/2418812?v=4","gravatar_id":"","url":"https://api.github.com/users/Smurf-IV","html_url":"https://github.com/Smurf-IV","followers_url":"https://api.github.com/users/Smurf-IV/followers","following_url":"https://api.github.com/users/Smurf-IV/following{/other_user}","gists_url":"https://api.github.com/users/Smurf-IV/gists{/gist_id}","starred_url":"https://api.github.com/users/Smurf-IV/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Smurf-IV/subscriptions","organizations_url":"https://api.github.com/users/Smurf-IV/orgs","repos_url":"https://api.github.com/users/Smurf-IV/repos","events_url":"https://api.github.com/users/Smurf-IV/events{/privacy}","received_events_url":"https://api.github.com/users/Smurf-IV/received_events","type":"User","site_admin":false},"body":null,"commit_id":"e174641d5578c95967e8df67aea55a15ad919ac7","submitted_at":"2021-07-21T07:00:05Z","state":"commented","html_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199#pullrequestreview-711317412","pull_request_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199#pullrequestreview-711317412"},"pull_request":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199"}}},"pull_request":{"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199","id":692062634,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyMDYyNjM0","html_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199","diff_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199.diff","patch_url":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199.patch","issue_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199","number":199,"state":"open","locked":false,"title":"Alpha fr197","user":{"login":"Wagnerp","id":949607,"node_id":"MDQ6VXNlcjk0OTYwNw==","avatar_url":"https://avatars.githubusercontent.com/u/949607?v=4","gravatar_id":"","url":"https://api.github.com/users/Wagnerp","html_url":"https://github.com/Wagnerp","followers_url":"https://api.github.com/users/Wagnerp/followers","following_url":"https://api.github.com/users/Wagnerp/following{/other_user}","gists_url":"https://api.github.com/users/Wagnerp/gists{/gist_id}","starred_url":"https://api.github.com/users/Wagnerp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Wagnerp/subscriptions","organizations_url":"https://api.github.com/users/Wagnerp/orgs","repos_url":"https://api.github.com/users/Wagnerp/repos","events_url":"https://api.github.com/users/Wagnerp/events{/privacy}","received_events_url":"https://api.github.com/users/Wagnerp/received_events","type":"User","site_admin":false},"body":"#197 pt. 1","created_at":"2021-07-18T09:59:40Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e7e5bc21581f2760133e1342dc94bf883113274a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/commits","review_comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/comments","review_comment_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199/comments","statuses_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/6045c9a6424e198f542c84a97ff094a4952c866d","head":{"label":"Krypton-Suite:alpha-fr197","ref":"alpha-fr197","sha":"6045c9a6424e198f542c84a97ff094a4952c866d","user":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"repo":{"id":243247330,"node_id":"MDEwOlJlcG9zaXRvcnkyNDMyNDczMzA=","name":"Standard-Toolkit","full_name":"Krypton-Suite/Standard-Toolkit","private":false,"owner":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Krypton-Suite/Standard-Toolkit","description":"An update to Component factory's krypton toolkit to support .NET Framework 3.5 to .NET Core/.NET","fork":false,"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit","forks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/forks","keys_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/teams","hooks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/hooks","issue_events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/events{/number}","events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/events","assignees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/assignees{/user}","branches_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/branches{/branch}","tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/tags","blobs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/{sha}","languages_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/languages","stargazers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/stargazers","contributors_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contributors","subscribers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscribers","subscription_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscription","commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contents/{+path}","compare_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/merges","archive_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/downloads","issues_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues{/number}","pulls_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls{/number}","milestones_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/milestones{/number}","notifications_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/labels{/name}","releases_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/releases{/id}","deployments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/deployments","created_at":"2020-02-26T11:33:33Z","updated_at":"2021-07-20T17:53:46Z","pushed_at":"2021-07-20T17:47:02Z","git_url":"git://github.com/Krypton-Suite/Standard-Toolkit.git","ssh_url":"git@github.com:Krypton-Suite/Standard-Toolkit.git","clone_url":"https://github.com/Krypton-Suite/Standard-Toolkit.git","svn_url":"https://github.com/Krypton-Suite/Standard-Toolkit","homepage":"","size":12480,"stargazers_count":84,"watchers_count":84,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":59,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":12,"open_issues":59,"watchers":84,"default_branch":"master"}},"base":{"label":"Krypton-Suite:alpha","ref":"alpha","sha":"4c6b6a3d8990d1bc888f01369b2348bd909dda72","user":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"repo":{"id":243247330,"node_id":"MDEwOlJlcG9zaXRvcnkyNDMyNDczMzA=","name":"Standard-Toolkit","full_name":"Krypton-Suite/Standard-Toolkit","private":false,"owner":{"login":"Krypton-Suite","id":61501509,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTAxNTA5","avatar_url":"https://avatars.githubusercontent.com/u/61501509?v=4","gravatar_id":"","url":"https://api.github.com/users/Krypton-Suite","html_url":"https://github.com/Krypton-Suite","followers_url":"https://api.github.com/users/Krypton-Suite/followers","following_url":"https://api.github.com/users/Krypton-Suite/following{/other_user}","gists_url":"https://api.github.com/users/Krypton-Suite/gists{/gist_id}","starred_url":"https://api.github.com/users/Krypton-Suite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Krypton-Suite/subscriptions","organizations_url":"https://api.github.com/users/Krypton-Suite/orgs","repos_url":"https://api.github.com/users/Krypton-Suite/repos","events_url":"https://api.github.com/users/Krypton-Suite/events{/privacy}","received_events_url":"https://api.github.com/users/Krypton-Suite/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Krypton-Suite/Standard-Toolkit","description":"An update to Component factory's krypton toolkit to support .NET Framework 3.5 to .NET Core/.NET","fork":false,"url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit","forks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/forks","keys_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/teams","hooks_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/hooks","issue_events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/events{/number}","events_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/events","assignees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/assignees{/user}","branches_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/branches{/branch}","tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/tags","blobs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/{sha}","languages_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/languages","stargazers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/stargazers","contributors_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contributors","subscribers_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscribers","subscription_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/subscription","commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/contents/{+path}","compare_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/merges","archive_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/downloads","issues_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues{/number}","pulls_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls{/number}","milestones_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/milestones{/number}","notifications_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/labels{/name}","releases_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/releases{/id}","deployments_url":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/deployments","created_at":"2020-02-26T11:33:33Z","updated_at":"2021-07-20T17:53:46Z","pushed_at":"2021-07-20T17:47:02Z","git_url":"git://github.com/Krypton-Suite/Standard-Toolkit.git","ssh_url":"git@github.com:Krypton-Suite/Standard-Toolkit.git","clone_url":"https://github.com/Krypton-Suite/Standard-Toolkit.git","svn_url":"https://github.com/Krypton-Suite/Standard-Toolkit","homepage":"","size":12480,"stargazers_count":84,"watchers_count":84,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":59,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":12,"open_issues":59,"watchers":84,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199"},"html":{"href":"https://github.com/Krypton-Suite/Standard-Toolkit/pull/199"},"issue":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199"},"comments":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/issues/199/comments"},"review_comments":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/comments"},"review_comment":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/pulls/199/commits"},"statuses":{"href":"https://api.github.com/repos/Krypton-Suite/Standard-Toolkit/statuses/6045c9a6424e198f542c84a97ff094a4952c866d"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T07:00:06Z","org":{"id":61501509,"login":"Krypton-Suite","gravatar_id":"","url":"https://api.github.com/orgs/Krypton-Suite","avatar_url":"https://avatars.githubusercontent.com/u/61501509?"}} +{"id":"17244792697","type":"PullRequestEvent","actor":{"id":48777463,"login":"parkinhee","display_login":"parkinhee","gravatar_id":"","url":"https://api.github.com/users/parkinhee","avatar_url":"https://avatars.githubusercontent.com/u/48777463?"},"repo":{"id":379487763,"name":"Taesun0727/wisevill","url":"https://api.github.com/repos/Taesun0727/wisevill"},"payload":{"action":"opened","number":110,"pull_request":{"url":"https://api.github.com/repos/Taesun0727/wisevill/pulls/110","id":694104913,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0OTEz","html_url":"https://github.com/Taesun0727/wisevill/pull/110","diff_url":"https://github.com/Taesun0727/wisevill/pull/110.diff","patch_url":"https://github.com/Taesun0727/wisevill/pull/110.patch","issue_url":"https://api.github.com/repos/Taesun0727/wisevill/issues/110","number":110,"state":"open","locked":false,"title":"Ttt","user":{"login":"parkinhee","id":48777463,"node_id":"MDQ6VXNlcjQ4Nzc3NDYz","avatar_url":"https://avatars.githubusercontent.com/u/48777463?v=4","gravatar_id":"","url":"https://api.github.com/users/parkinhee","html_url":"https://github.com/parkinhee","followers_url":"https://api.github.com/users/parkinhee/followers","following_url":"https://api.github.com/users/parkinhee/following{/other_user}","gists_url":"https://api.github.com/users/parkinhee/gists{/gist_id}","starred_url":"https://api.github.com/users/parkinhee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/parkinhee/subscriptions","organizations_url":"https://api.github.com/users/parkinhee/orgs","repos_url":"https://api.github.com/users/parkinhee/repos","events_url":"https://api.github.com/users/parkinhee/events{/privacy}","received_events_url":"https://api.github.com/users/parkinhee/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Taesun0727/wisevill/pulls/110/commits","review_comments_url":"https://api.github.com/repos/Taesun0727/wisevill/pulls/110/comments","review_comment_url":"https://api.github.com/repos/Taesun0727/wisevill/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Taesun0727/wisevill/issues/110/comments","statuses_url":"https://api.github.com/repos/Taesun0727/wisevill/statuses/83bfc6b84dd76d18ebac07976b1dce008c20a067","head":{"label":"Taesun0727:ttt","ref":"ttt","sha":"83bfc6b84dd76d18ebac07976b1dce008c20a067","user":{"login":"Taesun0727","id":77261999,"node_id":"MDQ6VXNlcjc3MjYxOTk5","avatar_url":"https://avatars.githubusercontent.com/u/77261999?v=4","gravatar_id":"","url":"https://api.github.com/users/Taesun0727","html_url":"https://github.com/Taesun0727","followers_url":"https://api.github.com/users/Taesun0727/followers","following_url":"https://api.github.com/users/Taesun0727/following{/other_user}","gists_url":"https://api.github.com/users/Taesun0727/gists{/gist_id}","starred_url":"https://api.github.com/users/Taesun0727/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Taesun0727/subscriptions","organizations_url":"https://api.github.com/users/Taesun0727/orgs","repos_url":"https://api.github.com/users/Taesun0727/repos","events_url":"https://api.github.com/users/Taesun0727/events{/privacy}","received_events_url":"https://api.github.com/users/Taesun0727/received_events","type":"User","site_admin":false},"repo":{"id":379487763,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk0ODc3NjM=","name":"wisevill","full_name":"Taesun0727/wisevill","private":false,"owner":{"login":"Taesun0727","id":77261999,"node_id":"MDQ6VXNlcjc3MjYxOTk5","avatar_url":"https://avatars.githubusercontent.com/u/77261999?v=4","gravatar_id":"","url":"https://api.github.com/users/Taesun0727","html_url":"https://github.com/Taesun0727","followers_url":"https://api.github.com/users/Taesun0727/followers","following_url":"https://api.github.com/users/Taesun0727/following{/other_user}","gists_url":"https://api.github.com/users/Taesun0727/gists{/gist_id}","starred_url":"https://api.github.com/users/Taesun0727/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Taesun0727/subscriptions","organizations_url":"https://api.github.com/users/Taesun0727/orgs","repos_url":"https://api.github.com/users/Taesun0727/repos","events_url":"https://api.github.com/users/Taesun0727/events{/privacy}","received_events_url":"https://api.github.com/users/Taesun0727/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Taesun0727/wisevill","description":null,"fork":false,"url":"https://api.github.com/repos/Taesun0727/wisevill","forks_url":"https://api.github.com/repos/Taesun0727/wisevill/forks","keys_url":"https://api.github.com/repos/Taesun0727/wisevill/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Taesun0727/wisevill/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Taesun0727/wisevill/teams","hooks_url":"https://api.github.com/repos/Taesun0727/wisevill/hooks","issue_events_url":"https://api.github.com/repos/Taesun0727/wisevill/issues/events{/number}","events_url":"https://api.github.com/repos/Taesun0727/wisevill/events","assignees_url":"https://api.github.com/repos/Taesun0727/wisevill/assignees{/user}","branches_url":"https://api.github.com/repos/Taesun0727/wisevill/branches{/branch}","tags_url":"https://api.github.com/repos/Taesun0727/wisevill/tags","blobs_url":"https://api.github.com/repos/Taesun0727/wisevill/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Taesun0727/wisevill/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Taesun0727/wisevill/git/refs{/sha}","trees_url":"https://api.github.com/repos/Taesun0727/wisevill/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Taesun0727/wisevill/statuses/{sha}","languages_url":"https://api.github.com/repos/Taesun0727/wisevill/languages","stargazers_url":"https://api.github.com/repos/Taesun0727/wisevill/stargazers","contributors_url":"https://api.github.com/repos/Taesun0727/wisevill/contributors","subscribers_url":"https://api.github.com/repos/Taesun0727/wisevill/subscribers","subscription_url":"https://api.github.com/repos/Taesun0727/wisevill/subscription","commits_url":"https://api.github.com/repos/Taesun0727/wisevill/commits{/sha}","git_commits_url":"https://api.github.com/repos/Taesun0727/wisevill/git/commits{/sha}","comments_url":"https://api.github.com/repos/Taesun0727/wisevill/comments{/number}","issue_comment_url":"https://api.github.com/repos/Taesun0727/wisevill/issues/comments{/number}","contents_url":"https://api.github.com/repos/Taesun0727/wisevill/contents/{+path}","compare_url":"https://api.github.com/repos/Taesun0727/wisevill/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Taesun0727/wisevill/merges","archive_url":"https://api.github.com/repos/Taesun0727/wisevill/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Taesun0727/wisevill/downloads","issues_url":"https://api.github.com/repos/Taesun0727/wisevill/issues{/number}","pulls_url":"https://api.github.com/repos/Taesun0727/wisevill/pulls{/number}","milestones_url":"https://api.github.com/repos/Taesun0727/wisevill/milestones{/number}","notifications_url":"https://api.github.com/repos/Taesun0727/wisevill/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Taesun0727/wisevill/labels{/name}","releases_url":"https://api.github.com/repos/Taesun0727/wisevill/releases{/id}","deployments_url":"https://api.github.com/repos/Taesun0727/wisevill/deployments","created_at":"2021-06-23T05:19:22Z","updated_at":"2021-07-21T04:52:00Z","pushed_at":"2021-07-21T06:59:56Z","git_url":"git://github.com/Taesun0727/wisevill.git","ssh_url":"git@github.com:Taesun0727/wisevill.git","clone_url":"https://github.com/Taesun0727/wisevill.git","svn_url":"https://github.com/Taesun0727/wisevill","homepage":null,"size":1329,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"base":{"label":"Taesun0727:master","ref":"master","sha":"3602731368c0b1ceb9287ece9b9341aeaf68ac20","user":{"login":"Taesun0727","id":77261999,"node_id":"MDQ6VXNlcjc3MjYxOTk5","avatar_url":"https://avatars.githubusercontent.com/u/77261999?v=4","gravatar_id":"","url":"https://api.github.com/users/Taesun0727","html_url":"https://github.com/Taesun0727","followers_url":"https://api.github.com/users/Taesun0727/followers","following_url":"https://api.github.com/users/Taesun0727/following{/other_user}","gists_url":"https://api.github.com/users/Taesun0727/gists{/gist_id}","starred_url":"https://api.github.com/users/Taesun0727/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Taesun0727/subscriptions","organizations_url":"https://api.github.com/users/Taesun0727/orgs","repos_url":"https://api.github.com/users/Taesun0727/repos","events_url":"https://api.github.com/users/Taesun0727/events{/privacy}","received_events_url":"https://api.github.com/users/Taesun0727/received_events","type":"User","site_admin":false},"repo":{"id":379487763,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk0ODc3NjM=","name":"wisevill","full_name":"Taesun0727/wisevill","private":false,"owner":{"login":"Taesun0727","id":77261999,"node_id":"MDQ6VXNlcjc3MjYxOTk5","avatar_url":"https://avatars.githubusercontent.com/u/77261999?v=4","gravatar_id":"","url":"https://api.github.com/users/Taesun0727","html_url":"https://github.com/Taesun0727","followers_url":"https://api.github.com/users/Taesun0727/followers","following_url":"https://api.github.com/users/Taesun0727/following{/other_user}","gists_url":"https://api.github.com/users/Taesun0727/gists{/gist_id}","starred_url":"https://api.github.com/users/Taesun0727/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Taesun0727/subscriptions","organizations_url":"https://api.github.com/users/Taesun0727/orgs","repos_url":"https://api.github.com/users/Taesun0727/repos","events_url":"https://api.github.com/users/Taesun0727/events{/privacy}","received_events_url":"https://api.github.com/users/Taesun0727/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Taesun0727/wisevill","description":null,"fork":false,"url":"https://api.github.com/repos/Taesun0727/wisevill","forks_url":"https://api.github.com/repos/Taesun0727/wisevill/forks","keys_url":"https://api.github.com/repos/Taesun0727/wisevill/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Taesun0727/wisevill/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Taesun0727/wisevill/teams","hooks_url":"https://api.github.com/repos/Taesun0727/wisevill/hooks","issue_events_url":"https://api.github.com/repos/Taesun0727/wisevill/issues/events{/number}","events_url":"https://api.github.com/repos/Taesun0727/wisevill/events","assignees_url":"https://api.github.com/repos/Taesun0727/wisevill/assignees{/user}","branches_url":"https://api.github.com/repos/Taesun0727/wisevill/branches{/branch}","tags_url":"https://api.github.com/repos/Taesun0727/wisevill/tags","blobs_url":"https://api.github.com/repos/Taesun0727/wisevill/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Taesun0727/wisevill/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Taesun0727/wisevill/git/refs{/sha}","trees_url":"https://api.github.com/repos/Taesun0727/wisevill/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Taesun0727/wisevill/statuses/{sha}","languages_url":"https://api.github.com/repos/Taesun0727/wisevill/languages","stargazers_url":"https://api.github.com/repos/Taesun0727/wisevill/stargazers","contributors_url":"https://api.github.com/repos/Taesun0727/wisevill/contributors","subscribers_url":"https://api.github.com/repos/Taesun0727/wisevill/subscribers","subscription_url":"https://api.github.com/repos/Taesun0727/wisevill/subscription","commits_url":"https://api.github.com/repos/Taesun0727/wisevill/commits{/sha}","git_commits_url":"https://api.github.com/repos/Taesun0727/wisevill/git/commits{/sha}","comments_url":"https://api.github.com/repos/Taesun0727/wisevill/comments{/number}","issue_comment_url":"https://api.github.com/repos/Taesun0727/wisevill/issues/comments{/number}","contents_url":"https://api.github.com/repos/Taesun0727/wisevill/contents/{+path}","compare_url":"https://api.github.com/repos/Taesun0727/wisevill/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Taesun0727/wisevill/merges","archive_url":"https://api.github.com/repos/Taesun0727/wisevill/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Taesun0727/wisevill/downloads","issues_url":"https://api.github.com/repos/Taesun0727/wisevill/issues{/number}","pulls_url":"https://api.github.com/repos/Taesun0727/wisevill/pulls{/number}","milestones_url":"https://api.github.com/repos/Taesun0727/wisevill/milestones{/number}","notifications_url":"https://api.github.com/repos/Taesun0727/wisevill/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Taesun0727/wisevill/labels{/name}","releases_url":"https://api.github.com/repos/Taesun0727/wisevill/releases{/id}","deployments_url":"https://api.github.com/repos/Taesun0727/wisevill/deployments","created_at":"2021-06-23T05:19:22Z","updated_at":"2021-07-21T04:52:00Z","pushed_at":"2021-07-21T06:59:56Z","git_url":"git://github.com/Taesun0727/wisevill.git","ssh_url":"git@github.com:Taesun0727/wisevill.git","clone_url":"https://github.com/Taesun0727/wisevill.git","svn_url":"https://github.com/Taesun0727/wisevill","homepage":null,"size":1329,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Taesun0727/wisevill/pulls/110"},"html":{"href":"https://github.com/Taesun0727/wisevill/pull/110"},"issue":{"href":"https://api.github.com/repos/Taesun0727/wisevill/issues/110"},"comments":{"href":"https://api.github.com/repos/Taesun0727/wisevill/issues/110/comments"},"review_comments":{"href":"https://api.github.com/repos/Taesun0727/wisevill/pulls/110/comments"},"review_comment":{"href":"https://api.github.com/repos/Taesun0727/wisevill/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Taesun0727/wisevill/pulls/110/commits"},"statuses":{"href":"https://api.github.com/repos/Taesun0727/wisevill/statuses/83bfc6b84dd76d18ebac07976b1dce008c20a067"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":9,"deletions":6,"changed_files":3}},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17244792702","type":"PullRequestEvent","actor":{"id":11707729,"login":"gabrieldonadel","display_login":"gabrieldonadel","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","avatar_url":"https://avatars.githubusercontent.com/u/11707729?"},"repo":{"id":387916158,"name":"gabrieldonadel/pull-requests-limits","url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits"},"payload":{"action":"opened","number":4614,"pull_request":{"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/4614","id":694104914,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTA0OTE0","html_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/4614","diff_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/4614.diff","patch_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/4614.patch","issue_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/4614","number":4614,"state":"open","locked":false,"title":"Add file 4636.txt","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"body":null,"created_at":"2021-07-21T07:00:05Z","updated_at":"2021-07-21T07:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/4614/commits","review_comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/4614/comments","review_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/4614/comments","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/cc419e1088340655ea9a7154225749984d74cee6","head":{"label":"gabrieldonadel:4636","ref":"4636","sha":"cc419e1088340655ea9a7154225749984d74cee6","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"repo":{"id":387916158,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MTYxNTg=","name":"pull-requests-limits","full_name":"gabrieldonadel/pull-requests-limits","private":false,"owner":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gabrieldonadel/pull-requests-limits","description":"Testing Github Pull requests limits","fork":false,"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits","forks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/forks","keys_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/teams","hooks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/hooks","issue_events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/events{/number}","events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/events","assignees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/assignees{/user}","branches_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/branches{/branch}","tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/tags","blobs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/refs{/sha}","trees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/{sha}","languages_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/languages","stargazers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/stargazers","contributors_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contributors","subscribers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscribers","subscription_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscription","commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/commits{/sha}","git_commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/commits{/sha}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/comments{/number}","issue_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/comments{/number}","contents_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contents/{+path}","compare_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/merges","archive_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/downloads","issues_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues{/number}","pulls_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls{/number}","milestones_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/milestones{/number}","notifications_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/labels{/name}","releases_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/releases{/id}","deployments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/deployments","created_at":"2021-07-20T21:17:40Z","updated_at":"2021-07-21T01:36:14Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/gabrieldonadel/pull-requests-limits.git","ssh_url":"git@github.com:gabrieldonadel/pull-requests-limits.git","clone_url":"https://github.com/gabrieldonadel/pull-requests-limits.git","svn_url":"https://github.com/gabrieldonadel/pull-requests-limits","homepage":null,"size":968,"stargazers_count":1,"watchers_count":1,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4612,"license":null,"forks":0,"open_issues":4612,"watchers":1,"default_branch":"master"}},"base":{"label":"gabrieldonadel:master","ref":"master","sha":"d82cf4fd9e97a60cfebab6b8f7f85f9f8380b29b","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"repo":{"id":387916158,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MTYxNTg=","name":"pull-requests-limits","full_name":"gabrieldonadel/pull-requests-limits","private":false,"owner":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gabrieldonadel/pull-requests-limits","description":"Testing Github Pull requests limits","fork":false,"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits","forks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/forks","keys_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/teams","hooks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/hooks","issue_events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/events{/number}","events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/events","assignees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/assignees{/user}","branches_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/branches{/branch}","tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/tags","blobs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/refs{/sha}","trees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/{sha}","languages_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/languages","stargazers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/stargazers","contributors_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contributors","subscribers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscribers","subscription_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscription","commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/commits{/sha}","git_commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/commits{/sha}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/comments{/number}","issue_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/comments{/number}","contents_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contents/{+path}","compare_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/merges","archive_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/downloads","issues_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues{/number}","pulls_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls{/number}","milestones_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/milestones{/number}","notifications_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/labels{/name}","releases_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/releases{/id}","deployments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/deployments","created_at":"2021-07-20T21:17:40Z","updated_at":"2021-07-21T01:36:14Z","pushed_at":"2021-07-21T07:00:04Z","git_url":"git://github.com/gabrieldonadel/pull-requests-limits.git","ssh_url":"git@github.com:gabrieldonadel/pull-requests-limits.git","clone_url":"https://github.com/gabrieldonadel/pull-requests-limits.git","svn_url":"https://github.com/gabrieldonadel/pull-requests-limits","homepage":null,"size":968,"stargazers_count":1,"watchers_count":1,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4612,"license":null,"forks":0,"open_issues":4612,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/4614"},"html":{"href":"https://github.com/gabrieldonadel/pull-requests-limits/pull/4614"},"issue":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/4614"},"comments":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/4614/comments"},"review_comments":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/4614/comments"},"review_comment":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/4614/commits"},"statuses":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/cc419e1088340655ea9a7154225749984d74cee6"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":0,"changed_files":1}},"public":true,"created_at":"2021-07-21T07:00:06Z"} +{"id":"17245514528","type":"PushEvent","actor":{"id":17990689,"login":"Hoversquid","display_login":"Hoversquid","gravatar_id":"","url":"https://api.github.com/users/Hoversquid","avatar_url":"https://avatars.githubusercontent.com/u/17990689?"},"repo":{"id":383327745,"name":"Hoversquid/VQGAN_CLIP_Z_Quantize","url":"https://api.github.com/repos/Hoversquid/VQGAN_CLIP_Z_Quantize"},"payload":{"push_id":7562054919,"size":1,"distinct_size":1,"ref":"refs/heads/video","head":"4242bc901557708da873694bb4eff3111a41b0e7","before":"fb8af0f86fcbd34c76d43c0cdc061dcb96e09c4d","commits":[{"sha":"4242bc901557708da873694bb4eff3111a41b0e7","author":{"name":"Hoversquid","email":"a37c47d728a80ccc7021f90a819de63984067c66@gmail.com"},"message":"fix combined path","distinct":true,"url":"https://api.github.com/repos/Hoversquid/VQGAN_CLIP_Z_Quantize/commits/4242bc901557708da873694bb4eff3111a41b0e7"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514533","type":"WatchEvent","actor":{"id":76926509,"login":"ArchivvonJang","display_login":"ArchivvonJang","gravatar_id":"","url":"https://api.github.com/users/ArchivvonJang","avatar_url":"https://avatars.githubusercontent.com/u/76926509?"},"repo":{"id":93291681,"name":"JaeYeopHan/Interview_Question_for_Beginner","url":"https://api.github.com/repos/JaeYeopHan/Interview_Question_for_Beginner"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514542","type":"PushEvent","actor":{"id":1093032,"login":"zwacky","display_login":"zwacky","gravatar_id":"","url":"https://api.github.com/users/zwacky","avatar_url":"https://avatars.githubusercontent.com/u/1093032?"},"repo":{"id":289489895,"name":"zwacky/wicki-io","url":"https://api.github.com/repos/zwacky/wicki-io"},"payload":{"push_id":7562054916,"size":1,"distinct_size":1,"ref":"refs/heads/gh-pages","head":"37b70cd5428b8dcc6a5e0f632890fc27dc847c5d","before":"a8827932073c9b086b2f0cf82e93d701f2a628d9","commits":[{"sha":"37b70cd5428b8dcc6a5e0f632890fc27dc847c5d","author":{"name":"Simon Wicki","email":"196e9f704a07d1e86724ae31cdc6341465df66a5@justwatch.com"},"message":"🚀 publish to gh-pages","distinct":true,"url":"https://api.github.com/repos/zwacky/wicki-io/commits/37b70cd5428b8dcc6a5e0f632890fc27dc847c5d"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514568","type":"PullRequestEvent","actor":{"id":80700348,"login":"githubvaladm","display_login":"githubvaladm","gravatar_id":"","url":"https://api.github.com/users/githubvaladm","avatar_url":"https://avatars.githubusercontent.com/u/80700348?"},"repo":{"id":388040199,"name":"philips-validation-beta/github-validation","url":"https://api.github.com/repos/philips-validation-beta/github-validation"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/1","id":694141259,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxMjU5","html_url":"https://github.com/philips-validation-beta/github-validation/pull/1","diff_url":"https://github.com/philips-validation-beta/github-validation/pull/1.diff","patch_url":"https://github.com/philips-validation-beta/github-validation/pull/1.patch","issue_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/1","number":1,"state":"closed","locked":false,"title":"TC13:changed readme file","user":{"login":"githubvaladm","id":80700348,"node_id":"MDQ6VXNlcjgwNzAwMzQ4","avatar_url":"https://avatars.githubusercontent.com/u/80700348?v=4","gravatar_id":"","url":"https://api.github.com/users/githubvaladm","html_url":"https://github.com/githubvaladm","followers_url":"https://api.github.com/users/githubvaladm/followers","following_url":"https://api.github.com/users/githubvaladm/following{/other_user}","gists_url":"https://api.github.com/users/githubvaladm/gists{/gist_id}","starred_url":"https://api.github.com/users/githubvaladm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/githubvaladm/subscriptions","organizations_url":"https://api.github.com/users/githubvaladm/orgs","repos_url":"https://api.github.com/users/githubvaladm/repos","events_url":"https://api.github.com/users/githubvaladm/events{/privacy}","received_events_url":"https://api.github.com/users/githubvaladm/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:59:23Z","updated_at":"2021-07-21T07:59:59Z","closed_at":"2021-07-21T07:59:59Z","merged_at":"2021-07-21T07:59:59Z","merge_commit_sha":"e6297b8fdce76a6675db6bdb5137c0c1efc3b534","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/1/commits","review_comments_url":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/1/comments","review_comment_url":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/comments{/number}","comments_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/1/comments","statuses_url":"https://api.github.com/repos/philips-validation-beta/github-validation/statuses/21a284abaec737d7d654e3f767ea007cc492c240","head":{"label":"philips-validation-beta:branch1","ref":"branch1","sha":"21a284abaec737d7d654e3f767ea007cc492c240","user":{"login":"philips-validation-beta","id":83454667,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0NjY3","avatar_url":"https://avatars.githubusercontent.com/u/83454667?v=4","gravatar_id":"","url":"https://api.github.com/users/philips-validation-beta","html_url":"https://github.com/philips-validation-beta","followers_url":"https://api.github.com/users/philips-validation-beta/followers","following_url":"https://api.github.com/users/philips-validation-beta/following{/other_user}","gists_url":"https://api.github.com/users/philips-validation-beta/gists{/gist_id}","starred_url":"https://api.github.com/users/philips-validation-beta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/philips-validation-beta/subscriptions","organizations_url":"https://api.github.com/users/philips-validation-beta/orgs","repos_url":"https://api.github.com/users/philips-validation-beta/repos","events_url":"https://api.github.com/users/philips-validation-beta/events{/privacy}","received_events_url":"https://api.github.com/users/philips-validation-beta/received_events","type":"Organization","site_admin":false},"repo":{"id":388040199,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDAxOTk=","name":"github-validation","full_name":"philips-validation-beta/github-validation","private":false,"owner":{"login":"philips-validation-beta","id":83454667,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0NjY3","avatar_url":"https://avatars.githubusercontent.com/u/83454667?v=4","gravatar_id":"","url":"https://api.github.com/users/philips-validation-beta","html_url":"https://github.com/philips-validation-beta","followers_url":"https://api.github.com/users/philips-validation-beta/followers","following_url":"https://api.github.com/users/philips-validation-beta/following{/other_user}","gists_url":"https://api.github.com/users/philips-validation-beta/gists{/gist_id}","starred_url":"https://api.github.com/users/philips-validation-beta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/philips-validation-beta/subscriptions","organizations_url":"https://api.github.com/users/philips-validation-beta/orgs","repos_url":"https://api.github.com/users/philips-validation-beta/repos","events_url":"https://api.github.com/users/philips-validation-beta/events{/privacy}","received_events_url":"https://api.github.com/users/philips-validation-beta/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/philips-validation-beta/github-validation","description":null,"fork":false,"url":"https://api.github.com/repos/philips-validation-beta/github-validation","forks_url":"https://api.github.com/repos/philips-validation-beta/github-validation/forks","keys_url":"https://api.github.com/repos/philips-validation-beta/github-validation/keys{/key_id}","collaborators_url":"https://api.github.com/repos/philips-validation-beta/github-validation/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/philips-validation-beta/github-validation/teams","hooks_url":"https://api.github.com/repos/philips-validation-beta/github-validation/hooks","issue_events_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/events{/number}","events_url":"https://api.github.com/repos/philips-validation-beta/github-validation/events","assignees_url":"https://api.github.com/repos/philips-validation-beta/github-validation/assignees{/user}","branches_url":"https://api.github.com/repos/philips-validation-beta/github-validation/branches{/branch}","tags_url":"https://api.github.com/repos/philips-validation-beta/github-validation/tags","blobs_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/refs{/sha}","trees_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/trees{/sha}","statuses_url":"https://api.github.com/repos/philips-validation-beta/github-validation/statuses/{sha}","languages_url":"https://api.github.com/repos/philips-validation-beta/github-validation/languages","stargazers_url":"https://api.github.com/repos/philips-validation-beta/github-validation/stargazers","contributors_url":"https://api.github.com/repos/philips-validation-beta/github-validation/contributors","subscribers_url":"https://api.github.com/repos/philips-validation-beta/github-validation/subscribers","subscription_url":"https://api.github.com/repos/philips-validation-beta/github-validation/subscription","commits_url":"https://api.github.com/repos/philips-validation-beta/github-validation/commits{/sha}","git_commits_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/commits{/sha}","comments_url":"https://api.github.com/repos/philips-validation-beta/github-validation/comments{/number}","issue_comment_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/comments{/number}","contents_url":"https://api.github.com/repos/philips-validation-beta/github-validation/contents/{+path}","compare_url":"https://api.github.com/repos/philips-validation-beta/github-validation/compare/{base}...{head}","merges_url":"https://api.github.com/repos/philips-validation-beta/github-validation/merges","archive_url":"https://api.github.com/repos/philips-validation-beta/github-validation/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/philips-validation-beta/github-validation/downloads","issues_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues{/number}","pulls_url":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls{/number}","milestones_url":"https://api.github.com/repos/philips-validation-beta/github-validation/milestones{/number}","notifications_url":"https://api.github.com/repos/philips-validation-beta/github-validation/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/philips-validation-beta/github-validation/labels{/name}","releases_url":"https://api.github.com/repos/philips-validation-beta/github-validation/releases{/id}","deployments_url":"https://api.github.com/repos/philips-validation-beta/github-validation/deployments","created_at":"2021-07-21T07:58:36Z","updated_at":"2021-07-21T07:58:39Z","pushed_at":"2021-07-21T07:59:59Z","git_url":"git://github.com/philips-validation-beta/github-validation.git","ssh_url":"git@github.com:philips-validation-beta/github-validation.git","clone_url":"https://github.com/philips-validation-beta/github-validation.git","svn_url":"https://github.com/philips-validation-beta/github-validation","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"base":{"label":"philips-validation-beta:main","ref":"main","sha":"da451304a2b638c617548abca4ac5b0d61369e2d","user":{"login":"philips-validation-beta","id":83454667,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0NjY3","avatar_url":"https://avatars.githubusercontent.com/u/83454667?v=4","gravatar_id":"","url":"https://api.github.com/users/philips-validation-beta","html_url":"https://github.com/philips-validation-beta","followers_url":"https://api.github.com/users/philips-validation-beta/followers","following_url":"https://api.github.com/users/philips-validation-beta/following{/other_user}","gists_url":"https://api.github.com/users/philips-validation-beta/gists{/gist_id}","starred_url":"https://api.github.com/users/philips-validation-beta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/philips-validation-beta/subscriptions","organizations_url":"https://api.github.com/users/philips-validation-beta/orgs","repos_url":"https://api.github.com/users/philips-validation-beta/repos","events_url":"https://api.github.com/users/philips-validation-beta/events{/privacy}","received_events_url":"https://api.github.com/users/philips-validation-beta/received_events","type":"Organization","site_admin":false},"repo":{"id":388040199,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDAxOTk=","name":"github-validation","full_name":"philips-validation-beta/github-validation","private":false,"owner":{"login":"philips-validation-beta","id":83454667,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDU0NjY3","avatar_url":"https://avatars.githubusercontent.com/u/83454667?v=4","gravatar_id":"","url":"https://api.github.com/users/philips-validation-beta","html_url":"https://github.com/philips-validation-beta","followers_url":"https://api.github.com/users/philips-validation-beta/followers","following_url":"https://api.github.com/users/philips-validation-beta/following{/other_user}","gists_url":"https://api.github.com/users/philips-validation-beta/gists{/gist_id}","starred_url":"https://api.github.com/users/philips-validation-beta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/philips-validation-beta/subscriptions","organizations_url":"https://api.github.com/users/philips-validation-beta/orgs","repos_url":"https://api.github.com/users/philips-validation-beta/repos","events_url":"https://api.github.com/users/philips-validation-beta/events{/privacy}","received_events_url":"https://api.github.com/users/philips-validation-beta/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/philips-validation-beta/github-validation","description":null,"fork":false,"url":"https://api.github.com/repos/philips-validation-beta/github-validation","forks_url":"https://api.github.com/repos/philips-validation-beta/github-validation/forks","keys_url":"https://api.github.com/repos/philips-validation-beta/github-validation/keys{/key_id}","collaborators_url":"https://api.github.com/repos/philips-validation-beta/github-validation/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/philips-validation-beta/github-validation/teams","hooks_url":"https://api.github.com/repos/philips-validation-beta/github-validation/hooks","issue_events_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/events{/number}","events_url":"https://api.github.com/repos/philips-validation-beta/github-validation/events","assignees_url":"https://api.github.com/repos/philips-validation-beta/github-validation/assignees{/user}","branches_url":"https://api.github.com/repos/philips-validation-beta/github-validation/branches{/branch}","tags_url":"https://api.github.com/repos/philips-validation-beta/github-validation/tags","blobs_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/refs{/sha}","trees_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/trees{/sha}","statuses_url":"https://api.github.com/repos/philips-validation-beta/github-validation/statuses/{sha}","languages_url":"https://api.github.com/repos/philips-validation-beta/github-validation/languages","stargazers_url":"https://api.github.com/repos/philips-validation-beta/github-validation/stargazers","contributors_url":"https://api.github.com/repos/philips-validation-beta/github-validation/contributors","subscribers_url":"https://api.github.com/repos/philips-validation-beta/github-validation/subscribers","subscription_url":"https://api.github.com/repos/philips-validation-beta/github-validation/subscription","commits_url":"https://api.github.com/repos/philips-validation-beta/github-validation/commits{/sha}","git_commits_url":"https://api.github.com/repos/philips-validation-beta/github-validation/git/commits{/sha}","comments_url":"https://api.github.com/repos/philips-validation-beta/github-validation/comments{/number}","issue_comment_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/comments{/number}","contents_url":"https://api.github.com/repos/philips-validation-beta/github-validation/contents/{+path}","compare_url":"https://api.github.com/repos/philips-validation-beta/github-validation/compare/{base}...{head}","merges_url":"https://api.github.com/repos/philips-validation-beta/github-validation/merges","archive_url":"https://api.github.com/repos/philips-validation-beta/github-validation/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/philips-validation-beta/github-validation/downloads","issues_url":"https://api.github.com/repos/philips-validation-beta/github-validation/issues{/number}","pulls_url":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls{/number}","milestones_url":"https://api.github.com/repos/philips-validation-beta/github-validation/milestones{/number}","notifications_url":"https://api.github.com/repos/philips-validation-beta/github-validation/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/philips-validation-beta/github-validation/labels{/name}","releases_url":"https://api.github.com/repos/philips-validation-beta/github-validation/releases{/id}","deployments_url":"https://api.github.com/repos/philips-validation-beta/github-validation/deployments","created_at":"2021-07-21T07:58:36Z","updated_at":"2021-07-21T07:58:39Z","pushed_at":"2021-07-21T07:59:59Z","git_url":"git://github.com/philips-validation-beta/github-validation.git","ssh_url":"git@github.com:philips-validation-beta/github-validation.git","clone_url":"https://github.com/philips-validation-beta/github-validation.git","svn_url":"https://github.com/philips-validation-beta/github-validation","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/1"},"html":{"href":"https://github.com/philips-validation-beta/github-validation/pull/1"},"issue":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/1"},"comments":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/philips-validation-beta/github-validation/statuses/21a284abaec737d7d654e3f767ea007cc492c240"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"githubvaladm","id":80700348,"node_id":"MDQ6VXNlcjgwNzAwMzQ4","avatar_url":"https://avatars.githubusercontent.com/u/80700348?v=4","gravatar_id":"","url":"https://api.github.com/users/githubvaladm","html_url":"https://github.com/githubvaladm","followers_url":"https://api.github.com/users/githubvaladm/followers","following_url":"https://api.github.com/users/githubvaladm/following{/other_user}","gists_url":"https://api.github.com/users/githubvaladm/gists{/gist_id}","starred_url":"https://api.github.com/users/githubvaladm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/githubvaladm/subscriptions","organizations_url":"https://api.github.com/users/githubvaladm/orgs","repos_url":"https://api.github.com/users/githubvaladm/repos","events_url":"https://api.github.com/users/githubvaladm/events{/privacy}","received_events_url":"https://api.github.com/users/githubvaladm/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:00Z","org":{"id":83454667,"login":"philips-validation-beta","gravatar_id":"","url":"https://api.github.com/orgs/philips-validation-beta","avatar_url":"https://avatars.githubusercontent.com/u/83454667?"}} +{"id":"17245514573","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":324273526,"name":"JiuRiend/auto-green","url":"https://api.github.com/repos/JiuRiend/auto-green"},"payload":{"push_id":7562054918,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"d584a4cd6ac5f536c6c49bda6296bb0fa1219dec","before":"e8f1828394136b097cba29fa1b10b945dbb40ca8","commits":[{"sha":"d584a4cd6ac5f536c6c49bda6296bb0fa1219dec","author":{"name":"xuliu","email":"d95d1f41266d37246ffc8610986934a6123a88f8@163.com"},"message":"a commit a day keeps your girlfriend away","distinct":true,"url":"https://api.github.com/repos/JiuRiend/auto-green/commits/d584a4cd6ac5f536c6c49bda6296bb0fa1219dec"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514574","type":"PushEvent","actor":{"id":35951221,"login":"RomanChasovitin","display_login":"RomanChasovitin","gravatar_id":"","url":"https://api.github.com/users/RomanChasovitin","avatar_url":"https://avatars.githubusercontent.com/u/35951221?"},"repo":{"id":65986375,"name":"maddevsio/maddevs","url":"https://api.github.com/repos/maddevsio/maddevs"},"payload":{"push_id":7562054950,"size":1,"distinct_size":1,"ref":"refs/heads/MAR-1462","head":"71ca8400b833cb332b5614a26c88cce89b239e51","before":"d9366b87c2a2af2af994c507800bba85616646e8","commits":[{"sha":"71ca8400b833cb332b5614a26c88cce89b239e51","author":{"name":"Roman Chasovitin","email":"87217576d07d099e2cf5fbb79b249300dd46c8f7@gmail.com"},"message":"MAR-1462. Updated radiator version.","distinct":true,"url":"https://api.github.com/repos/maddevsio/maddevs/commits/71ca8400b833cb332b5614a26c88cce89b239e51"}]},"public":true,"created_at":"2021-07-21T08:00:00Z","org":{"id":21100936,"login":"maddevsio","gravatar_id":"","url":"https://api.github.com/orgs/maddevsio","avatar_url":"https://avatars.githubusercontent.com/u/21100936?"}} +{"id":"17245514585","type":"PushEvent","actor":{"id":71610591,"login":"VoidMasterX","display_login":"VoidMasterX","gravatar_id":"","url":"https://api.github.com/users/VoidMasterX","avatar_url":"https://avatars.githubusercontent.com/u/71610591?"},"repo":{"id":303836618,"name":"VoidMasterX/ui","url":"https://api.github.com/repos/VoidMasterX/ui"},"payload":{"push_id":7562054937,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"d473510859fe92130136c8250fa136fa0a0da1ad","before":"8eea84a1cdbb5645868d50174d950764d821d8c1","commits":[{"sha":"d473510859fe92130136c8250fa136fa0a0da1ad","author":{"name":"Siper","email":"b0913ff6d65907387cb2b5538879a0896f351e4e@users.noreply.github.com"},"message":"Update pw.lua","distinct":true,"url":"https://api.github.com/repos/VoidMasterX/ui/commits/d473510859fe92130136c8250fa136fa0a0da1ad"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514593","type":"PushEvent","actor":{"id":28486212,"login":"sierpien88","display_login":"sierpien88","gravatar_id":"","url":"https://api.github.com/users/sierpien88","avatar_url":"https://avatars.githubusercontent.com/u/28486212?"},"repo":{"id":379342839,"name":"GerardSzymanski94/awsa","url":"https://api.github.com/repos/GerardSzymanski94/awsa"},"payload":{"push_id":7562054954,"size":1,"distinct_size":1,"ref":"refs/heads/agata","head":"5e68ff55e89898e30d7d61cc0781b6cc09e24048","before":"ec7eaad1d3a05e16b66d51d1f1225795f9c36f7a","commits":[{"sha":"5e68ff55e89898e30d7d61cc0781b6cc09e24048","author":{"name":"sierpien","email":"64ddb662a6fe8c3067bb103e18022212faaf5220@gmail.com"},"message":"faq style","distinct":true,"url":"https://api.github.com/repos/GerardSzymanski94/awsa/commits/5e68ff55e89898e30d7d61cc0781b6cc09e24048"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514605","type":"PushEvent","actor":{"id":55564581,"login":"Cammin","display_login":"Cammin","gravatar_id":"","url":"https://api.github.com/users/Cammin","avatar_url":"https://avatars.githubusercontent.com/u/55564581?"},"repo":{"id":311052664,"name":"Cammin/LDtkToUnity","url":"https://api.github.com/repos/Cammin/LDtkToUnity"},"payload":{"push_id":7562054944,"size":1,"distinct_size":1,"ref":"refs/heads/develop","head":"a4e7c986c94e0d453554967c73d119941e659c90","before":"7f07a05aa88cefd6915c65bc75a422587b48ac1b","commits":[{"sha":"a4e7c986c94e0d453554967c73d119941e659c90","author":{"name":"Cammin","email":"7a30686c25637e367d36cdc41e2c059eda4340d0@gmail.com"},"message":"package update 2.1.5","distinct":true,"url":"https://api.github.com/repos/Cammin/LDtkToUnity/commits/a4e7c986c94e0d453554967c73d119941e659c90"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514645","type":"PushEvent","actor":{"id":67499961,"login":"Junbro0708","display_login":"Junbro0708","gravatar_id":"","url":"https://api.github.com/users/Junbro0708","avatar_url":"https://avatars.githubusercontent.com/u/67499961?"},"repo":{"id":363324027,"name":"Junbro0708/Algorithm_CPP","url":"https://api.github.com/repos/Junbro0708/Algorithm_CPP"},"payload":{"push_id":7562054956,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"e0b5230712e132b22f2e3d23fb7e5b1a8cd6cfe5","before":"ebd7953b94c732ba9183d3796da506915a91a1ec","commits":[{"sha":"e0b5230712e132b22f2e3d23fb7e5b1a8cd6cfe5","author":{"name":"Junbro0708","email":"53b767db09f342012768d85181756f63f7cbb737@soongsil.ac.kr"},"message":"[80] 백준 1929번 소수 구하기","distinct":true,"url":"https://api.github.com/repos/Junbro0708/Algorithm_CPP/commits/e0b5230712e132b22f2e3d23fb7e5b1a8cd6cfe5"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514653","type":"WatchEvent","actor":{"id":83502671,"login":"tulj","display_login":"tulj","gravatar_id":"","url":"https://api.github.com/users/tulj","avatar_url":"https://avatars.githubusercontent.com/u/83502671?"},"repo":{"id":150301917,"name":"gouravthakur39/beginners-C-program-examples","url":"https://api.github.com/repos/gouravthakur39/beginners-C-program-examples"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514669","type":"PushEvent","actor":{"id":80700348,"login":"githubvaladm","display_login":"githubvaladm","gravatar_id":"","url":"https://api.github.com/users/githubvaladm","avatar_url":"https://avatars.githubusercontent.com/u/80700348?"},"repo":{"id":388040199,"name":"philips-validation-beta/github-validation","url":"https://api.github.com/repos/philips-validation-beta/github-validation"},"payload":{"push_id":7562054993,"size":2,"distinct_size":1,"ref":"refs/heads/main","head":"e6297b8fdce76a6675db6bdb5137c0c1efc3b534","before":"da451304a2b638c617548abca4ac5b0d61369e2d","commits":[{"sha":"21a284abaec737d7d654e3f767ea007cc492c240","author":{"name":"githubvaladm","email":"be1c2e149223967c6160e455c5f97f6a07eae059@users.noreply.github.com"},"message":"Update README.md","distinct":false,"url":"https://api.github.com/repos/philips-validation-beta/github-validation/commits/21a284abaec737d7d654e3f767ea007cc492c240"},{"sha":"e6297b8fdce76a6675db6bdb5137c0c1efc3b534","author":{"name":"githubvaladm","email":"be1c2e149223967c6160e455c5f97f6a07eae059@users.noreply.github.com"},"message":"Merge pull request #1 from philips-validation-beta/branch1\n\nTC13:changed readme file","distinct":true,"url":"https://api.github.com/repos/philips-validation-beta/github-validation/commits/e6297b8fdce76a6675db6bdb5137c0c1efc3b534"}]},"public":true,"created_at":"2021-07-21T08:00:00Z","org":{"id":83454667,"login":"philips-validation-beta","gravatar_id":"","url":"https://api.github.com/orgs/philips-validation-beta","avatar_url":"https://avatars.githubusercontent.com/u/83454667?"}} +{"id":"17245514686","type":"IssueCommentEvent","actor":{"id":57408660,"login":"bkocev","display_login":"bkocev","gravatar_id":"","url":"https://api.github.com/users/bkocev","avatar_url":"https://avatars.githubusercontent.com/u/57408660?"},"repo":{"id":25217345,"name":"rock-control/control-orogen-auv_control","url":"https://api.github.com/repos/rock-control/control-orogen-auv_control"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/rock-control/control-orogen-auv_control/issues/37","repository_url":"https://api.github.com/repos/rock-control/control-orogen-auv_control","labels_url":"https://api.github.com/repos/rock-control/control-orogen-auv_control/issues/37/labels{/name}","comments_url":"https://api.github.com/repos/rock-control/control-orogen-auv_control/issues/37/comments","events_url":"https://api.github.com/repos/rock-control/control-orogen-auv_control/issues/37/events","html_url":"https://github.com/rock-control/control-orogen-auv_control/issues/37","id":946365491,"node_id":"MDU6SXNzdWU5NDYzNjU0OTE=","number":37,"title":"Integration into Rock-ROS bridge release docker container","user":{"login":"bkocev","id":57408660,"node_id":"MDQ6VXNlcjU3NDA4NjYw","avatar_url":"https://avatars.githubusercontent.com/u/57408660?v=4","gravatar_id":"","url":"https://api.github.com/users/bkocev","html_url":"https://github.com/bkocev","followers_url":"https://api.github.com/users/bkocev/followers","following_url":"https://api.github.com/users/bkocev/following{/other_user}","gists_url":"https://api.github.com/users/bkocev/gists{/gist_id}","starred_url":"https://api.github.com/users/bkocev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bkocev/subscriptions","organizations_url":"https://api.github.com/users/bkocev/orgs","repos_url":"https://api.github.com/users/bkocev/repos","events_url":"https://api.github.com/users/bkocev/events{/privacy}","received_events_url":"https://api.github.com/users/bkocev/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2021-07-16T14:52:49Z","updated_at":"2021-07-21T08:00:00Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"Hi everyone,\r\n\r\nI am trying to integrate this orogen component in the Rock-ROS bridge.\r\nUnfortunately, I am getting a build error using amake. Belo w is the traceback.\r\n\r\nTraceback (most recent call last):\r\n File \"/opt/ros/melodic/lib/python2.7/dist-packages/geneus/geneus_main.py\", line 137, in genmain\r\n pkg_map = get_pkg_map()\r\n File \"/opt/ros/melodic/lib/python2.7/dist-packages/geneus/geneus_main.py\", line 56, in get_pkg_map\r\n pkgs = packages.find_packages(ws)\r\n File \"/usr/lib/python2.7/dist-packages/catkin_pkg/packages.py\", line 89, in find_packages\r\n packages = find_packages_allowing_duplicates(basepath, exclude_paths=exclude_paths, exclude_subspaces=exclude_subspaces, warnings=warnings)\r\n File \"/usr/lib/python2.7/dist-packages/catkin_pkg/packages.py\", line 150, in find_packages_allowing_duplicates\r\n xml, filename=filename, warnings=warnings)\r\n File \"/usr/lib/python2.7/dist-packages/catkin_pkg/package.py\", line 542, in parse_package_string\r\n raise InvalidPackage('The manifest contains invalid XML:\\n%s' % ex, filename)\r\n InvalidPackage: Error(s) in package '/opt/rock_workspace/install/lib/x86_64-linux-gnu/ruby/2.5.0/orogen/templates/typekit/package.xml':\r\n The manifest contains invalid XML:\r\n not well-formed (invalid token): line 2, column 9\r\n ERROR: Error(s) in package '/opt/rock_workspace/install/lib/x86_64-linux-gnu/ruby/2.5.0/orogen/templates/typekit/package.xml':\r\n The manifest contains invalid XML:\r\n not well-formed (invalid token): line 2, column 9\r\n .orogen/typekit/transports/ros/CMakeFiles/orogen_auv_control_msgs_generate_messages_eus.dir/build.make:134: recipe for target 'devel/share/roseus/ros/orogen_auv_control_msgs/manifest.l' failed\r\n make[2]: *** [devel/share/roseus/ros/orogen_auv_control_msgs/manifest.l] Error 3\r\n CMakeFiles/Makefile2:1133: recipe for target '.orogen/typekit/transports/ros/CMakeFiles/orogen_auv_control_msgs_generate_messages_eus.dir/all' failed\r\n make[1]: *** [.orogen/typekit/transports/ros/CMakeFiles/orogen_auv_control_msgs_generate_messages_eus.dir/all] Error 2\r\n\r\nI am guessing there might be an issue with the python version in the rock-ros bridge docker container out of which I got a release version. Nevertheless, I tried to update/upgrade as much as possible. Currently, I set python using autoproj reconfigure to /usr/bin/python2.7 but still it does not work.\r\n\r\nAny remarks/suggestions would be very welcome.\r\n\r\nThanks,\r\nBojan Kocev.","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/rock-control/control-orogen-auv_control/issues/comments/883977107","html_url":"https://github.com/rock-control/control-orogen-auv_control/issues/37#issuecomment-883977107","issue_url":"https://api.github.com/repos/rock-control/control-orogen-auv_control/issues/37","id":883977107,"node_id":"IC_kwDOAYDJQc40sGuT","user":{"login":"bkocev","id":57408660,"node_id":"MDQ6VXNlcjU3NDA4NjYw","avatar_url":"https://avatars.githubusercontent.com/u/57408660?v=4","gravatar_id":"","url":"https://api.github.com/users/bkocev","html_url":"https://github.com/bkocev","followers_url":"https://api.github.com/users/bkocev/followers","following_url":"https://api.github.com/users/bkocev/following{/other_user}","gists_url":"https://api.github.com/users/bkocev/gists{/gist_id}","starred_url":"https://api.github.com/users/bkocev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bkocev/subscriptions","organizations_url":"https://api.github.com/users/bkocev/orgs","repos_url":"https://api.github.com/users/bkocev/repos","events_url":"https://api.github.com/users/bkocev/events{/privacy}","received_events_url":"https://api.github.com/users/bkocev/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:00Z","updated_at":"2021-07-21T08:00:00Z","author_association":"NONE","body":"Maybe, because it is a Rock-ROS bridge? :)","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:00Z","org":{"id":7403553,"login":"rock-control","gravatar_id":"","url":"https://api.github.com/orgs/rock-control","avatar_url":"https://avatars.githubusercontent.com/u/7403553?"}} +{"id":"17245514706","type":"CreateEvent","actor":{"id":32644413,"login":"DavidZagury","display_login":"DavidZagury","gravatar_id":"","url":"https://api.github.com/users/DavidZagury","avatar_url":"https://avatars.githubusercontent.com/u/32644413?"},"repo":{"id":324731374,"name":"DavidZagury/sonic-buildimage","url":"https://api.github.com/repos/DavidZagury/sonic-buildimage"},"payload":{"ref":"master_submodule-platform-common","ref_type":"branch","master_branch":"master","description":"Scripts which perform an installable binary image build for SONiC","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514710","type":"IssuesEvent","actor":{"id":895503,"login":"poolpOrg","display_login":"poolpOrg","gravatar_id":"","url":"https://api.github.com/users/poolpOrg","avatar_url":"https://avatars.githubusercontent.com/u/895503?"},"repo":{"id":126189620,"name":"poolpOrg/poolp.org","url":"https://api.github.com/repos/poolpOrg/poolp.org"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/poolpOrg/poolp.org/issues/53","repository_url":"https://api.github.com/repos/poolpOrg/poolp.org","labels_url":"https://api.github.com/repos/poolpOrg/poolp.org/issues/53/labels{/name}","comments_url":"https://api.github.com/repos/poolpOrg/poolp.org/issues/53/comments","events_url":"https://api.github.com/repos/poolpOrg/poolp.org/issues/53/events","html_url":"https://github.com/poolpOrg/poolp.org/issues/53","id":739639613,"node_id":"MDU6SXNzdWU3Mzk2Mzk2MTM=","number":53,"title":"OpenSMTPD proc filters & fc-rDNS","user":{"login":"poolpOrg","id":895503,"node_id":"MDQ6VXNlcjg5NTUwMw==","avatar_url":"https://avatars.githubusercontent.com/u/895503?v=4","gravatar_id":"","url":"https://api.github.com/users/poolpOrg","html_url":"https://github.com/poolpOrg","followers_url":"https://api.github.com/users/poolpOrg/followers","following_url":"https://api.github.com/users/poolpOrg/following{/other_user}","gists_url":"https://api.github.com/users/poolpOrg/gists{/gist_id}","starred_url":"https://api.github.com/users/poolpOrg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poolpOrg/subscriptions","organizations_url":"https://api.github.com/users/poolpOrg/orgs","repos_url":"https://api.github.com/users/poolpOrg/repos","events_url":"https://api.github.com/users/poolpOrg/events{/privacy}","received_events_url":"https://api.github.com/users/poolpOrg/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":true,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2018-12-06T21:24:52Z","updated_at":"2021-07-21T08:00:00Z","closed_at":"2021-07-21T08:00:00Z","author_association":"OWNER","active_lock_reason":null,"body":"","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514711","type":"PushEvent","actor":{"id":20966023,"login":"frycast","display_login":"frycast","gravatar_id":"","url":"https://api.github.com/users/frycast","avatar_url":"https://avatars.githubusercontent.com/u/20966023?"},"repo":{"id":352879183,"name":"frycast/danielvfryer3","url":"https://api.github.com/repos/frycast/danielvfryer3"},"payload":{"push_id":7562055018,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"d3b08a302bfd733b7bf6e573d09411a2e718d526","before":"42ff84a97b7c7d6f13df1a50e880bd5044a3ad91","commits":[{"sha":"d3b08a302bfd733b7bf6e573d09411a2e718d526","author":{"name":"Daniel Vidali Fryer","email":"c732ec7f32d589327653cd53e5534a5cbbb8a63b@live.com.au"},"message":"Update _index.md","distinct":true,"url":"https://api.github.com/repos/frycast/danielvfryer3/commits/d3b08a302bfd733b7bf6e573d09411a2e718d526"}]},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514712","type":"IssuesEvent","actor":{"id":3043118,"login":"mshirdel","display_login":"mshirdel","gravatar_id":"","url":"https://api.github.com/users/mshirdel","avatar_url":"https://avatars.githubusercontent.com/u/3043118?"},"repo":{"id":347923838,"name":"mshirdel/gapbug","url":"https://api.github.com/repos/mshirdel/gapbug"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/mshirdel/gapbug/issues/12","repository_url":"https://api.github.com/repos/mshirdel/gapbug","labels_url":"https://api.github.com/repos/mshirdel/gapbug/issues/12/labels{/name}","comments_url":"https://api.github.com/repos/mshirdel/gapbug/issues/12/comments","events_url":"https://api.github.com/repos/mshirdel/gapbug/issues/12/events","html_url":"https://github.com/mshirdel/gapbug/issues/12","id":947517864,"node_id":"MDU6SXNzdWU5NDc1MTc4NjQ=","number":12,"title":"Test","user":{"login":"zkeshtkar","id":49828035,"node_id":"MDQ6VXNlcjQ5ODI4MDM1","avatar_url":"https://avatars.githubusercontent.com/u/49828035?v=4","gravatar_id":"","url":"https://api.github.com/users/zkeshtkar","html_url":"https://github.com/zkeshtkar","followers_url":"https://api.github.com/users/zkeshtkar/followers","following_url":"https://api.github.com/users/zkeshtkar/following{/other_user}","gists_url":"https://api.github.com/users/zkeshtkar/gists{/gist_id}","starred_url":"https://api.github.com/users/zkeshtkar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zkeshtkar/subscriptions","organizations_url":"https://api.github.com/users/zkeshtkar/orgs","repos_url":"https://api.github.com/users/zkeshtkar/repos","events_url":"https://api.github.com/users/zkeshtkar/events{/privacy}","received_events_url":"https://api.github.com/users/zkeshtkar/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-19T10:40:45Z","updated_at":"2021-07-21T08:00:00Z","closed_at":"2021-07-21T08:00:00Z","author_association":"NONE","active_lock_reason":null,"body":"hello friends\r\nhope you all ok\r\ni wrote some test (views_test and model_test and form_test ) for Account application\r\nif you think it is good time, i will send them to take a look at them","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:00Z"} +{"id":"17245514739","type":"PushEvent","actor":{"id":8517910,"login":"LombiqBot","display_login":"LombiqBot","gravatar_id":"","url":"https://api.github.com/users/LombiqBot","avatar_url":"https://avatars.githubusercontent.com/u/8517910?"},"repo":{"id":49138417,"name":"Lombiq/Om.Orchard.SocialMetaTags","url":"https://api.github.com/repos/Lombiq/Om.Orchard.SocialMetaTags"},"payload":{"push_id":7562055021,"size":0,"distinct_size":0,"ref":"refs/heads/master","head":"50c1ed259cabe08846c40291f8980a22a5a68062","before":"50c1ed259cabe08846c40291f8980a22a5a68062","commits":[]},"public":true,"created_at":"2021-07-21T08:00:01Z","org":{"id":8158177,"login":"Lombiq","gravatar_id":"","url":"https://api.github.com/orgs/Lombiq","avatar_url":"https://avatars.githubusercontent.com/u/8158177?"}} +{"id":"17245514750","type":"PushEvent","actor":{"id":37301733,"login":"Gabinou","display_login":"Gabinou","gravatar_id":"","url":"https://api.github.com/users/Gabinou","avatar_url":"https://avatars.githubusercontent.com/u/37301733?"},"repo":{"id":233493236,"name":"Gabinou/Codename_FireSaga","url":"https://api.github.com/repos/Gabinou/Codename_FireSaga"},"payload":{"push_id":7562055023,"size":2,"distinct_size":2,"ref":"refs/heads/master","head":"5a94fc6251472ff884061cff6cb158f021e586c8","before":"36d1167a709af612043fbfafbbd41a81a83aab24","commits":[{"sha":"7d2e56b533e398497769d5a1bb488fad5a47cdb3","author":{"name":"Gabriel Taillon","email":"0b395b9c3a2ec99c61180c083347731d9ca4f03d@gmail.com"},"message":"many more patterns of differing order. make more colorssss","distinct":true,"url":"https://api.github.com/repos/Gabinou/Codename_FireSaga/commits/7d2e56b533e398497769d5a1bb488fad5a47cdb3"},{"sha":"5a94fc6251472ff884061cff6cb158f021e586c8","author":{"name":"Gabriel Taillon","email":"0b395b9c3a2ec99c61180c083347731d9ca4f03d@gmail.com"},"message":"more tilesss","distinct":true,"url":"https://api.github.com/repos/Gabinou/Codename_FireSaga/commits/5a94fc6251472ff884061cff6cb158f021e586c8"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514756","type":"IssuesEvent","actor":{"id":5029476,"login":"cis1124","display_login":"cis1124","gravatar_id":"","url":"https://api.github.com/users/cis1124","avatar_url":"https://avatars.githubusercontent.com/u/5029476?"},"repo":{"id":16662094,"name":"miguelgrinberg/Flask-SocketIO","url":"https://api.github.com/repos/miguelgrinberg/Flask-SocketIO"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/miguelgrinberg/Flask-SocketIO/issues/1641","repository_url":"https://api.github.com/repos/miguelgrinberg/Flask-SocketIO","labels_url":"https://api.github.com/repos/miguelgrinberg/Flask-SocketIO/issues/1641/labels{/name}","comments_url":"https://api.github.com/repos/miguelgrinberg/Flask-SocketIO/issues/1641/comments","events_url":"https://api.github.com/repos/miguelgrinberg/Flask-SocketIO/issues/1641/events","html_url":"https://github.com/miguelgrinberg/Flask-SocketIO/issues/1641","id":949435824,"node_id":"MDU6SXNzdWU5NDk0MzU4MjQ=","number":1641,"title":"Cannot get flask g in trigger_event.","user":{"login":"cis1124","id":5029476,"node_id":"MDQ6VXNlcjUwMjk0NzY=","avatar_url":"https://avatars.githubusercontent.com/u/5029476?v=4","gravatar_id":"","url":"https://api.github.com/users/cis1124","html_url":"https://github.com/cis1124","followers_url":"https://api.github.com/users/cis1124/followers","following_url":"https://api.github.com/users/cis1124/following{/other_user}","gists_url":"https://api.github.com/users/cis1124/gists{/gist_id}","starred_url":"https://api.github.com/users/cis1124/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cis1124/subscriptions","organizations_url":"https://api.github.com/users/cis1124/orgs","repos_url":"https://api.github.com/users/cis1124/repos","events_url":"https://api.github.com/users/cis1124/events{/privacy}","received_events_url":"https://api.github.com/users/cis1124/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T08:00:00Z","updated_at":"2021-07-21T08:00:00Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"I have a global custmom namespace for access control,\r\n\r\ncode:\r\n```\r\nclass SecurityNamespace(Namespace):\r\n\r\n def trigger_event(self, event, *args):\r\n if event == \"connect\":\r\n print(\"custom namespace for access control\")\r\n # get endpoint\r\n user=g.user #get \"Working outside of application context\"\r\n endpoint = self.namespace\r\n if check_access(user, endpoint):\r\n super().trigger_event(event, *args)\r\n else:\r\n return\r\n else:\r\n super().trigger_event(event, *args)\r\n```\r\n\r\n```\r\nclass WebAPI(SecurityNamespace):\r\n\r\n def __init__(self, namespace):\r\n super().__init__(namespace)\r\n\r\n \r\n def on_connect(self):\r\n \r\n print(g.user)# this will ok\r\n self._do_something(request)\r\n return True\r\n```\r\n\r\nCannot use g in trigger_event,any method to fix this?","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514772","type":"PushEvent","actor":{"id":32082243,"login":"wulivictor","display_login":"wulivictor","gravatar_id":"","url":"https://api.github.com/users/wulivictor","avatar_url":"https://avatars.githubusercontent.com/u/32082243?"},"repo":{"id":296179479,"name":"wulivictor/ExamOnline","url":"https://api.github.com/repos/wulivictor/ExamOnline"},"payload":{"push_id":7562055027,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"bee0329eb83a8c3a442807548fff7476e6370849","before":"1f9d677ca8688d7fd3e77fc919e916ee036ea02d","commits":[{"sha":"bee0329eb83a8c3a442807548fff7476e6370849","author":{"name":"wulivictor","email":"e86e5e643c2e7ae07b328af9adfbe878fe6abfb4@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/wulivictor/ExamOnline/commits/bee0329eb83a8c3a442807548fff7476e6370849"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514791","type":"WatchEvent","actor":{"id":34101367,"login":"HStarrr","display_login":"HStarrr","gravatar_id":"","url":"https://api.github.com/users/HStarrr","avatar_url":"https://avatars.githubusercontent.com/u/34101367?"},"repo":{"id":99946544,"name":"brianwarehime/inSp3ctor","url":"https://api.github.com/repos/brianwarehime/inSp3ctor"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514796","type":"DeleteEvent","actor":{"id":10810283,"login":"direwolf-github","display_login":"direwolf-github","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","avatar_url":"https://avatars.githubusercontent.com/u/10810283?"},"repo":{"id":388040450,"name":"direwolf-github/my-app-5f19618c","url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c"},"payload":{"ref":"branch-c9b911a8","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514805","type":"CreateEvent","actor":{"id":11707729,"login":"gabrieldonadel","display_login":"gabrieldonadel","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","avatar_url":"https://avatars.githubusercontent.com/u/11707729?"},"repo":{"id":387916158,"name":"gabrieldonadel/pull-requests-limits","url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits"},"payload":{"ref":"5101","ref_type":"branch","master_branch":"master","description":"Testing Github Pull requests limits","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514808","type":"PushEvent","actor":{"id":33158585,"login":"byteHulk","display_login":"byteHulk","gravatar_id":"","url":"https://api.github.com/users/byteHulk","avatar_url":"https://avatars.githubusercontent.com/u/33158585?"},"repo":{"id":358445863,"name":"byteHulk/internalSkill","url":"https://api.github.com/repos/byteHulk/internalSkill"},"payload":{"push_id":7562055054,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"c1ee665ae82597e04f593c0d78d6d4005a8a800d","before":"d17d119586e3bb2b07c7b9160a28bf77d535cfaf","commits":[{"sha":"c1ee665ae82597e04f593c0d78d6d4005a8a800d","author":{"name":"bowenhuang","email":"a48dbbbfd670a244a2e493e29f6500aed8927cf8@aminer.cn"},"message":"doc:complete leetcode 25","distinct":true,"url":"https://api.github.com/repos/byteHulk/internalSkill/commits/c1ee665ae82597e04f593c0d78d6d4005a8a800d"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514809","type":"ForkEvent","actor":{"id":20126113,"login":"ardiantutomo","display_login":"ardiantutomo","gravatar_id":"","url":"https://api.github.com/users/ardiantutomo","avatar_url":"https://avatars.githubusercontent.com/u/20126113?"},"repo":{"id":46707446,"name":"Adobe-Marketing-Cloud/aem-translation-framework-bootstrap-connector","url":"https://api.github.com/repos/Adobe-Marketing-Cloud/aem-translation-framework-bootstrap-connector"},"payload":{"forkee":{"id":388040562,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1NjI=","name":"aem-translation-framework-bootstrap-connector","full_name":"ardiantutomo/aem-translation-framework-bootstrap-connector","private":false,"owner":{"login":"ardiantutomo","id":20126113,"node_id":"MDQ6VXNlcjIwMTI2MTEz","avatar_url":"https://avatars.githubusercontent.com/u/20126113?v=4","gravatar_id":"","url":"https://api.github.com/users/ardiantutomo","html_url":"https://github.com/ardiantutomo","followers_url":"https://api.github.com/users/ardiantutomo/followers","following_url":"https://api.github.com/users/ardiantutomo/following{/other_user}","gists_url":"https://api.github.com/users/ardiantutomo/gists{/gist_id}","starred_url":"https://api.github.com/users/ardiantutomo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ardiantutomo/subscriptions","organizations_url":"https://api.github.com/users/ardiantutomo/orgs","repos_url":"https://api.github.com/users/ardiantutomo/repos","events_url":"https://api.github.com/users/ardiantutomo/events{/privacy}","received_events_url":"https://api.github.com/users/ardiantutomo/received_events","type":"User","site_admin":false},"html_url":"https://github.com/ardiantutomo/aem-translation-framework-bootstrap-connector","description":"Bootstrap Translation connector using the Adobe Experience Manager (AEM) Translation Vendor API","fork":true,"url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector","forks_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/forks","keys_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/teams","hooks_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/hooks","issue_events_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/issues/events{/number}","events_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/events","assignees_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/assignees{/user}","branches_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/branches{/branch}","tags_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/tags","blobs_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/git/refs{/sha}","trees_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/statuses/{sha}","languages_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/languages","stargazers_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/stargazers","contributors_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/contributors","subscribers_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/subscribers","subscription_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/subscription","commits_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/commits{/sha}","git_commits_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/git/commits{/sha}","comments_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/comments{/number}","issue_comment_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/issues/comments{/number}","contents_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/contents/{+path}","compare_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/merges","archive_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/downloads","issues_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/issues{/number}","pulls_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/pulls{/number}","milestones_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/milestones{/number}","notifications_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/labels{/name}","releases_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/releases{/id}","deployments_url":"https://api.github.com/repos/ardiantutomo/aem-translation-framework-bootstrap-connector/deployments","created_at":"2021-07-21T08:00:00Z","updated_at":"2021-04-14T09:00:07Z","pushed_at":"2020-07-30T12:23:54Z","git_url":"git://github.com/ardiantutomo/aem-translation-framework-bootstrap-connector.git","ssh_url":"git@github.com:ardiantutomo/aem-translation-framework-bootstrap-connector.git","clone_url":"https://github.com/ardiantutomo/aem-translation-framework-bootstrap-connector.git","svn_url":"https://github.com/ardiantutomo/aem-translation-framework-bootstrap-connector","homepage":"https://helpx.adobe.com/experience-manager/using/bootstrap.html","size":2111,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:01Z","org":{"id":1440554,"login":"Adobe-Marketing-Cloud","gravatar_id":"","url":"https://api.github.com/orgs/Adobe-Marketing-Cloud","avatar_url":"https://avatars.githubusercontent.com/u/1440554?"}} +{"id":"17245514813","type":"PushEvent","actor":{"id":34954731,"login":"jereldlimjy","display_login":"jereldlimjy","gravatar_id":"","url":"https://api.github.com/users/jereldlimjy","avatar_url":"https://avatars.githubusercontent.com/u/34954731?"},"repo":{"id":365973048,"name":"jereldlimjy/zilswap-webapp","url":"https://api.github.com/repos/jereldlimjy/zilswap-webapp"},"payload":{"push_id":7562055045,"size":2,"distinct_size":2,"ref":"refs/heads/bridge","head":"f1156f0de0fb43a3c55a547a7210ad073104729e","before":"dcdf061d55232da43b5f8eddfb9550bebf22d569","commits":[{"sha":"66898de2ad3a06cd3c562f1a3780a0cde1cb69c2","author":{"name":"Jereld Lim","email":"56b885a0714e6a7a484ba8cc02bc53bc315d5670@hotmail.com"},"message":"revert staging changes","distinct":true,"url":"https://api.github.com/repos/jereldlimjy/zilswap-webapp/commits/66898de2ad3a06cd3c562f1a3780a0cde1cb69c2"},{"sha":"f1156f0de0fb43a3c55a547a7210ad073104729e","author":{"name":"Jereld Lim","email":"56b885a0714e6a7a484ba8cc02bc53bc315d5670@hotmail.com"},"message":"updates","distinct":true,"url":"https://api.github.com/repos/jereldlimjy/zilswap-webapp/commits/f1156f0de0fb43a3c55a547a7210ad073104729e"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514815","type":"CreateEvent","actor":{"id":47575445,"login":"nikolvyskvarko","display_login":"nikolvyskvarko","gravatar_id":"","url":"https://api.github.com/users/nikolvyskvarko","avatar_url":"https://avatars.githubusercontent.com/u/47575445?"},"repo":{"id":388040561,"name":"nikolvyskvarko/StepTracker","url":"https://api.github.com/repos/nikolvyskvarko/StepTracker"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514820","type":"PushEvent","actor":{"id":82073759,"login":"oshima-yoppi","display_login":"oshima-yoppi","gravatar_id":"","url":"https://api.github.com/users/oshima-yoppi","avatar_url":"https://avatars.githubusercontent.com/u/82073759?"},"repo":{"id":367556643,"name":"Koji1116/Cansat2021ver","url":"https://api.github.com/repos/Koji1116/Cansat2021ver"},"payload":{"push_id":7562055056,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"2f5abe265b19914a6ecc1862f7ed5d7351a77f2f","before":"38115a8c230e2ade06419f1646fa7427f4f3c3d8","commits":[{"sha":"2f5abe265b19914a6ecc1862f7ed5d7351a77f2f","author":{"name":"gitub","email":"a1c235cfd221332c9f7fdcd9b8d925537c973383@users.noreply.github.com"},"message":"Update panoramashooting.py","distinct":true,"url":"https://api.github.com/repos/Koji1116/Cansat2021ver/commits/2f5abe265b19914a6ecc1862f7ed5d7351a77f2f"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514823","type":"PushEvent","actor":{"id":68417258,"login":"breakingheatmap","display_login":"breakingheatmap","gravatar_id":"","url":"https://api.github.com/users/breakingheatmap","avatar_url":"https://avatars.githubusercontent.com/u/68417258?"},"repo":{"id":280356388,"name":"breakingheatmap/breakingheatmap","url":"https://api.github.com/repos/breakingheatmap/breakingheatmap"},"payload":{"push_id":7562055062,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"150a35def34f0b26cea52a35cb49a24444a6c062","before":"4accee5358cac0b04702ebd3fc11938385222e35","commits":[{"sha":"150a35def34f0b26cea52a35cb49a24444a6c062","author":{"name":"sudomaze","email":"6fbf11c1befd5a6b40696150a1a62f21b2fc7860@gmail.com"},"message":"1626854397","distinct":true,"url":"https://api.github.com/repos/breakingheatmap/breakingheatmap/commits/150a35def34f0b26cea52a35cb49a24444a6c062"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514827","type":"PushEvent","actor":{"id":52290251,"login":"botcopado","display_login":"botcopado","gravatar_id":"","url":"https://api.github.com/users/botcopado","avatar_url":"https://avatars.githubusercontent.com/u/52290251?"},"repo":{"id":388040501,"name":"botcopado/moeldv_DSLQ77BE","url":"https://api.github.com/repos/botcopado/moeldv_DSLQ77BE"},"payload":{"push_id":7562055065,"size":1,"distinct_size":1,"ref":"refs/heads/feature/US-0062599","head":"2d0cfe019a81e1718bbee986b3b91111751b0756","before":"93d1fb4fb6e7902ae9084f0e4a4cdead533aeb7e","commits":[{"sha":"2d0cfe019a81e1718bbee986b3b91111751b0756","author":{"name":"Test QA User","email":"0aeb9bf205e33c465a83ad48c5898f09dd71cceb@copado.com"},"message":"Bot initialization","distinct":true,"url":"https://api.github.com/repos/botcopado/moeldv_DSLQ77BE/commits/2d0cfe019a81e1718bbee986b3b91111751b0756"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514840","type":"PushEvent","actor":{"id":85116961,"login":"tomdavie","display_login":"tomdavie","gravatar_id":"","url":"https://api.github.com/users/tomdavie","avatar_url":"https://avatars.githubusercontent.com/u/85116961?"},"repo":{"id":378964842,"name":"tomdavie/codeclan_homework_tomdavie","url":"https://api.github.com/repos/tomdavie/codeclan_homework_tomdavie"},"payload":{"push_id":7562055060,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"1d03eb2da7af699859cf154a64a4010caaa9c74a","before":"725596fc5d1285a9d02b0fd092b722edd827e3d3","commits":[{"sha":"1d03eb2da7af699859cf154a64a4010caaa9c74a","author":{"name":"tomdavie","email":"e21b6ad2861613f260bb81a97977059c9aa94331@users.noreply.github.com"},"message":"Update","distinct":true,"url":"https://api.github.com/repos/tomdavie/codeclan_homework_tomdavie/commits/1d03eb2da7af699859cf154a64a4010caaa9c74a"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514852","type":"PullRequestEvent","actor":{"id":10810283,"login":"direwolf-github","display_login":"direwolf-github","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","avatar_url":"https://avatars.githubusercontent.com/u/10810283?"},"repo":{"id":388040450,"name":"direwolf-github/my-app-5f19618c","url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/1","id":694141419,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNDE5","html_url":"https://github.com/direwolf-github/my-app-5f19618c/pull/1","diff_url":"https://github.com/direwolf-github/my-app-5f19618c/pull/1.diff","patch_url":"https://github.com/direwolf-github/my-app-5f19618c/pull/1.patch","issue_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/1","number":1,"state":"closed","locked":false,"title":"Direwolf review apps test branch-c9b911a8","user":{"login":"direwolf-github","id":10810283,"node_id":"MDQ6VXNlcjEwODEwMjgz","avatar_url":"https://avatars.githubusercontent.com/u/10810283?v=4","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","html_url":"https://github.com/direwolf-github","followers_url":"https://api.github.com/users/direwolf-github/followers","following_url":"https://api.github.com/users/direwolf-github/following{/other_user}","gists_url":"https://api.github.com/users/direwolf-github/gists{/gist_id}","starred_url":"https://api.github.com/users/direwolf-github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/direwolf-github/subscriptions","organizations_url":"https://api.github.com/users/direwolf-github/orgs","repos_url":"https://api.github.com/users/direwolf-github/repos","events_url":"https://api.github.com/users/direwolf-github/events{/privacy}","received_events_url":"https://api.github.com/users/direwolf-github/received_events","type":"User","site_admin":false},"body":"Direwolf review apps test branch-c9b911a8","created_at":"2021-07-21T07:59:39Z","updated_at":"2021-07-21T08:00:01Z","closed_at":"2021-07-21T08:00:01Z","merged_at":null,"merge_commit_sha":"f05eb5150c66048d8714910e3014ec5ad32fb45d","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/1/commits","review_comments_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/1/comments","review_comment_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/comments{/number}","comments_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/1/comments","statuses_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/statuses/fce7408bee7aef10398bec8e196d28b83aa93278","head":{"label":"direwolf-github:branch-c9b911a8","ref":"branch-c9b911a8","sha":"fce7408bee7aef10398bec8e196d28b83aa93278","user":{"login":"direwolf-github","id":10810283,"node_id":"MDQ6VXNlcjEwODEwMjgz","avatar_url":"https://avatars.githubusercontent.com/u/10810283?v=4","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","html_url":"https://github.com/direwolf-github","followers_url":"https://api.github.com/users/direwolf-github/followers","following_url":"https://api.github.com/users/direwolf-github/following{/other_user}","gists_url":"https://api.github.com/users/direwolf-github/gists{/gist_id}","starred_url":"https://api.github.com/users/direwolf-github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/direwolf-github/subscriptions","organizations_url":"https://api.github.com/users/direwolf-github/orgs","repos_url":"https://api.github.com/users/direwolf-github/repos","events_url":"https://api.github.com/users/direwolf-github/events{/privacy}","received_events_url":"https://api.github.com/users/direwolf-github/received_events","type":"User","site_admin":false},"repo":{"id":388040450,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA0NTA=","name":"my-app-5f19618c","full_name":"direwolf-github/my-app-5f19618c","private":false,"owner":{"login":"direwolf-github","id":10810283,"node_id":"MDQ6VXNlcjEwODEwMjgz","avatar_url":"https://avatars.githubusercontent.com/u/10810283?v=4","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","html_url":"https://github.com/direwolf-github","followers_url":"https://api.github.com/users/direwolf-github/followers","following_url":"https://api.github.com/users/direwolf-github/following{/other_user}","gists_url":"https://api.github.com/users/direwolf-github/gists{/gist_id}","starred_url":"https://api.github.com/users/direwolf-github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/direwolf-github/subscriptions","organizations_url":"https://api.github.com/users/direwolf-github/orgs","repos_url":"https://api.github.com/users/direwolf-github/repos","events_url":"https://api.github.com/users/direwolf-github/events{/privacy}","received_events_url":"https://api.github.com/users/direwolf-github/received_events","type":"User","site_admin":false},"html_url":"https://github.com/direwolf-github/my-app-5f19618c","description":null,"fork":false,"url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c","forks_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/forks","keys_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/keys{/key_id}","collaborators_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/teams","hooks_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/hooks","issue_events_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/events{/number}","events_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/events","assignees_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/assignees{/user}","branches_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/branches{/branch}","tags_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/tags","blobs_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/refs{/sha}","trees_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/trees{/sha}","statuses_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/statuses/{sha}","languages_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/languages","stargazers_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/stargazers","contributors_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/contributors","subscribers_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/subscribers","subscription_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/subscription","commits_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/commits{/sha}","git_commits_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/commits{/sha}","comments_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/comments{/number}","issue_comment_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/comments{/number}","contents_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/contents/{+path}","compare_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/compare/{base}...{head}","merges_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/merges","archive_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/downloads","issues_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues{/number}","pulls_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls{/number}","milestones_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/milestones{/number}","notifications_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/labels{/name}","releases_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/releases{/id}","deployments_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/deployments","created_at":"2021-07-21T07:59:33Z","updated_at":"2021-07-21T07:59:36Z","pushed_at":"2021-07-21T08:00:00Z","git_url":"git://github.com/direwolf-github/my-app-5f19618c.git","ssh_url":"git@github.com:direwolf-github/my-app-5f19618c.git","clone_url":"https://github.com/direwolf-github/my-app-5f19618c.git","svn_url":"https://github.com/direwolf-github/my-app-5f19618c","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"direwolf-github:master","ref":"master","sha":"24b649c0ada6e7be3ffd70fc5368b6bfd1ff602b","user":{"login":"direwolf-github","id":10810283,"node_id":"MDQ6VXNlcjEwODEwMjgz","avatar_url":"https://avatars.githubusercontent.com/u/10810283?v=4","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","html_url":"https://github.com/direwolf-github","followers_url":"https://api.github.com/users/direwolf-github/followers","following_url":"https://api.github.com/users/direwolf-github/following{/other_user}","gists_url":"https://api.github.com/users/direwolf-github/gists{/gist_id}","starred_url":"https://api.github.com/users/direwolf-github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/direwolf-github/subscriptions","organizations_url":"https://api.github.com/users/direwolf-github/orgs","repos_url":"https://api.github.com/users/direwolf-github/repos","events_url":"https://api.github.com/users/direwolf-github/events{/privacy}","received_events_url":"https://api.github.com/users/direwolf-github/received_events","type":"User","site_admin":false},"repo":{"id":388040450,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA0NTA=","name":"my-app-5f19618c","full_name":"direwolf-github/my-app-5f19618c","private":false,"owner":{"login":"direwolf-github","id":10810283,"node_id":"MDQ6VXNlcjEwODEwMjgz","avatar_url":"https://avatars.githubusercontent.com/u/10810283?v=4","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","html_url":"https://github.com/direwolf-github","followers_url":"https://api.github.com/users/direwolf-github/followers","following_url":"https://api.github.com/users/direwolf-github/following{/other_user}","gists_url":"https://api.github.com/users/direwolf-github/gists{/gist_id}","starred_url":"https://api.github.com/users/direwolf-github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/direwolf-github/subscriptions","organizations_url":"https://api.github.com/users/direwolf-github/orgs","repos_url":"https://api.github.com/users/direwolf-github/repos","events_url":"https://api.github.com/users/direwolf-github/events{/privacy}","received_events_url":"https://api.github.com/users/direwolf-github/received_events","type":"User","site_admin":false},"html_url":"https://github.com/direwolf-github/my-app-5f19618c","description":null,"fork":false,"url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c","forks_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/forks","keys_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/keys{/key_id}","collaborators_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/teams","hooks_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/hooks","issue_events_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/events{/number}","events_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/events","assignees_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/assignees{/user}","branches_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/branches{/branch}","tags_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/tags","blobs_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/refs{/sha}","trees_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/trees{/sha}","statuses_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/statuses/{sha}","languages_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/languages","stargazers_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/stargazers","contributors_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/contributors","subscribers_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/subscribers","subscription_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/subscription","commits_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/commits{/sha}","git_commits_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/git/commits{/sha}","comments_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/comments{/number}","issue_comment_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/comments{/number}","contents_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/contents/{+path}","compare_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/compare/{base}...{head}","merges_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/merges","archive_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/downloads","issues_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues{/number}","pulls_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls{/number}","milestones_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/milestones{/number}","notifications_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/labels{/name}","releases_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/releases{/id}","deployments_url":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/deployments","created_at":"2021-07-21T07:59:33Z","updated_at":"2021-07-21T07:59:36Z","pushed_at":"2021-07-21T08:00:00Z","git_url":"git://github.com/direwolf-github/my-app-5f19618c.git","ssh_url":"git@github.com:direwolf-github/my-app-5f19618c.git","clone_url":"https://github.com/direwolf-github/my-app-5f19618c.git","svn_url":"https://github.com/direwolf-github/my-app-5f19618c","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/1"},"html":{"href":"https://github.com/direwolf-github/my-app-5f19618c/pull/1"},"issue":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/1"},"comments":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/direwolf-github/my-app-5f19618c/statuses/fce7408bee7aef10398bec8e196d28b83aa93278"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":true,"rebaseable":true,"mergeable_state":"clean","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":0,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514865","type":"PushEvent","actor":{"id":54167233,"login":"ccqstark","display_login":"ccqstark","gravatar_id":"","url":"https://api.github.com/users/ccqstark","avatar_url":"https://avatars.githubusercontent.com/u/54167233?"},"repo":{"id":387765739,"name":"ccqstark/image-bed","url":"https://api.github.com/repos/ccqstark/image-bed"},"payload":{"push_id":7562055081,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"2343c2993a9a44ff2fabb9ca4e94778019d1924e","before":"b2eb027a76f484b38ba76acb54507ca8ebbb97dc","commits":[{"sha":"2343c2993a9a44ff2fabb9ca4e94778019d1924e","author":{"name":"ccqstark","email":"30878b5c8d2997c6529b8afd13070e4f73722fa1@users.noreply.github.com"},"message":"Upload by PicGo","distinct":true,"url":"https://api.github.com/repos/ccqstark/image-bed/commits/2343c2993a9a44ff2fabb9ca4e94778019d1924e"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514872","type":"PushEvent","actor":{"id":85176794,"login":"kaneki-op","display_login":"kaneki-op","gravatar_id":"","url":"https://api.github.com/users/kaneki-op","avatar_url":"https://avatars.githubusercontent.com/u/85176794?"},"repo":{"id":377739699,"name":"kaneki-op/op.tv","url":"https://api.github.com/repos/kaneki-op/op.tv"},"payload":{"push_id":7562055093,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"1a466ab78339ba194d20e5b181412089033450a2","before":"a4c4833d386b78c030d7c3138ea8e1c63357fe83","commits":[{"sha":"1a466ab78339ba194d20e5b181412089033450a2","author":{"name":"prakash-op","email":"92a4dc4a20f9188038025a3386c31841cf56ca43@users.noreply.github.com"},"message":"Update index.html","distinct":true,"url":"https://api.github.com/repos/kaneki-op/op.tv/commits/1a466ab78339ba194d20e5b181412089033450a2"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514876","type":"ForkEvent","actor":{"id":34512644,"login":"qq1134093272","display_login":"qq1134093272","gravatar_id":"","url":"https://api.github.com/users/qq1134093272","avatar_url":"https://avatars.githubusercontent.com/u/34512644?"},"repo":{"id":186777581,"name":"CheckChe0803/flink-recommandSystem-demo","url":"https://api.github.com/repos/CheckChe0803/flink-recommandSystem-demo"},"payload":{"forkee":{"id":388040563,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1NjM=","name":"flink-recommandSystem-demo","full_name":"qq1134093272/flink-recommandSystem-demo","private":false,"owner":{"login":"qq1134093272","id":34512644,"node_id":"MDQ6VXNlcjM0NTEyNjQ0","avatar_url":"https://avatars.githubusercontent.com/u/34512644?v=4","gravatar_id":"","url":"https://api.github.com/users/qq1134093272","html_url":"https://github.com/qq1134093272","followers_url":"https://api.github.com/users/qq1134093272/followers","following_url":"https://api.github.com/users/qq1134093272/following{/other_user}","gists_url":"https://api.github.com/users/qq1134093272/gists{/gist_id}","starred_url":"https://api.github.com/users/qq1134093272/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/qq1134093272/subscriptions","organizations_url":"https://api.github.com/users/qq1134093272/orgs","repos_url":"https://api.github.com/users/qq1134093272/repos","events_url":"https://api.github.com/users/qq1134093272/events{/privacy}","received_events_url":"https://api.github.com/users/qq1134093272/received_events","type":"User","site_admin":false},"html_url":"https://github.com/qq1134093272/flink-recommandSystem-demo","description":":helicopter::rocket:基于Flink实现的商品实时推荐系统。flink统计商品热度,放入redis缓存,分析日志信息,将画像标签和实时记录放入Hbase。在用户发起推荐请求后,根据用户画像重排序热度榜,并结合协同过滤和标签两个推荐模块为新生成的榜单的每一个产品添加关联产品,最后返回新的用户列表。","fork":true,"url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo","forks_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/forks","keys_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/teams","hooks_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/hooks","issue_events_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/issues/events{/number}","events_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/events","assignees_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/assignees{/user}","branches_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/branches{/branch}","tags_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/tags","blobs_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/git/refs{/sha}","trees_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/statuses/{sha}","languages_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/languages","stargazers_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/stargazers","contributors_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/contributors","subscribers_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/subscribers","subscription_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/subscription","commits_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/commits{/sha}","git_commits_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/git/commits{/sha}","comments_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/comments{/number}","issue_comment_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/issues/comments{/number}","contents_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/contents/{+path}","compare_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/merges","archive_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/downloads","issues_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/issues{/number}","pulls_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/pulls{/number}","milestones_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/milestones{/number}","notifications_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/labels{/name}","releases_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/releases{/id}","deployments_url":"https://api.github.com/repos/qq1134093272/flink-recommandSystem-demo/deployments","created_at":"2021-07-21T08:00:01Z","updated_at":"2021-07-21T01:39:15Z","pushed_at":"2021-07-02T02:07:51Z","git_url":"git://github.com/qq1134093272/flink-recommandSystem-demo.git","ssh_url":"git@github.com:qq1134093272/flink-recommandSystem-demo.git","clone_url":"https://github.com/qq1134093272/flink-recommandSystem-demo.git","svn_url":"https://github.com/qq1134093272/flink-recommandSystem-demo","homepage":"","size":4894,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514887","type":"ForkEvent","actor":{"id":10127287,"login":"miludan","display_login":"miludan","gravatar_id":"","url":"https://api.github.com/users/miludan","avatar_url":"https://avatars.githubusercontent.com/u/10127287?"},"repo":{"id":2203645,"name":"CocoaPods/CocoaPods","url":"https://api.github.com/repos/CocoaPods/CocoaPods"},"payload":{"forkee":{"id":388040566,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1NjY=","name":"CocoaPods","full_name":"miludan/CocoaPods","private":false,"owner":{"login":"miludan","id":10127287,"node_id":"MDQ6VXNlcjEwMTI3Mjg3","avatar_url":"https://avatars.githubusercontent.com/u/10127287?v=4","gravatar_id":"","url":"https://api.github.com/users/miludan","html_url":"https://github.com/miludan","followers_url":"https://api.github.com/users/miludan/followers","following_url":"https://api.github.com/users/miludan/following{/other_user}","gists_url":"https://api.github.com/users/miludan/gists{/gist_id}","starred_url":"https://api.github.com/users/miludan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/miludan/subscriptions","organizations_url":"https://api.github.com/users/miludan/orgs","repos_url":"https://api.github.com/users/miludan/repos","events_url":"https://api.github.com/users/miludan/events{/privacy}","received_events_url":"https://api.github.com/users/miludan/received_events","type":"User","site_admin":false},"html_url":"https://github.com/miludan/CocoaPods","description":"The Cocoa Dependency Manager.","fork":true,"url":"https://api.github.com/repos/miludan/CocoaPods","forks_url":"https://api.github.com/repos/miludan/CocoaPods/forks","keys_url":"https://api.github.com/repos/miludan/CocoaPods/keys{/key_id}","collaborators_url":"https://api.github.com/repos/miludan/CocoaPods/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/miludan/CocoaPods/teams","hooks_url":"https://api.github.com/repos/miludan/CocoaPods/hooks","issue_events_url":"https://api.github.com/repos/miludan/CocoaPods/issues/events{/number}","events_url":"https://api.github.com/repos/miludan/CocoaPods/events","assignees_url":"https://api.github.com/repos/miludan/CocoaPods/assignees{/user}","branches_url":"https://api.github.com/repos/miludan/CocoaPods/branches{/branch}","tags_url":"https://api.github.com/repos/miludan/CocoaPods/tags","blobs_url":"https://api.github.com/repos/miludan/CocoaPods/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/miludan/CocoaPods/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/miludan/CocoaPods/git/refs{/sha}","trees_url":"https://api.github.com/repos/miludan/CocoaPods/git/trees{/sha}","statuses_url":"https://api.github.com/repos/miludan/CocoaPods/statuses/{sha}","languages_url":"https://api.github.com/repos/miludan/CocoaPods/languages","stargazers_url":"https://api.github.com/repos/miludan/CocoaPods/stargazers","contributors_url":"https://api.github.com/repos/miludan/CocoaPods/contributors","subscribers_url":"https://api.github.com/repos/miludan/CocoaPods/subscribers","subscription_url":"https://api.github.com/repos/miludan/CocoaPods/subscription","commits_url":"https://api.github.com/repos/miludan/CocoaPods/commits{/sha}","git_commits_url":"https://api.github.com/repos/miludan/CocoaPods/git/commits{/sha}","comments_url":"https://api.github.com/repos/miludan/CocoaPods/comments{/number}","issue_comment_url":"https://api.github.com/repos/miludan/CocoaPods/issues/comments{/number}","contents_url":"https://api.github.com/repos/miludan/CocoaPods/contents/{+path}","compare_url":"https://api.github.com/repos/miludan/CocoaPods/compare/{base}...{head}","merges_url":"https://api.github.com/repos/miludan/CocoaPods/merges","archive_url":"https://api.github.com/repos/miludan/CocoaPods/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/miludan/CocoaPods/downloads","issues_url":"https://api.github.com/repos/miludan/CocoaPods/issues{/number}","pulls_url":"https://api.github.com/repos/miludan/CocoaPods/pulls{/number}","milestones_url":"https://api.github.com/repos/miludan/CocoaPods/milestones{/number}","notifications_url":"https://api.github.com/repos/miludan/CocoaPods/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/miludan/CocoaPods/labels{/name}","releases_url":"https://api.github.com/repos/miludan/CocoaPods/releases{/id}","deployments_url":"https://api.github.com/repos/miludan/CocoaPods/deployments","created_at":"2021-07-21T08:00:01Z","updated_at":"2021-07-20T22:20:03Z","pushed_at":"2021-07-20T22:19:59Z","git_url":"git://github.com/miludan/CocoaPods.git","ssh_url":"git@github.com:miludan/CocoaPods.git","clone_url":"https://github.com/miludan/CocoaPods.git","svn_url":"https://github.com/miludan/CocoaPods","homepage":"https://cocoapods.org/","size":76278,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:01Z","org":{"id":1189714,"login":"CocoaPods","gravatar_id":"","url":"https://api.github.com/orgs/CocoaPods","avatar_url":"https://avatars.githubusercontent.com/u/1189714?"}} +{"id":"17245514889","type":"PullRequestReviewEvent","actor":{"id":372735,"login":"svenefftinge","display_login":"svenefftinge","gravatar_id":"","url":"https://api.github.com/users/svenefftinge","avatar_url":"https://avatars.githubusercontent.com/u/372735?"},"repo":{"id":130879558,"name":"gitpod-io/gitpod","url":"https://api.github.com/repos/gitpod-io/gitpod"},"payload":{"action":"created","review":{"id":711364882,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzY0ODgy","user":{"login":"svenefftinge","id":372735,"node_id":"MDQ6VXNlcjM3MjczNQ==","avatar_url":"https://avatars.githubusercontent.com/u/372735?v=4","gravatar_id":"","url":"https://api.github.com/users/svenefftinge","html_url":"https://github.com/svenefftinge","followers_url":"https://api.github.com/users/svenefftinge/followers","following_url":"https://api.github.com/users/svenefftinge/following{/other_user}","gists_url":"https://api.github.com/users/svenefftinge/gists{/gist_id}","starred_url":"https://api.github.com/users/svenefftinge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/svenefftinge/subscriptions","organizations_url":"https://api.github.com/users/svenefftinge/orgs","repos_url":"https://api.github.com/users/svenefftinge/repos","events_url":"https://api.github.com/users/svenefftinge/events{/privacy}","received_events_url":"https://api.github.com/users/svenefftinge/received_events","type":"User","site_admin":false},"body":null,"commit_id":"8601021844904dcd946b303258e7d1f71cf4a7b6","submitted_at":"2021-07-21T08:00:01Z","state":"commented","html_url":"https://github.com/gitpod-io/gitpod/pull/4631#pullrequestreview-711364882","pull_request_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/gitpod-io/gitpod/pull/4631#pullrequestreview-711364882"},"pull_request":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631"}}},"pull_request":{"url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631","id":678865592,"node_id":"MDExOlB1bGxSZXF1ZXN0Njc4ODY1NTky","html_url":"https://github.com/gitpod-io/gitpod/pull/4631","diff_url":"https://github.com/gitpod-io/gitpod/pull/4631.diff","patch_url":"https://github.com/gitpod-io/gitpod/pull/4631.patch","issue_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631","number":4631,"state":"open","locked":false,"title":"Implement project configurator","user":{"login":"jankeromnes","id":599268,"node_id":"MDQ6VXNlcjU5OTI2OA==","avatar_url":"https://avatars.githubusercontent.com/u/599268?v=4","gravatar_id":"","url":"https://api.github.com/users/jankeromnes","html_url":"https://github.com/jankeromnes","followers_url":"https://api.github.com/users/jankeromnes/followers","following_url":"https://api.github.com/users/jankeromnes/following{/other_user}","gists_url":"https://api.github.com/users/jankeromnes/gists{/gist_id}","starred_url":"https://api.github.com/users/jankeromnes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jankeromnes/subscriptions","organizations_url":"https://api.github.com/users/jankeromnes/orgs","repos_url":"https://api.github.com/users/jankeromnes/repos","events_url":"https://api.github.com/users/jankeromnes/events{/privacy}","received_events_url":"https://api.github.com/users/jankeromnes/received_events","type":"User","site_admin":false},"body":"Initial pass to fix https://github.com/gitpod-io/gitpod/issues/4648\r\n\r\nDepends on & contains https://github.com/gitpod-io/gitpod/pull/4846\r\n\r\nWhat it does:\r\n\r\n- Adds a `config` (text) field to `d_b_project`, so that Projects can have a custom `.gitpod.yml` content\r\n- Adds a Project Configurator view in the dashboard, as final step in the Add Project flow (also currently accessible via a Project's UI)\r\n- When determining the config of a new workspace, use this priority order:\r\n 1. From repository\r\n 2. (_new_) From Project DB\r\n 3. From definitely-gp\r\n 4. Derived (Go) or default\r\n- When starting from a custom `.gitpod.yml` (from the Project DB), the initializer also places the uncomitted `.gitpod.yml` into the workspace (to encourage repo-based configs)\r\n\r\nHow to test:\r\n\r\n- Create a new team\r\n- Add a new project (either by inserting into `d_b_project`, or we set up a GitHub App integration -- @AlexTugarev could you help with the latter?)\r\n- If the project already has a repo-based config, the configurator should indicate that and be disabled. If there is no repo config, the configurator should allow you to write a new `.gitpod.yml` (e.g. based on one of the presets) and then test it / iterate on it in a quick loop (with the build output on the right)\r\n\r\nThe UX of this is still rough, but the goal is to add this final step rather soon, and then iterate on the entire Add Project flow.","created_at":"2021-06-28T09:18:16Z","updated_at":"2021-07-21T08:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"15cbca865d27363a6344dd9cdb57e4155d6384e6","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":3171280082,"node_id":"MDU6TGFiZWwzMTcxMjgwMDgy","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/approved","name":"approved","color":"ededed","default":false,"description":null},{"id":2481698797,"node_id":"MDU6TGFiZWwyNDgxNjk4Nzk3","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/component:%20dashboard","name":"component: dashboard","color":"e1e4e8","default":false,"description":""},{"id":3173195428,"node_id":"MDU6TGFiZWwzMTczMTk1NDI4","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/do-not-merge/hold","name":"do-not-merge/hold","color":"B60205","default":false,"description":""},{"id":3046862260,"node_id":"MDU6TGFiZWwzMDQ2ODYyMjYw","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/roadmap%20item:%20teams%20&%20projects","name":"roadmap item: teams & projects","color":"656769","default":false,"description":""},{"id":3171736349,"node_id":"MDU6TGFiZWwzMTcxNzM2MzQ5","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/size/XXL","name":"size/XXL","color":"BFD4F2","default":false,"description":""}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/commits","review_comments_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/comments","review_comment_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631/comments","statuses_url":"https://api.github.com/repos/gitpod-io/gitpod/statuses/8601021844904dcd946b303258e7d1f71cf4a7b6","head":{"label":"gitpod-io:jx/configure-project","ref":"jx/configure-project","sha":"8601021844904dcd946b303258e7d1f71cf4a7b6","user":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"repo":{"id":130879558,"node_id":"MDEwOlJlcG9zaXRvcnkxMzA4Nzk1NTg=","name":"gitpod","full_name":"gitpod-io/gitpod","private":false,"owner":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/gitpod-io/gitpod","description":"Gitpod automates the provisioning of ready-to-code development environments. ","fork":false,"url":"https://api.github.com/repos/gitpod-io/gitpod","forks_url":"https://api.github.com/repos/gitpod-io/gitpod/forks","keys_url":"https://api.github.com/repos/gitpod-io/gitpod/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gitpod-io/gitpod/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gitpod-io/gitpod/teams","hooks_url":"https://api.github.com/repos/gitpod-io/gitpod/hooks","issue_events_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/events{/number}","events_url":"https://api.github.com/repos/gitpod-io/gitpod/events","assignees_url":"https://api.github.com/repos/gitpod-io/gitpod/assignees{/user}","branches_url":"https://api.github.com/repos/gitpod-io/gitpod/branches{/branch}","tags_url":"https://api.github.com/repos/gitpod-io/gitpod/tags","blobs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gitpod-io/gitpod/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/refs{/sha}","trees_url":"https://api.github.com/repos/gitpod-io/gitpod/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gitpod-io/gitpod/statuses/{sha}","languages_url":"https://api.github.com/repos/gitpod-io/gitpod/languages","stargazers_url":"https://api.github.com/repos/gitpod-io/gitpod/stargazers","contributors_url":"https://api.github.com/repos/gitpod-io/gitpod/contributors","subscribers_url":"https://api.github.com/repos/gitpod-io/gitpod/subscribers","subscription_url":"https://api.github.com/repos/gitpod-io/gitpod/subscription","commits_url":"https://api.github.com/repos/gitpod-io/gitpod/commits{/sha}","git_commits_url":"https://api.github.com/repos/gitpod-io/gitpod/git/commits{/sha}","comments_url":"https://api.github.com/repos/gitpod-io/gitpod/comments{/number}","issue_comment_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/comments{/number}","contents_url":"https://api.github.com/repos/gitpod-io/gitpod/contents/{+path}","compare_url":"https://api.github.com/repos/gitpod-io/gitpod/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gitpod-io/gitpod/merges","archive_url":"https://api.github.com/repos/gitpod-io/gitpod/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gitpod-io/gitpod/downloads","issues_url":"https://api.github.com/repos/gitpod-io/gitpod/issues{/number}","pulls_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls{/number}","milestones_url":"https://api.github.com/repos/gitpod-io/gitpod/milestones{/number}","notifications_url":"https://api.github.com/repos/gitpod-io/gitpod/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gitpod-io/gitpod/labels{/name}","releases_url":"https://api.github.com/repos/gitpod-io/gitpod/releases{/id}","deployments_url":"https://api.github.com/repos/gitpod-io/gitpod/deployments","created_at":"2018-04-24T15:56:54Z","updated_at":"2021-07-21T07:36:46Z","pushed_at":"2021-07-21T07:59:25Z","git_url":"git://github.com/gitpod-io/gitpod.git","ssh_url":"git@github.com:gitpod-io/gitpod.git","clone_url":"https://github.com/gitpod-io/gitpod.git","svn_url":"https://github.com/gitpod-io/gitpod","homepage":"https://gitpod.io","size":46869,"stargazers_count":4483,"watchers_count":4483,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":530,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":394,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":530,"open_issues":394,"watchers":4483,"default_branch":"main"}},"base":{"label":"gitpod-io:main","ref":"main","sha":"0c778d9923eaa267712daa168d5ff2a94193ce2c","user":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"repo":{"id":130879558,"node_id":"MDEwOlJlcG9zaXRvcnkxMzA4Nzk1NTg=","name":"gitpod","full_name":"gitpod-io/gitpod","private":false,"owner":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/gitpod-io/gitpod","description":"Gitpod automates the provisioning of ready-to-code development environments. ","fork":false,"url":"https://api.github.com/repos/gitpod-io/gitpod","forks_url":"https://api.github.com/repos/gitpod-io/gitpod/forks","keys_url":"https://api.github.com/repos/gitpod-io/gitpod/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gitpod-io/gitpod/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gitpod-io/gitpod/teams","hooks_url":"https://api.github.com/repos/gitpod-io/gitpod/hooks","issue_events_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/events{/number}","events_url":"https://api.github.com/repos/gitpod-io/gitpod/events","assignees_url":"https://api.github.com/repos/gitpod-io/gitpod/assignees{/user}","branches_url":"https://api.github.com/repos/gitpod-io/gitpod/branches{/branch}","tags_url":"https://api.github.com/repos/gitpod-io/gitpod/tags","blobs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gitpod-io/gitpod/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/refs{/sha}","trees_url":"https://api.github.com/repos/gitpod-io/gitpod/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gitpod-io/gitpod/statuses/{sha}","languages_url":"https://api.github.com/repos/gitpod-io/gitpod/languages","stargazers_url":"https://api.github.com/repos/gitpod-io/gitpod/stargazers","contributors_url":"https://api.github.com/repos/gitpod-io/gitpod/contributors","subscribers_url":"https://api.github.com/repos/gitpod-io/gitpod/subscribers","subscription_url":"https://api.github.com/repos/gitpod-io/gitpod/subscription","commits_url":"https://api.github.com/repos/gitpod-io/gitpod/commits{/sha}","git_commits_url":"https://api.github.com/repos/gitpod-io/gitpod/git/commits{/sha}","comments_url":"https://api.github.com/repos/gitpod-io/gitpod/comments{/number}","issue_comment_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/comments{/number}","contents_url":"https://api.github.com/repos/gitpod-io/gitpod/contents/{+path}","compare_url":"https://api.github.com/repos/gitpod-io/gitpod/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gitpod-io/gitpod/merges","archive_url":"https://api.github.com/repos/gitpod-io/gitpod/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gitpod-io/gitpod/downloads","issues_url":"https://api.github.com/repos/gitpod-io/gitpod/issues{/number}","pulls_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls{/number}","milestones_url":"https://api.github.com/repos/gitpod-io/gitpod/milestones{/number}","notifications_url":"https://api.github.com/repos/gitpod-io/gitpod/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gitpod-io/gitpod/labels{/name}","releases_url":"https://api.github.com/repos/gitpod-io/gitpod/releases{/id}","deployments_url":"https://api.github.com/repos/gitpod-io/gitpod/deployments","created_at":"2018-04-24T15:56:54Z","updated_at":"2021-07-21T07:36:46Z","pushed_at":"2021-07-21T07:59:25Z","git_url":"git://github.com/gitpod-io/gitpod.git","ssh_url":"git@github.com:gitpod-io/gitpod.git","clone_url":"https://github.com/gitpod-io/gitpod.git","svn_url":"https://github.com/gitpod-io/gitpod","homepage":"https://gitpod.io","size":46869,"stargazers_count":4483,"watchers_count":4483,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":530,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":394,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":530,"open_issues":394,"watchers":4483,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631"},"html":{"href":"https://github.com/gitpod-io/gitpod/pull/4631"},"issue":{"href":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631"},"comments":{"href":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631/comments"},"review_comments":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/comments"},"review_comment":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/commits"},"statuses":{"href":"https://api.github.com/repos/gitpod-io/gitpod/statuses/8601021844904dcd946b303258e7d1f71cf4a7b6"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:01Z","org":{"id":37021919,"login":"gitpod-io","gravatar_id":"","url":"https://api.github.com/orgs/gitpod-io","avatar_url":"https://avatars.githubusercontent.com/u/37021919?"}} +{"id":"17245514894","type":"PushEvent","actor":{"id":25157114,"login":"kaelynchen","display_login":"kaelynchen","gravatar_id":"","url":"https://api.github.com/users/kaelynchen","avatar_url":"https://avatars.githubusercontent.com/u/25157114?"},"repo":{"id":386208907,"name":"kaelynchen/PurifyH5","url":"https://api.github.com/repos/kaelynchen/PurifyH5"},"payload":{"push_id":7562055089,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"1e9cc2a5a4c1bd778486d1414c9aeef4b51ce332","before":"37f45b13f0e1946f27fef1a4b9af44f5b4372dc0","commits":[{"sha":"1e9cc2a5a4c1bd778486d1414c9aeef4b51ce332","author":{"name":"kaelynchen","email":"622389b1dfdc3db8af29b94d5226b85d318be7db@qq.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/kaelynchen/PurifyH5/commits/1e9cc2a5a4c1bd778486d1414c9aeef4b51ce332"}]},"public":true,"created_at":"2021-07-21T08:00:01Z"} +{"id":"17245514901","type":"PullRequestReviewCommentEvent","actor":{"id":372735,"login":"svenefftinge","display_login":"svenefftinge","gravatar_id":"","url":"https://api.github.com/users/svenefftinge","avatar_url":"https://avatars.githubusercontent.com/u/372735?"},"repo":{"id":130879558,"name":"gitpod-io/gitpod","url":"https://api.github.com/repos/gitpod-io/gitpod"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/comments/673748169","pull_request_review_id":711364882,"id":673748169,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3Mzc0ODE2OQ==","diff_hunk":"@@ -26,6 +26,9 @@ export class DBProject {\n @Column()\n appInstallationId: string;\n \n+ @Column()\n+ config?: string;","path":"components/gitpod-db/src/typeorm/entity/db-project.ts","position":null,"original_position":5,"commit_id":"8601021844904dcd946b303258e7d1f71cf4a7b6","original_commit_id":"19834883c4ab1c287da2755330c6d40d83950f26","user":{"login":"svenefftinge","id":372735,"node_id":"MDQ6VXNlcjM3MjczNQ==","avatar_url":"https://avatars.githubusercontent.com/u/372735?v=4","gravatar_id":"","url":"https://api.github.com/users/svenefftinge","html_url":"https://github.com/svenefftinge","followers_url":"https://api.github.com/users/svenefftinge/followers","following_url":"https://api.github.com/users/svenefftinge/following{/other_user}","gists_url":"https://api.github.com/users/svenefftinge/gists{/gist_id}","starred_url":"https://api.github.com/users/svenefftinge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/svenefftinge/subscriptions","organizations_url":"https://api.github.com/users/svenefftinge/orgs","repos_url":"https://api.github.com/users/svenefftinge/repos","events_url":"https://api.github.com/users/svenefftinge/events{/privacy}","received_events_url":"https://api.github.com/users/svenefftinge/received_events","type":"User","site_admin":false},"body":"something like this:\r\n```\r\ntype ConfigPath = '.gitpod.yml' | '.gitpod.Dockerfile';\r\n@Column(\"simple-json\", { nullable: true }) \r\nconfig?: {[filePath: ConfigPath]: string}\r\n```","created_at":"2021-07-21T08:00:01Z","updated_at":"2021-07-21T08:00:01Z","html_url":"https://github.com/gitpod-io/gitpod/pull/4631#discussion_r673748169","pull_request_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/comments/673748169"},"html":{"href":"https://github.com/gitpod-io/gitpod/pull/4631#discussion_r673748169"},"pull_request":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631"}},"start_line":null,"original_start_line":null,"start_side":null,"line":null,"original_line":30,"side":"RIGHT","in_reply_to_id":672897330},"pull_request":{"url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631","id":678865592,"node_id":"MDExOlB1bGxSZXF1ZXN0Njc4ODY1NTky","html_url":"https://github.com/gitpod-io/gitpod/pull/4631","diff_url":"https://github.com/gitpod-io/gitpod/pull/4631.diff","patch_url":"https://github.com/gitpod-io/gitpod/pull/4631.patch","issue_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631","number":4631,"state":"open","locked":false,"title":"Implement project configurator","user":{"login":"jankeromnes","id":599268,"node_id":"MDQ6VXNlcjU5OTI2OA==","avatar_url":"https://avatars.githubusercontent.com/u/599268?v=4","gravatar_id":"","url":"https://api.github.com/users/jankeromnes","html_url":"https://github.com/jankeromnes","followers_url":"https://api.github.com/users/jankeromnes/followers","following_url":"https://api.github.com/users/jankeromnes/following{/other_user}","gists_url":"https://api.github.com/users/jankeromnes/gists{/gist_id}","starred_url":"https://api.github.com/users/jankeromnes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jankeromnes/subscriptions","organizations_url":"https://api.github.com/users/jankeromnes/orgs","repos_url":"https://api.github.com/users/jankeromnes/repos","events_url":"https://api.github.com/users/jankeromnes/events{/privacy}","received_events_url":"https://api.github.com/users/jankeromnes/received_events","type":"User","site_admin":false},"body":"Initial pass to fix https://github.com/gitpod-io/gitpod/issues/4648\r\n\r\nDepends on & contains https://github.com/gitpod-io/gitpod/pull/4846\r\n\r\nWhat it does:\r\n\r\n- Adds a `config` (text) field to `d_b_project`, so that Projects can have a custom `.gitpod.yml` content\r\n- Adds a Project Configurator view in the dashboard, as final step in the Add Project flow (also currently accessible via a Project's UI)\r\n- When determining the config of a new workspace, use this priority order:\r\n 1. From repository\r\n 2. (_new_) From Project DB\r\n 3. From definitely-gp\r\n 4. Derived (Go) or default\r\n- When starting from a custom `.gitpod.yml` (from the Project DB), the initializer also places the uncomitted `.gitpod.yml` into the workspace (to encourage repo-based configs)\r\n\r\nHow to test:\r\n\r\n- Create a new team\r\n- Add a new project (either by inserting into `d_b_project`, or we set up a GitHub App integration -- @AlexTugarev could you help with the latter?)\r\n- If the project already has a repo-based config, the configurator should indicate that and be disabled. If there is no repo config, the configurator should allow you to write a new `.gitpod.yml` (e.g. based on one of the presets) and then test it / iterate on it in a quick loop (with the build output on the right)\r\n\r\nThe UX of this is still rough, but the goal is to add this final step rather soon, and then iterate on the entire Add Project flow.","created_at":"2021-06-28T09:18:16Z","updated_at":"2021-07-21T08:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"15cbca865d27363a6344dd9cdb57e4155d6384e6","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":3171280082,"node_id":"MDU6TGFiZWwzMTcxMjgwMDgy","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/approved","name":"approved","color":"ededed","default":false,"description":null},{"id":2481698797,"node_id":"MDU6TGFiZWwyNDgxNjk4Nzk3","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/component:%20dashboard","name":"component: dashboard","color":"e1e4e8","default":false,"description":""},{"id":3173195428,"node_id":"MDU6TGFiZWwzMTczMTk1NDI4","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/do-not-merge/hold","name":"do-not-merge/hold","color":"B60205","default":false,"description":""},{"id":3046862260,"node_id":"MDU6TGFiZWwzMDQ2ODYyMjYw","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/roadmap%20item:%20teams%20&%20projects","name":"roadmap item: teams & projects","color":"656769","default":false,"description":""},{"id":3171736349,"node_id":"MDU6TGFiZWwzMTcxNzM2MzQ5","url":"https://api.github.com/repos/gitpod-io/gitpod/labels/size/XXL","name":"size/XXL","color":"BFD4F2","default":false,"description":""}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/commits","review_comments_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/comments","review_comment_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631/comments","statuses_url":"https://api.github.com/repos/gitpod-io/gitpod/statuses/8601021844904dcd946b303258e7d1f71cf4a7b6","head":{"label":"gitpod-io:jx/configure-project","ref":"jx/configure-project","sha":"8601021844904dcd946b303258e7d1f71cf4a7b6","user":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"repo":{"id":130879558,"node_id":"MDEwOlJlcG9zaXRvcnkxMzA4Nzk1NTg=","name":"gitpod","full_name":"gitpod-io/gitpod","private":false,"owner":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/gitpod-io/gitpod","description":"Gitpod automates the provisioning of ready-to-code development environments. ","fork":false,"url":"https://api.github.com/repos/gitpod-io/gitpod","forks_url":"https://api.github.com/repos/gitpod-io/gitpod/forks","keys_url":"https://api.github.com/repos/gitpod-io/gitpod/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gitpod-io/gitpod/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gitpod-io/gitpod/teams","hooks_url":"https://api.github.com/repos/gitpod-io/gitpod/hooks","issue_events_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/events{/number}","events_url":"https://api.github.com/repos/gitpod-io/gitpod/events","assignees_url":"https://api.github.com/repos/gitpod-io/gitpod/assignees{/user}","branches_url":"https://api.github.com/repos/gitpod-io/gitpod/branches{/branch}","tags_url":"https://api.github.com/repos/gitpod-io/gitpod/tags","blobs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gitpod-io/gitpod/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/refs{/sha}","trees_url":"https://api.github.com/repos/gitpod-io/gitpod/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gitpod-io/gitpod/statuses/{sha}","languages_url":"https://api.github.com/repos/gitpod-io/gitpod/languages","stargazers_url":"https://api.github.com/repos/gitpod-io/gitpod/stargazers","contributors_url":"https://api.github.com/repos/gitpod-io/gitpod/contributors","subscribers_url":"https://api.github.com/repos/gitpod-io/gitpod/subscribers","subscription_url":"https://api.github.com/repos/gitpod-io/gitpod/subscription","commits_url":"https://api.github.com/repos/gitpod-io/gitpod/commits{/sha}","git_commits_url":"https://api.github.com/repos/gitpod-io/gitpod/git/commits{/sha}","comments_url":"https://api.github.com/repos/gitpod-io/gitpod/comments{/number}","issue_comment_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/comments{/number}","contents_url":"https://api.github.com/repos/gitpod-io/gitpod/contents/{+path}","compare_url":"https://api.github.com/repos/gitpod-io/gitpod/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gitpod-io/gitpod/merges","archive_url":"https://api.github.com/repos/gitpod-io/gitpod/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gitpod-io/gitpod/downloads","issues_url":"https://api.github.com/repos/gitpod-io/gitpod/issues{/number}","pulls_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls{/number}","milestones_url":"https://api.github.com/repos/gitpod-io/gitpod/milestones{/number}","notifications_url":"https://api.github.com/repos/gitpod-io/gitpod/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gitpod-io/gitpod/labels{/name}","releases_url":"https://api.github.com/repos/gitpod-io/gitpod/releases{/id}","deployments_url":"https://api.github.com/repos/gitpod-io/gitpod/deployments","created_at":"2018-04-24T15:56:54Z","updated_at":"2021-07-21T07:36:46Z","pushed_at":"2021-07-21T07:59:25Z","git_url":"git://github.com/gitpod-io/gitpod.git","ssh_url":"git@github.com:gitpod-io/gitpod.git","clone_url":"https://github.com/gitpod-io/gitpod.git","svn_url":"https://github.com/gitpod-io/gitpod","homepage":"https://gitpod.io","size":46869,"stargazers_count":4483,"watchers_count":4483,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":530,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":394,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":530,"open_issues":394,"watchers":4483,"default_branch":"main"}},"base":{"label":"gitpod-io:main","ref":"main","sha":"0c778d9923eaa267712daa168d5ff2a94193ce2c","user":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"repo":{"id":130879558,"node_id":"MDEwOlJlcG9zaXRvcnkxMzA4Nzk1NTg=","name":"gitpod","full_name":"gitpod-io/gitpod","private":false,"owner":{"login":"gitpod-io","id":37021919,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MDIxOTE5","avatar_url":"https://avatars.githubusercontent.com/u/37021919?v=4","gravatar_id":"","url":"https://api.github.com/users/gitpod-io","html_url":"https://github.com/gitpod-io","followers_url":"https://api.github.com/users/gitpod-io/followers","following_url":"https://api.github.com/users/gitpod-io/following{/other_user}","gists_url":"https://api.github.com/users/gitpod-io/gists{/gist_id}","starred_url":"https://api.github.com/users/gitpod-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gitpod-io/subscriptions","organizations_url":"https://api.github.com/users/gitpod-io/orgs","repos_url":"https://api.github.com/users/gitpod-io/repos","events_url":"https://api.github.com/users/gitpod-io/events{/privacy}","received_events_url":"https://api.github.com/users/gitpod-io/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/gitpod-io/gitpod","description":"Gitpod automates the provisioning of ready-to-code development environments. ","fork":false,"url":"https://api.github.com/repos/gitpod-io/gitpod","forks_url":"https://api.github.com/repos/gitpod-io/gitpod/forks","keys_url":"https://api.github.com/repos/gitpod-io/gitpod/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gitpod-io/gitpod/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gitpod-io/gitpod/teams","hooks_url":"https://api.github.com/repos/gitpod-io/gitpod/hooks","issue_events_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/events{/number}","events_url":"https://api.github.com/repos/gitpod-io/gitpod/events","assignees_url":"https://api.github.com/repos/gitpod-io/gitpod/assignees{/user}","branches_url":"https://api.github.com/repos/gitpod-io/gitpod/branches{/branch}","tags_url":"https://api.github.com/repos/gitpod-io/gitpod/tags","blobs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gitpod-io/gitpod/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gitpod-io/gitpod/git/refs{/sha}","trees_url":"https://api.github.com/repos/gitpod-io/gitpod/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gitpod-io/gitpod/statuses/{sha}","languages_url":"https://api.github.com/repos/gitpod-io/gitpod/languages","stargazers_url":"https://api.github.com/repos/gitpod-io/gitpod/stargazers","contributors_url":"https://api.github.com/repos/gitpod-io/gitpod/contributors","subscribers_url":"https://api.github.com/repos/gitpod-io/gitpod/subscribers","subscription_url":"https://api.github.com/repos/gitpod-io/gitpod/subscription","commits_url":"https://api.github.com/repos/gitpod-io/gitpod/commits{/sha}","git_commits_url":"https://api.github.com/repos/gitpod-io/gitpod/git/commits{/sha}","comments_url":"https://api.github.com/repos/gitpod-io/gitpod/comments{/number}","issue_comment_url":"https://api.github.com/repos/gitpod-io/gitpod/issues/comments{/number}","contents_url":"https://api.github.com/repos/gitpod-io/gitpod/contents/{+path}","compare_url":"https://api.github.com/repos/gitpod-io/gitpod/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gitpod-io/gitpod/merges","archive_url":"https://api.github.com/repos/gitpod-io/gitpod/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gitpod-io/gitpod/downloads","issues_url":"https://api.github.com/repos/gitpod-io/gitpod/issues{/number}","pulls_url":"https://api.github.com/repos/gitpod-io/gitpod/pulls{/number}","milestones_url":"https://api.github.com/repos/gitpod-io/gitpod/milestones{/number}","notifications_url":"https://api.github.com/repos/gitpod-io/gitpod/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gitpod-io/gitpod/labels{/name}","releases_url":"https://api.github.com/repos/gitpod-io/gitpod/releases{/id}","deployments_url":"https://api.github.com/repos/gitpod-io/gitpod/deployments","created_at":"2018-04-24T15:56:54Z","updated_at":"2021-07-21T07:36:46Z","pushed_at":"2021-07-21T07:59:25Z","git_url":"git://github.com/gitpod-io/gitpod.git","ssh_url":"git@github.com:gitpod-io/gitpod.git","clone_url":"https://github.com/gitpod-io/gitpod.git","svn_url":"https://github.com/gitpod-io/gitpod","homepage":"https://gitpod.io","size":46869,"stargazers_count":4483,"watchers_count":4483,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":530,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":394,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":530,"open_issues":394,"watchers":4483,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631"},"html":{"href":"https://github.com/gitpod-io/gitpod/pull/4631"},"issue":{"href":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631"},"comments":{"href":"https://api.github.com/repos/gitpod-io/gitpod/issues/4631/comments"},"review_comments":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/comments"},"review_comment":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gitpod-io/gitpod/pulls/4631/commits"},"statuses":{"href":"https://api.github.com/repos/gitpod-io/gitpod/statuses/8601021844904dcd946b303258e7d1f71cf4a7b6"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:01Z","org":{"id":37021919,"login":"gitpod-io","gravatar_id":"","url":"https://api.github.com/orgs/gitpod-io","avatar_url":"https://avatars.githubusercontent.com/u/37021919?"}} +{"id":"17245514907","type":"CreateEvent","actor":{"id":52290251,"login":"botcopado","display_login":"botcopado","gravatar_id":"","url":"https://api.github.com/users/botcopado","avatar_url":"https://avatars.githubusercontent.com/u/52290251?"},"repo":{"id":388040565,"name":"botcopado/moeldv_YBFPER8P","url":"https://api.github.com/repos/botcopado/moeldv_YBFPER8P"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514908","type":"PushEvent","actor":{"id":8111373,"login":"neocarto","display_login":"neocarto","gravatar_id":"","url":"https://api.github.com/users/neocarto","avatar_url":"https://avatars.githubusercontent.com/u/8111373?"},"repo":{"id":388028387,"name":"neocarto/cv","url":"https://api.github.com/repos/neocarto/cv"},"payload":{"push_id":7562055114,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"0345269f2cef1e280450b16f260aed34023340e0","before":"37ce16c4f7a879d769e950fcfdc73d10fba8d21c","commits":[{"sha":"0345269f2cef1e280450b16f260aed34023340e0","author":{"name":"Nicolas LAMBERT","email":"1aaec5f0bf5fb2c6a413b1cd6b8525d76afb0aee@ums-riate.fr"},"message":"Set theme jekyll-theme-hacker","distinct":true,"url":"https://api.github.com/repos/neocarto/cv/commits/0345269f2cef1e280450b16f260aed34023340e0"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514910","type":"PushEvent","actor":{"id":62052913,"login":"venkuppu-chn","display_login":"venkuppu-chn","gravatar_id":"","url":"https://api.github.com/users/venkuppu-chn","avatar_url":"https://avatars.githubusercontent.com/u/62052913?"},"repo":{"id":385274986,"name":"venkuppu-chn/cortx-hare","url":"https://api.github.com/repos/venkuppu-chn/cortx-hare"},"payload":{"push_id":7562055086,"size":2,"distinct_size":2,"ref":"refs/heads/main","head":"416863176f1fe09a6b23156316ad9878113fc98d","before":"254735da6d7fa2838abf48627cfb585a12cdca04","commits":[{"sha":"4838966677335e718d32cae1a70f78c8110bac96","author":{"name":"Venkatesh K","email":"70057838534969e1bdb576aea65bfd755d9ec4bb@seagate.com"},"message":"Alex is the tool used to scan inclusive words\n\nSigned-off-by: Venkatesh K ","distinct":true,"url":"https://api.github.com/repos/venkuppu-chn/cortx-hare/commits/4838966677335e718d32cae1a70f78c8110bac96"},{"sha":"416863176f1fe09a6b23156316ad9878113fc98d","author":{"name":"Venkatesh K","email":"70057838534969e1bdb576aea65bfd755d9ec4bb@seagate.com"},"message":"Merge remote-tracking branch 'origin/main' into main","distinct":true,"url":"https://api.github.com/repos/venkuppu-chn/cortx-hare/commits/416863176f1fe09a6b23156316ad9878113fc98d"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514912","type":"CreateEvent","actor":{"id":25700305,"login":"lsylo","display_login":"lsylo","gravatar_id":"","url":"https://api.github.com/users/lsylo","avatar_url":"https://avatars.githubusercontent.com/u/25700305?"},"repo":{"id":388040383,"name":"lsylo/income-report","url":"https://api.github.com/repos/lsylo/income-report"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514915","type":"WatchEvent","actor":{"id":10071813,"login":"caixiangyue","display_login":"caixiangyue","gravatar_id":"","url":"https://api.github.com/users/caixiangyue","avatar_url":"https://avatars.githubusercontent.com/u/10071813?"},"repo":{"id":283410102,"name":"sogou/workflow","url":"https://api.github.com/repos/sogou/workflow"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":15903299,"login":"sogou","gravatar_id":"","url":"https://api.github.com/orgs/sogou","avatar_url":"https://avatars.githubusercontent.com/u/15903299?"}} +{"id":"17245514922","type":"CreateEvent","actor":{"id":67758551,"login":"lee857","display_login":"lee857","gravatar_id":"","url":"https://api.github.com/users/lee857","avatar_url":"https://avatars.githubusercontent.com/u/67758551?"},"repo":{"id":388040567,"name":"lee857/portfolio-project","url":"https://api.github.com/repos/lee857/portfolio-project"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514938","type":"PullRequestReviewEvent","actor":{"id":572898,"login":"DRiKE","display_login":"DRiKE","gravatar_id":"","url":"https://api.github.com/users/DRiKE","avatar_url":"https://avatars.githubusercontent.com/u/572898?"},"repo":{"id":142451959,"name":"NLnetLabs/routinator","url":"https://api.github.com/repos/NLnetLabs/routinator"},"payload":{"action":"created","review":{"id":711364891,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzY0ODkx","user":{"login":"DRiKE","id":572898,"node_id":"MDQ6VXNlcjU3Mjg5OA==","avatar_url":"https://avatars.githubusercontent.com/u/572898?v=4","gravatar_id":"","url":"https://api.github.com/users/DRiKE","html_url":"https://github.com/DRiKE","followers_url":"https://api.github.com/users/DRiKE/followers","following_url":"https://api.github.com/users/DRiKE/following{/other_user}","gists_url":"https://api.github.com/users/DRiKE/gists{/gist_id}","starred_url":"https://api.github.com/users/DRiKE/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DRiKE/subscriptions","organizations_url":"https://api.github.com/users/DRiKE/orgs","repos_url":"https://api.github.com/users/DRiKE/repos","events_url":"https://api.github.com/users/DRiKE/events{/privacy}","received_events_url":"https://api.github.com/users/DRiKE/received_events","type":"User","site_admin":false},"body":"","commit_id":"30dda575993ab37182615623eb0b274952043df6","submitted_at":"2021-07-21T08:00:01Z","state":"approved","html_url":"https://github.com/NLnetLabs/routinator/pull/611#pullrequestreview-711364891","pull_request_url":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/NLnetLabs/routinator/pull/611#pullrequestreview-711364891"},"pull_request":{"href":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611"}}},"pull_request":{"url":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611","id":693370658,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzMzcwNjU4","html_url":"https://github.com/NLnetLabs/routinator/pull/611","diff_url":"https://github.com/NLnetLabs/routinator/pull/611.diff","patch_url":"https://github.com/NLnetLabs/routinator/pull/611.patch","issue_url":"https://api.github.com/repos/NLnetLabs/routinator/issues/611","number":611,"state":"open","locked":false,"title":"Set the RRDP connect timeout from the correct command line option.","user":{"login":"partim","id":1318494,"node_id":"MDQ6VXNlcjEzMTg0OTQ=","avatar_url":"https://avatars.githubusercontent.com/u/1318494?v=4","gravatar_id":"","url":"https://api.github.com/users/partim","html_url":"https://github.com/partim","followers_url":"https://api.github.com/users/partim/followers","following_url":"https://api.github.com/users/partim/following{/other_user}","gists_url":"https://api.github.com/users/partim/gists{/gist_id}","starred_url":"https://api.github.com/users/partim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/partim/subscriptions","organizations_url":"https://api.github.com/users/partim/orgs","repos_url":"https://api.github.com/users/partim/repos","events_url":"https://api.github.com/users/partim/events{/privacy}","received_events_url":"https://api.github.com/users/partim/received_events","type":"User","site_admin":false},"body":"Currently, the option is set from the `rrdp-timeout` command line option rather than `rrdp-connect-timeout`.","created_at":"2021-07-20T11:03:08Z","updated_at":"2021-07-21T08:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f05faf3efac2f9389ce1ea0dcf28b5d03d9d8675","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611/commits","review_comments_url":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611/comments","review_comment_url":"https://api.github.com/repos/NLnetLabs/routinator/pulls/comments{/number}","comments_url":"https://api.github.com/repos/NLnetLabs/routinator/issues/611/comments","statuses_url":"https://api.github.com/repos/NLnetLabs/routinator/statuses/30dda575993ab37182615623eb0b274952043df6","head":{"label":"NLnetLabs:rrdp-connect-timeout-fix","ref":"rrdp-connect-timeout-fix","sha":"30dda575993ab37182615623eb0b274952043df6","user":{"login":"NLnetLabs","id":1895545,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE4OTU1NDU=","avatar_url":"https://avatars.githubusercontent.com/u/1895545?v=4","gravatar_id":"","url":"https://api.github.com/users/NLnetLabs","html_url":"https://github.com/NLnetLabs","followers_url":"https://api.github.com/users/NLnetLabs/followers","following_url":"https://api.github.com/users/NLnetLabs/following{/other_user}","gists_url":"https://api.github.com/users/NLnetLabs/gists{/gist_id}","starred_url":"https://api.github.com/users/NLnetLabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NLnetLabs/subscriptions","organizations_url":"https://api.github.com/users/NLnetLabs/orgs","repos_url":"https://api.github.com/users/NLnetLabs/repos","events_url":"https://api.github.com/users/NLnetLabs/events{/privacy}","received_events_url":"https://api.github.com/users/NLnetLabs/received_events","type":"Organization","site_admin":false},"repo":{"id":142451959,"node_id":"MDEwOlJlcG9zaXRvcnkxNDI0NTE5NTk=","name":"routinator","full_name":"NLnetLabs/routinator","private":false,"owner":{"login":"NLnetLabs","id":1895545,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE4OTU1NDU=","avatar_url":"https://avatars.githubusercontent.com/u/1895545?v=4","gravatar_id":"","url":"https://api.github.com/users/NLnetLabs","html_url":"https://github.com/NLnetLabs","followers_url":"https://api.github.com/users/NLnetLabs/followers","following_url":"https://api.github.com/users/NLnetLabs/following{/other_user}","gists_url":"https://api.github.com/users/NLnetLabs/gists{/gist_id}","starred_url":"https://api.github.com/users/NLnetLabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NLnetLabs/subscriptions","organizations_url":"https://api.github.com/users/NLnetLabs/orgs","repos_url":"https://api.github.com/users/NLnetLabs/repos","events_url":"https://api.github.com/users/NLnetLabs/events{/privacy}","received_events_url":"https://api.github.com/users/NLnetLabs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/NLnetLabs/routinator","description":"An RPKI Validator written in Rust","fork":false,"url":"https://api.github.com/repos/NLnetLabs/routinator","forks_url":"https://api.github.com/repos/NLnetLabs/routinator/forks","keys_url":"https://api.github.com/repos/NLnetLabs/routinator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NLnetLabs/routinator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NLnetLabs/routinator/teams","hooks_url":"https://api.github.com/repos/NLnetLabs/routinator/hooks","issue_events_url":"https://api.github.com/repos/NLnetLabs/routinator/issues/events{/number}","events_url":"https://api.github.com/repos/NLnetLabs/routinator/events","assignees_url":"https://api.github.com/repos/NLnetLabs/routinator/assignees{/user}","branches_url":"https://api.github.com/repos/NLnetLabs/routinator/branches{/branch}","tags_url":"https://api.github.com/repos/NLnetLabs/routinator/tags","blobs_url":"https://api.github.com/repos/NLnetLabs/routinator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NLnetLabs/routinator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NLnetLabs/routinator/git/refs{/sha}","trees_url":"https://api.github.com/repos/NLnetLabs/routinator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NLnetLabs/routinator/statuses/{sha}","languages_url":"https://api.github.com/repos/NLnetLabs/routinator/languages","stargazers_url":"https://api.github.com/repos/NLnetLabs/routinator/stargazers","contributors_url":"https://api.github.com/repos/NLnetLabs/routinator/contributors","subscribers_url":"https://api.github.com/repos/NLnetLabs/routinator/subscribers","subscription_url":"https://api.github.com/repos/NLnetLabs/routinator/subscription","commits_url":"https://api.github.com/repos/NLnetLabs/routinator/commits{/sha}","git_commits_url":"https://api.github.com/repos/NLnetLabs/routinator/git/commits{/sha}","comments_url":"https://api.github.com/repos/NLnetLabs/routinator/comments{/number}","issue_comment_url":"https://api.github.com/repos/NLnetLabs/routinator/issues/comments{/number}","contents_url":"https://api.github.com/repos/NLnetLabs/routinator/contents/{+path}","compare_url":"https://api.github.com/repos/NLnetLabs/routinator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NLnetLabs/routinator/merges","archive_url":"https://api.github.com/repos/NLnetLabs/routinator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NLnetLabs/routinator/downloads","issues_url":"https://api.github.com/repos/NLnetLabs/routinator/issues{/number}","pulls_url":"https://api.github.com/repos/NLnetLabs/routinator/pulls{/number}","milestones_url":"https://api.github.com/repos/NLnetLabs/routinator/milestones{/number}","notifications_url":"https://api.github.com/repos/NLnetLabs/routinator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NLnetLabs/routinator/labels{/name}","releases_url":"https://api.github.com/repos/NLnetLabs/routinator/releases{/id}","deployments_url":"https://api.github.com/repos/NLnetLabs/routinator/deployments","created_at":"2018-07-26T14:24:08Z","updated_at":"2021-07-20T10:05:33Z","pushed_at":"2021-07-20T12:42:28Z","git_url":"git://github.com/NLnetLabs/routinator.git","ssh_url":"git@github.com:NLnetLabs/routinator.git","clone_url":"https://github.com/NLnetLabs/routinator.git","svn_url":"https://github.com/NLnetLabs/routinator","homepage":"https://nlnetlabs.nl/rpki","size":6014,"stargazers_count":249,"watchers_count":249,"language":"Rust","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":43,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":52,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":43,"open_issues":52,"watchers":249,"default_branch":"main"}},"base":{"label":"NLnetLabs:main","ref":"main","sha":"d838a43f2fd005f70a550bbd0cd91cfefa42e431","user":{"login":"NLnetLabs","id":1895545,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE4OTU1NDU=","avatar_url":"https://avatars.githubusercontent.com/u/1895545?v=4","gravatar_id":"","url":"https://api.github.com/users/NLnetLabs","html_url":"https://github.com/NLnetLabs","followers_url":"https://api.github.com/users/NLnetLabs/followers","following_url":"https://api.github.com/users/NLnetLabs/following{/other_user}","gists_url":"https://api.github.com/users/NLnetLabs/gists{/gist_id}","starred_url":"https://api.github.com/users/NLnetLabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NLnetLabs/subscriptions","organizations_url":"https://api.github.com/users/NLnetLabs/orgs","repos_url":"https://api.github.com/users/NLnetLabs/repos","events_url":"https://api.github.com/users/NLnetLabs/events{/privacy}","received_events_url":"https://api.github.com/users/NLnetLabs/received_events","type":"Organization","site_admin":false},"repo":{"id":142451959,"node_id":"MDEwOlJlcG9zaXRvcnkxNDI0NTE5NTk=","name":"routinator","full_name":"NLnetLabs/routinator","private":false,"owner":{"login":"NLnetLabs","id":1895545,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE4OTU1NDU=","avatar_url":"https://avatars.githubusercontent.com/u/1895545?v=4","gravatar_id":"","url":"https://api.github.com/users/NLnetLabs","html_url":"https://github.com/NLnetLabs","followers_url":"https://api.github.com/users/NLnetLabs/followers","following_url":"https://api.github.com/users/NLnetLabs/following{/other_user}","gists_url":"https://api.github.com/users/NLnetLabs/gists{/gist_id}","starred_url":"https://api.github.com/users/NLnetLabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NLnetLabs/subscriptions","organizations_url":"https://api.github.com/users/NLnetLabs/orgs","repos_url":"https://api.github.com/users/NLnetLabs/repos","events_url":"https://api.github.com/users/NLnetLabs/events{/privacy}","received_events_url":"https://api.github.com/users/NLnetLabs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/NLnetLabs/routinator","description":"An RPKI Validator written in Rust","fork":false,"url":"https://api.github.com/repos/NLnetLabs/routinator","forks_url":"https://api.github.com/repos/NLnetLabs/routinator/forks","keys_url":"https://api.github.com/repos/NLnetLabs/routinator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NLnetLabs/routinator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NLnetLabs/routinator/teams","hooks_url":"https://api.github.com/repos/NLnetLabs/routinator/hooks","issue_events_url":"https://api.github.com/repos/NLnetLabs/routinator/issues/events{/number}","events_url":"https://api.github.com/repos/NLnetLabs/routinator/events","assignees_url":"https://api.github.com/repos/NLnetLabs/routinator/assignees{/user}","branches_url":"https://api.github.com/repos/NLnetLabs/routinator/branches{/branch}","tags_url":"https://api.github.com/repos/NLnetLabs/routinator/tags","blobs_url":"https://api.github.com/repos/NLnetLabs/routinator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NLnetLabs/routinator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NLnetLabs/routinator/git/refs{/sha}","trees_url":"https://api.github.com/repos/NLnetLabs/routinator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NLnetLabs/routinator/statuses/{sha}","languages_url":"https://api.github.com/repos/NLnetLabs/routinator/languages","stargazers_url":"https://api.github.com/repos/NLnetLabs/routinator/stargazers","contributors_url":"https://api.github.com/repos/NLnetLabs/routinator/contributors","subscribers_url":"https://api.github.com/repos/NLnetLabs/routinator/subscribers","subscription_url":"https://api.github.com/repos/NLnetLabs/routinator/subscription","commits_url":"https://api.github.com/repos/NLnetLabs/routinator/commits{/sha}","git_commits_url":"https://api.github.com/repos/NLnetLabs/routinator/git/commits{/sha}","comments_url":"https://api.github.com/repos/NLnetLabs/routinator/comments{/number}","issue_comment_url":"https://api.github.com/repos/NLnetLabs/routinator/issues/comments{/number}","contents_url":"https://api.github.com/repos/NLnetLabs/routinator/contents/{+path}","compare_url":"https://api.github.com/repos/NLnetLabs/routinator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NLnetLabs/routinator/merges","archive_url":"https://api.github.com/repos/NLnetLabs/routinator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NLnetLabs/routinator/downloads","issues_url":"https://api.github.com/repos/NLnetLabs/routinator/issues{/number}","pulls_url":"https://api.github.com/repos/NLnetLabs/routinator/pulls{/number}","milestones_url":"https://api.github.com/repos/NLnetLabs/routinator/milestones{/number}","notifications_url":"https://api.github.com/repos/NLnetLabs/routinator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NLnetLabs/routinator/labels{/name}","releases_url":"https://api.github.com/repos/NLnetLabs/routinator/releases{/id}","deployments_url":"https://api.github.com/repos/NLnetLabs/routinator/deployments","created_at":"2018-07-26T14:24:08Z","updated_at":"2021-07-20T10:05:33Z","pushed_at":"2021-07-20T12:42:28Z","git_url":"git://github.com/NLnetLabs/routinator.git","ssh_url":"git@github.com:NLnetLabs/routinator.git","clone_url":"https://github.com/NLnetLabs/routinator.git","svn_url":"https://github.com/NLnetLabs/routinator","homepage":"https://nlnetlabs.nl/rpki","size":6014,"stargazers_count":249,"watchers_count":249,"language":"Rust","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":43,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":52,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":43,"open_issues":52,"watchers":249,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611"},"html":{"href":"https://github.com/NLnetLabs/routinator/pull/611"},"issue":{"href":"https://api.github.com/repos/NLnetLabs/routinator/issues/611"},"comments":{"href":"https://api.github.com/repos/NLnetLabs/routinator/issues/611/comments"},"review_comments":{"href":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611/comments"},"review_comment":{"href":"https://api.github.com/repos/NLnetLabs/routinator/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/NLnetLabs/routinator/pulls/611/commits"},"statuses":{"href":"https://api.github.com/repos/NLnetLabs/routinator/statuses/30dda575993ab37182615623eb0b274952043df6"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":1895545,"login":"NLnetLabs","gravatar_id":"","url":"https://api.github.com/orgs/NLnetLabs","avatar_url":"https://avatars.githubusercontent.com/u/1895545?"}} +{"id":"17245514939","type":"MemberEvent","actor":{"id":87553686,"login":"danhnguyen2","display_login":"danhnguyen2","gravatar_id":"","url":"https://api.github.com/users/danhnguyen2","avatar_url":"https://avatars.githubusercontent.com/u/87553686?"},"repo":{"id":386809027,"name":"danhnguyen2/TeamA","url":"https://api.github.com/repos/danhnguyen2/TeamA"},"payload":{"member":{"login":"Fabianlatina16","id":87752576,"node_id":"MDQ6VXNlcjg3NzUyNTc2","avatar_url":"https://avatars.githubusercontent.com/u/87752576?v=4","gravatar_id":"","url":"https://api.github.com/users/Fabianlatina16","html_url":"https://github.com/Fabianlatina16","followers_url":"https://api.github.com/users/Fabianlatina16/followers","following_url":"https://api.github.com/users/Fabianlatina16/following{/other_user}","gists_url":"https://api.github.com/users/Fabianlatina16/gists{/gist_id}","starred_url":"https://api.github.com/users/Fabianlatina16/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Fabianlatina16/subscriptions","organizations_url":"https://api.github.com/users/Fabianlatina16/orgs","repos_url":"https://api.github.com/users/Fabianlatina16/repos","events_url":"https://api.github.com/users/Fabianlatina16/events{/privacy}","received_events_url":"https://api.github.com/users/Fabianlatina16/received_events","type":"User","site_admin":false},"action":"added"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514943","type":"WatchEvent","actor":{"id":15075638,"login":"Fasjeit","display_login":"Fasjeit","gravatar_id":"","url":"https://api.github.com/users/Fasjeit","avatar_url":"https://avatars.githubusercontent.com/u/15075638?"},"repo":{"id":42267778,"name":"NuGetPackageExplorer/NuGetPackageExplorer","url":"https://api.github.com/repos/NuGetPackageExplorer/NuGetPackageExplorer"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":15967039,"login":"NuGetPackageExplorer","gravatar_id":"","url":"https://api.github.com/orgs/NuGetPackageExplorer","avatar_url":"https://avatars.githubusercontent.com/u/15967039?"}} +{"id":"17245514950","type":"PushEvent","actor":{"id":52290251,"login":"botcopado","display_login":"botcopado","gravatar_id":"","url":"https://api.github.com/users/botcopado","avatar_url":"https://avatars.githubusercontent.com/u/52290251?"},"repo":{"id":388040370,"name":"botcopado/moeldv_QSOJVUTM","url":"https://api.github.com/repos/botcopado/moeldv_QSOJVUTM"},"payload":{"push_id":7562055116,"size":4,"distinct_size":0,"ref":"refs/heads/promotion/P25615-MOEDeployment","head":"692b977d32460e74ac35c95a30acbce734454787","before":"93d1fb4fb6e7902ae9084f0e4a4cdead533aeb7e","commits":[{"sha":"f869f9d82d1a046fd8264a1ca04023027de9e5bc","author":{"name":"Test QA User","email":"0aeb9bf205e33c465a83ad48c5898f09dd71cceb@copado.com"},"message":"Bot initialization","distinct":false,"url":"https://api.github.com/repos/botcopado/moeldv_QSOJVUTM/commits/f869f9d82d1a046fd8264a1ca04023027de9e5bc"},{"sha":"18b8f37bf65aaca74b5f4598bc5ae5f19f50b322","author":{"name":"Test QA User","email":"0aeb9bf205e33c465a83ad48c5898f09dd71cceb@copado.com"},"message":"Bot initialization","distinct":false,"url":"https://api.github.com/repos/botcopado/moeldv_QSOJVUTM/commits/18b8f37bf65aaca74b5f4598bc5ae5f19f50b322"},{"sha":"c93c876fd4ec8a45c3dc4b0440775058e8d12846","author":{"name":"Test QA User","email":"0aeb9bf205e33c465a83ad48c5898f09dd71cceb@copado.com"},"message":"Bot initialization","distinct":false,"url":"https://api.github.com/repos/botcopado/moeldv_QSOJVUTM/commits/c93c876fd4ec8a45c3dc4b0440775058e8d12846"},{"sha":"692b977d32460e74ac35c95a30acbce734454787","author":{"name":"Test QA User","email":"0aeb9bf205e33c465a83ad48c5898f09dd71cceb@copado.com"},"message":"Bot initialization","distinct":false,"url":"https://api.github.com/repos/botcopado/moeldv_QSOJVUTM/commits/692b977d32460e74ac35c95a30acbce734454787"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514962","type":"PushEvent","actor":{"id":81859158,"login":"cherin0115","display_login":"cherin0115","gravatar_id":"","url":"https://api.github.com/users/cherin0115","avatar_url":"https://avatars.githubusercontent.com/u/81859158?"},"repo":{"id":387420703,"name":"cherin0115/dothome21","url":"https://api.github.com/repos/cherin0115/dothome21"},"payload":{"push_id":7562055130,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f71745b391ebdd0384891aaabed74a5ceb8a8ab2","before":"f436fe6c85d990d56dcb0d808cd202a6c89ccb46","commits":[{"sha":"f71745b391ebdd0384891aaabed74a5ceb8a8ab2","author":{"name":"cherin0115","email":"bb8e7d489e5f40465bb4a825ee8d35c5a8846137@gmail.com"},"message":"layout06 added","distinct":true,"url":"https://api.github.com/repos/cherin0115/dothome21/commits/f71745b391ebdd0384891aaabed74a5ceb8a8ab2"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514968","type":"IssueCommentEvent","actor":{"id":8723280,"login":"OCA-git-bot","display_login":"OCA-git-bot","gravatar_id":"","url":"https://api.github.com/users/OCA-git-bot","avatar_url":"https://avatars.githubusercontent.com/u/8723280?"},"repo":{"id":265171081,"name":"OCA/dms","url":"https://api.github.com/repos/OCA/dms"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/OCA/dms/issues/117","repository_url":"https://api.github.com/repos/OCA/dms","labels_url":"https://api.github.com/repos/OCA/dms/issues/117/labels{/name}","comments_url":"https://api.github.com/repos/OCA/dms/issues/117/comments","events_url":"https://api.github.com/repos/OCA/dms/issues/117/events","html_url":"https://github.com/OCA/dms/pull/117","id":943237344,"node_id":"MDExOlB1bGxSZXF1ZXN0Njg4ODg2MTkz","number":117,"title":"[13.0][IMP+FIX] dms: Changes in file tree view + Fix file preview.","user":{"login":"victoralmau","id":4117568,"node_id":"MDQ6VXNlcjQxMTc1Njg=","avatar_url":"https://avatars.githubusercontent.com/u/4117568?v=4","gravatar_id":"","url":"https://api.github.com/users/victoralmau","html_url":"https://github.com/victoralmau","followers_url":"https://api.github.com/users/victoralmau/followers","following_url":"https://api.github.com/users/victoralmau/following{/other_user}","gists_url":"https://api.github.com/users/victoralmau/gists{/gist_id}","starred_url":"https://api.github.com/users/victoralmau/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/victoralmau/subscriptions","organizations_url":"https://api.github.com/users/victoralmau/orgs","repos_url":"https://api.github.com/users/victoralmau/repos","events_url":"https://api.github.com/users/victoralmau/events{/privacy}","received_events_url":"https://api.github.com/users/victoralmau/received_events","type":"User","site_admin":false},"labels":[{"id":2192238558,"node_id":"MDU6TGFiZWwyMTkyMjM4NTU4","url":"https://api.github.com/repos/OCA/dms/labels/approved","name":"approved","color":"045509","default":false,"description":null},{"id":2185119623,"node_id":"MDU6TGFiZWwyMTg1MTE5NjIz","url":"https://api.github.com/repos/OCA/dms/labels/bot%20is%20merging%20%E2%8F%B3","name":"bot is merging ⏳","color":"ededed","default":false,"description":null},{"id":2433081496,"node_id":"MDU6TGFiZWwyNDMzMDgxNDk2","url":"https://api.github.com/repos/OCA/dms/labels/ready%20to%20merge","name":"ready to merge","color":"bfdadc","default":false,"description":null}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/OCA/dms/milestones/1","html_url":"https://github.com/OCA/dms/milestone/1","labels_url":"https://api.github.com/repos/OCA/dms/milestones/1/labels","id":5439894,"node_id":"MDk6TWlsZXN0b25lNTQzOTg5NA==","number":1,"title":"13.0","description":null,"creator":{"login":"pedrobaeza","id":7165771,"node_id":"MDQ6VXNlcjcxNjU3NzE=","avatar_url":"https://avatars.githubusercontent.com/u/7165771?v=4","gravatar_id":"","url":"https://api.github.com/users/pedrobaeza","html_url":"https://github.com/pedrobaeza","followers_url":"https://api.github.com/users/pedrobaeza/followers","following_url":"https://api.github.com/users/pedrobaeza/following{/other_user}","gists_url":"https://api.github.com/users/pedrobaeza/gists{/gist_id}","starred_url":"https://api.github.com/users/pedrobaeza/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pedrobaeza/subscriptions","organizations_url":"https://api.github.com/users/pedrobaeza/orgs","repos_url":"https://api.github.com/users/pedrobaeza/repos","events_url":"https://api.github.com/users/pedrobaeza/events{/privacy}","received_events_url":"https://api.github.com/users/pedrobaeza/received_events","type":"User","site_admin":false},"open_issues":5,"closed_issues":30,"state":"open","created_at":"2020-05-19T15:04:01Z","updated_at":"2021-07-20T09:14:11Z","due_on":null,"closed_at":null},"comments":4,"created_at":"2021-07-13T11:42:12Z","updated_at":"2021-07-21T08:00:02Z","closed_at":null,"author_association":"MEMBER","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/OCA/dms/pulls/117","html_url":"https://github.com/OCA/dms/pull/117","diff_url":"https://github.com/OCA/dms/pull/117.diff","patch_url":"https://github.com/OCA/dms/pull/117.patch"},"body":"[IMP] Changes in file tree view (It's necessary to change in 14.0)\r\n[FIX] File preview.\r\n[IMP ]Show in directory kanban view only root directories (Change **All** text to **Root**) + Change to searchpanel _directory_id_ (in files) or _parent_id_ (in directories) to filter equal and not _child_of_ (It's related to https://github.com/OCA/dms/issues/40 )\r\n\r\nLocked by:\r\n- [x] https://github.com/OCA/social/pull/744\r\n\r\nPlease @pedrobaeza and @Yajo can you review it?\r\n\r\n@Tecnativa TT30992","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/OCA/dms/issues/comments/883977116","html_url":"https://github.com/OCA/dms/pull/117#issuecomment-883977116","issue_url":"https://api.github.com/repos/OCA/dms/issues/117","id":883977116,"node_id":"IC_kwDOD84wic40sGuc","user":{"login":"OCA-git-bot","id":8723280,"node_id":"MDQ6VXNlcjg3MjMyODA=","avatar_url":"https://avatars.githubusercontent.com/u/8723280?v=4","gravatar_id":"","url":"https://api.github.com/users/OCA-git-bot","html_url":"https://github.com/OCA-git-bot","followers_url":"https://api.github.com/users/OCA-git-bot/followers","following_url":"https://api.github.com/users/OCA-git-bot/following{/other_user}","gists_url":"https://api.github.com/users/OCA-git-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/OCA-git-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OCA-git-bot/subscriptions","organizations_url":"https://api.github.com/users/OCA-git-bot/orgs","repos_url":"https://api.github.com/users/OCA-git-bot/repos","events_url":"https://api.github.com/users/OCA-git-bot/events{/privacy}","received_events_url":"https://api.github.com/users/OCA-git-bot/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:02Z","updated_at":"2021-07-21T08:00:02Z","author_association":"CONTRIBUTOR","body":"This PR has the `approved` label and has been created more than 5 days ago. It should therefore be ready to merge by a maintainer (or a PSC member if the concerned addon has no declared maintainer). 🤖","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":7600578,"login":"OCA","gravatar_id":"","url":"https://api.github.com/orgs/OCA","avatar_url":"https://avatars.githubusercontent.com/u/7600578?"}} +{"id":"17245514971","type":"PushEvent","actor":{"id":25141652,"login":"15826954460","display_login":"15826954460","gravatar_id":"","url":"https://api.github.com/users/15826954460","avatar_url":"https://avatars.githubusercontent.com/u/25141652?"},"repo":{"id":363316501,"name":"15826954460/code_stage","url":"https://api.github.com/repos/15826954460/code_stage"},"payload":{"push_id":7562055132,"size":1,"distinct_size":1,"ref":"refs/heads/feat_equipment","head":"409156fc4c5fad9b612dc0fef82c42ad5d535066","before":"2957f3ef4415b35db9f72ac1d838a86c8d7c9fc4","commits":[{"sha":"409156fc4c5fad9b612dc0fef82c42ad5d535066","author":{"name":"xuLady","email":"b0cc720fb65056526a29317a32782e802f4558aa@qq.com"},"message":"设备展示table增加删除功能","distinct":true,"url":"https://api.github.com/repos/15826954460/code_stage/commits/409156fc4c5fad9b612dc0fef82c42ad5d535066"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514972","type":"PushEvent","actor":{"id":85989057,"login":"internetzbot","display_login":"internetzbot","gravatar_id":"","url":"https://api.github.com/users/internetzbot","avatar_url":"https://avatars.githubusercontent.com/u/85989057?"},"repo":{"id":377260146,"name":"internetztube/jaukerl-ooe-archive","url":"https://api.github.com/repos/internetztube/jaukerl-ooe-archive"},"payload":{"push_id":7562055134,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"bf6c4a186e636099e5402a87fdea0dc43a3e3871","before":"95d2922fb3f34dcbb385a4a7870f57431d9b8291","commits":[{"sha":"bf6c4a186e636099e5402a87fdea0dc43a3e3871","author":{"name":"internetzbot","email":"c5d6e309f28eeb1bf280506bcf24a90da3518803@users.noreply.github.com"},"message":"Add 2021-07-21T08:00:00+00:00.json.","distinct":true,"url":"https://api.github.com/repos/internetztube/jaukerl-ooe-archive/commits/bf6c4a186e636099e5402a87fdea0dc43a3e3871"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514986","type":"MemberEvent","actor":{"id":29756526,"login":"sinhaugoh","display_login":"sinhaugoh","gravatar_id":"","url":"https://api.github.com/users/sinhaugoh","avatar_url":"https://avatars.githubusercontent.com/u/29756526?"},"repo":{"id":387382376,"name":"sinhaugoh/DEALL","url":"https://api.github.com/repos/sinhaugoh/DEALL"},"payload":{"member":{"login":"lawrencehsj","id":58553029,"node_id":"MDQ6VXNlcjU4NTUzMDI5","avatar_url":"https://avatars.githubusercontent.com/u/58553029?v=4","gravatar_id":"","url":"https://api.github.com/users/lawrencehsj","html_url":"https://github.com/lawrencehsj","followers_url":"https://api.github.com/users/lawrencehsj/followers","following_url":"https://api.github.com/users/lawrencehsj/following{/other_user}","gists_url":"https://api.github.com/users/lawrencehsj/gists{/gist_id}","starred_url":"https://api.github.com/users/lawrencehsj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lawrencehsj/subscriptions","organizations_url":"https://api.github.com/users/lawrencehsj/orgs","repos_url":"https://api.github.com/users/lawrencehsj/repos","events_url":"https://api.github.com/users/lawrencehsj/events{/privacy}","received_events_url":"https://api.github.com/users/lawrencehsj/received_events","type":"User","site_admin":false},"action":"added"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514995","type":"PushEvent","actor":{"id":39814207,"login":"pull[bot]","display_login":"pull","gravatar_id":"","url":"https://api.github.com/users/pull[bot]","avatar_url":"https://avatars.githubusercontent.com/u/39814207?"},"repo":{"id":375126034,"name":"rahil-sh/tutorials","url":"https://api.github.com/repos/rahil-sh/tutorials"},"payload":{"push_id":7562055143,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"1a07f9d2bfa2ba750598e8da153f101f645e35fe","before":"182dfd4e9f5f3d5a938eac648bea09a082ddc711","commits":[{"sha":"1a07f9d2bfa2ba750598e8da153f101f645e35fe","author":{"name":"Kai Yuan","email":"52df5956e44d56b84d70cabdc4e598a97da05809@Gmail.com"},"message":"add Phaser implementations (#11051)","distinct":true,"url":"https://api.github.com/repos/rahil-sh/tutorials/commits/1a07f9d2bfa2ba750598e8da153f101f645e35fe"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245514997","type":"PushEvent","actor":{"id":25363384,"login":"falconray0704","display_login":"falconray0704","gravatar_id":"","url":"https://api.github.com/users/falconray0704","avatar_url":"https://avatars.githubusercontent.com/u/25363384?"},"repo":{"id":197487628,"name":"falconray0704/dkhub","url":"https://api.github.com/repos/falconray0704/dkhub"},"payload":{"push_id":7562055146,"size":1,"distinct_size":1,"ref":"refs/heads/buildroot","head":"1bba46eedf69d8c42511044128ccb5a6f1e54799","before":"cce1224271a092369c4de7f5315cd54e93e936a8","commits":[{"sha":"1bba46eedf69d8c42511044128ccb5a6f1e54799","author":{"name":"FalconRay","email":"890c79e11e9583844fdcd8f5a2a105fcc05aab9f@yahoo.com"},"message":"Added missing package liblz4-tool into buildroot base image.","distinct":true,"url":"https://api.github.com/repos/falconray0704/dkhub/commits/1bba46eedf69d8c42511044128ccb5a6f1e54799"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515010","type":"ForkEvent","actor":{"id":1853707,"login":"samever7","display_login":"samever7","gravatar_id":"","url":"https://api.github.com/users/samever7","avatar_url":"https://avatars.githubusercontent.com/u/1853707?"},"repo":{"id":122696125,"name":"wb14123/seq2seq-couplet","url":"https://api.github.com/repos/wb14123/seq2seq-couplet"},"payload":{"forkee":{"id":388040569,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1Njk=","name":"seq2seq-couplet","full_name":"samever7/seq2seq-couplet","private":false,"owner":{"login":"samever7","id":1853707,"node_id":"MDQ6VXNlcjE4NTM3MDc=","avatar_url":"https://avatars.githubusercontent.com/u/1853707?v=4","gravatar_id":"","url":"https://api.github.com/users/samever7","html_url":"https://github.com/samever7","followers_url":"https://api.github.com/users/samever7/followers","following_url":"https://api.github.com/users/samever7/following{/other_user}","gists_url":"https://api.github.com/users/samever7/gists{/gist_id}","starred_url":"https://api.github.com/users/samever7/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/samever7/subscriptions","organizations_url":"https://api.github.com/users/samever7/orgs","repos_url":"https://api.github.com/users/samever7/repos","events_url":"https://api.github.com/users/samever7/events{/privacy}","received_events_url":"https://api.github.com/users/samever7/received_events","type":"User","site_admin":false},"html_url":"https://github.com/samever7/seq2seq-couplet","description":"Play couplet with seq2seq model. 用深度学习对对联。","fork":true,"url":"https://api.github.com/repos/samever7/seq2seq-couplet","forks_url":"https://api.github.com/repos/samever7/seq2seq-couplet/forks","keys_url":"https://api.github.com/repos/samever7/seq2seq-couplet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/samever7/seq2seq-couplet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/samever7/seq2seq-couplet/teams","hooks_url":"https://api.github.com/repos/samever7/seq2seq-couplet/hooks","issue_events_url":"https://api.github.com/repos/samever7/seq2seq-couplet/issues/events{/number}","events_url":"https://api.github.com/repos/samever7/seq2seq-couplet/events","assignees_url":"https://api.github.com/repos/samever7/seq2seq-couplet/assignees{/user}","branches_url":"https://api.github.com/repos/samever7/seq2seq-couplet/branches{/branch}","tags_url":"https://api.github.com/repos/samever7/seq2seq-couplet/tags","blobs_url":"https://api.github.com/repos/samever7/seq2seq-couplet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/samever7/seq2seq-couplet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/samever7/seq2seq-couplet/git/refs{/sha}","trees_url":"https://api.github.com/repos/samever7/seq2seq-couplet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/samever7/seq2seq-couplet/statuses/{sha}","languages_url":"https://api.github.com/repos/samever7/seq2seq-couplet/languages","stargazers_url":"https://api.github.com/repos/samever7/seq2seq-couplet/stargazers","contributors_url":"https://api.github.com/repos/samever7/seq2seq-couplet/contributors","subscribers_url":"https://api.github.com/repos/samever7/seq2seq-couplet/subscribers","subscription_url":"https://api.github.com/repos/samever7/seq2seq-couplet/subscription","commits_url":"https://api.github.com/repos/samever7/seq2seq-couplet/commits{/sha}","git_commits_url":"https://api.github.com/repos/samever7/seq2seq-couplet/git/commits{/sha}","comments_url":"https://api.github.com/repos/samever7/seq2seq-couplet/comments{/number}","issue_comment_url":"https://api.github.com/repos/samever7/seq2seq-couplet/issues/comments{/number}","contents_url":"https://api.github.com/repos/samever7/seq2seq-couplet/contents/{+path}","compare_url":"https://api.github.com/repos/samever7/seq2seq-couplet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/samever7/seq2seq-couplet/merges","archive_url":"https://api.github.com/repos/samever7/seq2seq-couplet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/samever7/seq2seq-couplet/downloads","issues_url":"https://api.github.com/repos/samever7/seq2seq-couplet/issues{/number}","pulls_url":"https://api.github.com/repos/samever7/seq2seq-couplet/pulls{/number}","milestones_url":"https://api.github.com/repos/samever7/seq2seq-couplet/milestones{/number}","notifications_url":"https://api.github.com/repos/samever7/seq2seq-couplet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/samever7/seq2seq-couplet/labels{/name}","releases_url":"https://api.github.com/repos/samever7/seq2seq-couplet/releases{/id}","deployments_url":"https://api.github.com/repos/samever7/seq2seq-couplet/deployments","created_at":"2021-07-21T08:00:01Z","updated_at":"2021-07-21T07:58:42Z","pushed_at":"2021-07-11T19:59:12Z","git_url":"git://github.com/samever7/seq2seq-couplet.git","ssh_url":"git@github.com:samever7/seq2seq-couplet.git","clone_url":"https://github.com/samever7/seq2seq-couplet.git","svn_url":"https://github.com/samever7/seq2seq-couplet","homepage":"https://ai.binwang.me/couplet","size":43,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515011","type":"CreateEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388040568,"name":"thatjohn01/753021085","url":"https://api.github.com/repos/thatjohn01/753021085"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":"西藏的孩子_PDF下载_鹰萨·罗布次仁著","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515013","type":"PushEvent","actor":{"id":33610328,"login":"hueleev","display_login":"hueleev","gravatar_id":"","url":"https://api.github.com/users/hueleev","avatar_url":"https://avatars.githubusercontent.com/u/33610328?"},"repo":{"id":379176062,"name":"hueleev/hueleev.github.io","url":"https://api.github.com/repos/hueleev/hueleev.github.io"},"payload":{"push_id":7562055138,"size":2,"distinct_size":2,"ref":"refs/heads/master","head":"14d6140537353706c4671531dd7d209859f55a35","before":"87e273b65b1b9e6c0fa42ffc5bddad5772948d69","commits":[{"sha":"15e4c6f62975f52cecaedb49845f610d775da99a","author":{"name":"hueleev","email":"1be3825e8327d2d8cc156260749f9bea0380490a@gmail.com"},"message":"test","distinct":true,"url":"https://api.github.com/repos/hueleev/hueleev.github.io/commits/15e4c6f62975f52cecaedb49845f610d775da99a"},{"sha":"14d6140537353706c4671531dd7d209859f55a35","author":{"name":"hueleev","email":"1be3825e8327d2d8cc156260749f9bea0380490a@gmail.com"},"message":"merge","distinct":true,"url":"https://api.github.com/repos/hueleev/hueleev.github.io/commits/14d6140537353706c4671531dd7d209859f55a35"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515021","type":"CreateEvent","actor":{"id":1473072,"login":"vitalets","display_login":"vitalets","gravatar_id":"","url":"https://api.github.com/users/vitalets","avatar_url":"https://avatars.githubusercontent.com/u/1473072?"},"repo":{"id":363393951,"name":"vitalets/skill-dev-proxy","url":"https://api.github.com/repos/vitalets/skill-dev-proxy"},"payload":{"ref":"v2.0.0","ref_type":"tag","master_branch":"main","description":"Навык для Алисы, позволяющий отлаживать другие навыки прямо на устройстве","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515030","type":"WatchEvent","actor":{"id":26359783,"login":"6451G","display_login":"6451G","gravatar_id":"","url":"https://api.github.com/users/6451G","avatar_url":"https://avatars.githubusercontent.com/u/26359783?"},"repo":{"id":15590229,"name":"agavrilov76/ExpectIt","url":"https://api.github.com/repos/agavrilov76/ExpectIt"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515056","type":"WatchEvent","actor":{"id":32321,"login":"fire","display_login":"fire","gravatar_id":"","url":"https://api.github.com/users/fire","avatar_url":"https://avatars.githubusercontent.com/u/32321?"},"repo":{"id":347974223,"name":"rmbashirov/rgbd-kinect-pose","url":"https://api.github.com/repos/rmbashirov/rgbd-kinect-pose"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515060","type":"CreateEvent","actor":{"id":29139614,"login":"renovate[bot]","display_login":"renovate","gravatar_id":"","url":"https://api.github.com/users/renovate[bot]","avatar_url":"https://avatars.githubusercontent.com/u/29139614?"},"repo":{"id":227850434,"name":"prisma/e2e-tests","url":"https://api.github.com/repos/prisma/e2e-tests"},"payload":{"ref":"renovate/supertest-6.x","ref_type":"branch","master_branch":"dev","description":"🥼🧬🧪🔬🧫🦠 - Continuously tests Prisma Client with various operating systems, frameworks, platforms, databases and more.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":17219288,"login":"prisma","gravatar_id":"","url":"https://api.github.com/orgs/prisma","avatar_url":"https://avatars.githubusercontent.com/u/17219288?"}} +{"id":"17245515063","type":"WatchEvent","actor":{"id":58673464,"login":"demvictory","display_login":"demvictory","gravatar_id":"","url":"https://api.github.com/users/demvictory","avatar_url":"https://avatars.githubusercontent.com/u/58673464?"},"repo":{"id":137451403,"name":"alibaba/nacos","url":"https://api.github.com/repos/alibaba/nacos"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":1961952,"login":"alibaba","gravatar_id":"","url":"https://api.github.com/orgs/alibaba","avatar_url":"https://avatars.githubusercontent.com/u/1961952?"}} +{"id":"17245515069","type":"WatchEvent","actor":{"id":32164846,"login":"Nek0Lex","display_login":"Nek0Lex","gravatar_id":"","url":"https://api.github.com/users/Nek0Lex","avatar_url":"https://avatars.githubusercontent.com/u/32164846?"},"repo":{"id":40484398,"name":"discordjs/discord.js","url":"https://api.github.com/repos/discordjs/discord.js"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":26492485,"login":"discordjs","gravatar_id":"","url":"https://api.github.com/orgs/discordjs","avatar_url":"https://avatars.githubusercontent.com/u/26492485?"}} +{"id":"17245515095","type":"CreateEvent","actor":{"id":50545818,"login":"caasiremos","display_login":"caasiremos","gravatar_id":"","url":"https://api.github.com/users/caasiremos","avatar_url":"https://avatars.githubusercontent.com/u/50545818?"},"repo":{"id":388039591,"name":"caasiremos/wamu","url":"https://api.github.com/repos/caasiremos/wamu"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":"Rotational group savings","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515083","type":"PushEvent","actor":{"id":82778280,"login":"Muir-UCSB","display_login":"Muir-UCSB","gravatar_id":"","url":"https://api.github.com/users/Muir-UCSB","avatar_url":"https://avatars.githubusercontent.com/u/82778280?"},"repo":{"id":379011192,"name":"Muir-UCSB/AE_ML_Comparative","url":"https://api.github.com/repos/Muir-UCSB/AE_ML_Comparative"},"payload":{"push_id":7562055160,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"149e74e1d507c250e5bd1002934e96f118737d7b","before":"3472ea6377e2444001fd484770601aa714a2d194","commits":[{"sha":"149e74e1d507c250e5bd1002934e96f118737d7b","author":{"name":"Muir-UCSB","email":"e23d91b1baab4fcf66dd530cff3e5b556fe0b8ad@users.noreply.github.com"},"message":"Add files via upload","distinct":true,"url":"https://api.github.com/repos/Muir-UCSB/AE_ML_Comparative/commits/149e74e1d507c250e5bd1002934e96f118737d7b"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515101","type":"PullRequestEvent","actor":{"id":59730796,"login":"avbuben","display_login":"avbuben","gravatar_id":"","url":"https://api.github.com/users/avbuben","avatar_url":"https://avatars.githubusercontent.com/u/59730796?"},"repo":{"id":386308872,"name":"a-devops/ansible-postgresql-role","url":"https://api.github.com/repos/a-devops/ansible-postgresql-role"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/1","id":691895631,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkxODk1NjMx","html_url":"https://github.com/a-devops/ansible-postgresql-role/pull/1","diff_url":"https://github.com/a-devops/ansible-postgresql-role/pull/1.diff","patch_url":"https://github.com/a-devops/ansible-postgresql-role/pull/1.patch","issue_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/1","number":1,"state":"closed","locked":false,"title":"devops-2367 update role","user":{"login":"avbuben","id":59730796,"node_id":"MDQ6VXNlcjU5NzMwNzk2","avatar_url":"https://avatars.githubusercontent.com/u/59730796?v=4","gravatar_id":"","url":"https://api.github.com/users/avbuben","html_url":"https://github.com/avbuben","followers_url":"https://api.github.com/users/avbuben/followers","following_url":"https://api.github.com/users/avbuben/following{/other_user}","gists_url":"https://api.github.com/users/avbuben/gists{/gist_id}","starred_url":"https://api.github.com/users/avbuben/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/avbuben/subscriptions","organizations_url":"https://api.github.com/users/avbuben/orgs","repos_url":"https://api.github.com/users/avbuben/repos","events_url":"https://api.github.com/users/avbuben/events{/privacy}","received_events_url":"https://api.github.com/users/avbuben/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-17T09:34:07Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":"2021-07-21T08:00:01Z","merge_commit_sha":"fbbdb8a7ff006efce63b7d5e59caf9b676ba6783","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/1/commits","review_comments_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/1/comments","review_comment_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/comments{/number}","comments_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/1/comments","statuses_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/statuses/7416dd1f1a5bd012d3531c2ddb976eea6effa3f3","head":{"label":"a-devops:internal/devops-2367-update-role","ref":"internal/devops-2367-update-role","sha":"7416dd1f1a5bd012d3531c2ddb976eea6effa3f3","user":{"login":"a-devops","id":59089336,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5MDg5MzM2","avatar_url":"https://avatars.githubusercontent.com/u/59089336?v=4","gravatar_id":"","url":"https://api.github.com/users/a-devops","html_url":"https://github.com/a-devops","followers_url":"https://api.github.com/users/a-devops/followers","following_url":"https://api.github.com/users/a-devops/following{/other_user}","gists_url":"https://api.github.com/users/a-devops/gists{/gist_id}","starred_url":"https://api.github.com/users/a-devops/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/a-devops/subscriptions","organizations_url":"https://api.github.com/users/a-devops/orgs","repos_url":"https://api.github.com/users/a-devops/repos","events_url":"https://api.github.com/users/a-devops/events{/privacy}","received_events_url":"https://api.github.com/users/a-devops/received_events","type":"Organization","site_admin":false},"repo":{"id":386308872,"node_id":"MDEwOlJlcG9zaXRvcnkzODYzMDg4NzI=","name":"ansible-postgresql-role","full_name":"a-devops/ansible-postgresql-role","private":false,"owner":{"login":"a-devops","id":59089336,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5MDg5MzM2","avatar_url":"https://avatars.githubusercontent.com/u/59089336?v=4","gravatar_id":"","url":"https://api.github.com/users/a-devops","html_url":"https://github.com/a-devops","followers_url":"https://api.github.com/users/a-devops/followers","following_url":"https://api.github.com/users/a-devops/following{/other_user}","gists_url":"https://api.github.com/users/a-devops/gists{/gist_id}","starred_url":"https://api.github.com/users/a-devops/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/a-devops/subscriptions","organizations_url":"https://api.github.com/users/a-devops/orgs","repos_url":"https://api.github.com/users/a-devops/repos","events_url":"https://api.github.com/users/a-devops/events{/privacy}","received_events_url":"https://api.github.com/users/a-devops/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/a-devops/ansible-postgresql-role","description":"Ansible role to install, configure and administrate Postgresql clusters","fork":true,"url":"https://api.github.com/repos/a-devops/ansible-postgresql-role","forks_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/forks","keys_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/keys{/key_id}","collaborators_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/teams","hooks_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/hooks","issue_events_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/events{/number}","events_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/events","assignees_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/assignees{/user}","branches_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/branches{/branch}","tags_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/tags","blobs_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/refs{/sha}","trees_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/trees{/sha}","statuses_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/statuses/{sha}","languages_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/languages","stargazers_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/stargazers","contributors_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/contributors","subscribers_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/subscribers","subscription_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/subscription","commits_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/commits{/sha}","git_commits_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/commits{/sha}","comments_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/comments{/number}","issue_comment_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/comments{/number}","contents_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/contents/{+path}","compare_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/compare/{base}...{head}","merges_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/merges","archive_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/downloads","issues_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues{/number}","pulls_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls{/number}","milestones_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/milestones{/number}","notifications_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/labels{/name}","releases_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/releases{/id}","deployments_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/deployments","created_at":"2021-07-15T13:56:39Z","updated_at":"2021-07-15T13:56:40Z","pushed_at":"2021-07-21T08:00:01Z","git_url":"git://github.com/a-devops/ansible-postgresql-role.git","ssh_url":"git@github.com:a-devops/ansible-postgresql-role.git","clone_url":"https://github.com/a-devops/ansible-postgresql-role.git","svn_url":"https://github.com/a-devops/ansible-postgresql-role","homepage":null,"size":136,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"a-devops:master","ref":"master","sha":"cb3e6294c1a0d7a606f8fe87ccdf9dc17d7a3b1b","user":{"login":"a-devops","id":59089336,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5MDg5MzM2","avatar_url":"https://avatars.githubusercontent.com/u/59089336?v=4","gravatar_id":"","url":"https://api.github.com/users/a-devops","html_url":"https://github.com/a-devops","followers_url":"https://api.github.com/users/a-devops/followers","following_url":"https://api.github.com/users/a-devops/following{/other_user}","gists_url":"https://api.github.com/users/a-devops/gists{/gist_id}","starred_url":"https://api.github.com/users/a-devops/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/a-devops/subscriptions","organizations_url":"https://api.github.com/users/a-devops/orgs","repos_url":"https://api.github.com/users/a-devops/repos","events_url":"https://api.github.com/users/a-devops/events{/privacy}","received_events_url":"https://api.github.com/users/a-devops/received_events","type":"Organization","site_admin":false},"repo":{"id":386308872,"node_id":"MDEwOlJlcG9zaXRvcnkzODYzMDg4NzI=","name":"ansible-postgresql-role","full_name":"a-devops/ansible-postgresql-role","private":false,"owner":{"login":"a-devops","id":59089336,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5MDg5MzM2","avatar_url":"https://avatars.githubusercontent.com/u/59089336?v=4","gravatar_id":"","url":"https://api.github.com/users/a-devops","html_url":"https://github.com/a-devops","followers_url":"https://api.github.com/users/a-devops/followers","following_url":"https://api.github.com/users/a-devops/following{/other_user}","gists_url":"https://api.github.com/users/a-devops/gists{/gist_id}","starred_url":"https://api.github.com/users/a-devops/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/a-devops/subscriptions","organizations_url":"https://api.github.com/users/a-devops/orgs","repos_url":"https://api.github.com/users/a-devops/repos","events_url":"https://api.github.com/users/a-devops/events{/privacy}","received_events_url":"https://api.github.com/users/a-devops/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/a-devops/ansible-postgresql-role","description":"Ansible role to install, configure and administrate Postgresql clusters","fork":true,"url":"https://api.github.com/repos/a-devops/ansible-postgresql-role","forks_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/forks","keys_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/keys{/key_id}","collaborators_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/teams","hooks_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/hooks","issue_events_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/events{/number}","events_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/events","assignees_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/assignees{/user}","branches_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/branches{/branch}","tags_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/tags","blobs_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/refs{/sha}","trees_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/trees{/sha}","statuses_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/statuses/{sha}","languages_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/languages","stargazers_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/stargazers","contributors_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/contributors","subscribers_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/subscribers","subscription_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/subscription","commits_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/commits{/sha}","git_commits_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/git/commits{/sha}","comments_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/comments{/number}","issue_comment_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/comments{/number}","contents_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/contents/{+path}","compare_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/compare/{base}...{head}","merges_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/merges","archive_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/downloads","issues_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues{/number}","pulls_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls{/number}","milestones_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/milestones{/number}","notifications_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/labels{/name}","releases_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/releases{/id}","deployments_url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/deployments","created_at":"2021-07-15T13:56:39Z","updated_at":"2021-07-15T13:56:40Z","pushed_at":"2021-07-21T08:00:01Z","git_url":"git://github.com/a-devops/ansible-postgresql-role.git","ssh_url":"git@github.com:a-devops/ansible-postgresql-role.git","clone_url":"https://github.com/a-devops/ansible-postgresql-role.git","svn_url":"https://github.com/a-devops/ansible-postgresql-role","homepage":null,"size":136,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/1"},"html":{"href":"https://github.com/a-devops/ansible-postgresql-role/pull/1"},"issue":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/1"},"comments":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/a-devops/ansible-postgresql-role/statuses/7416dd1f1a5bd012d3531c2ddb976eea6effa3f3"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"avbuben","id":59730796,"node_id":"MDQ6VXNlcjU5NzMwNzk2","avatar_url":"https://avatars.githubusercontent.com/u/59730796?v=4","gravatar_id":"","url":"https://api.github.com/users/avbuben","html_url":"https://github.com/avbuben","followers_url":"https://api.github.com/users/avbuben/followers","following_url":"https://api.github.com/users/avbuben/following{/other_user}","gists_url":"https://api.github.com/users/avbuben/gists{/gist_id}","starred_url":"https://api.github.com/users/avbuben/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/avbuben/subscriptions","organizations_url":"https://api.github.com/users/avbuben/orgs","repos_url":"https://api.github.com/users/avbuben/repos","events_url":"https://api.github.com/users/avbuben/events{/privacy}","received_events_url":"https://api.github.com/users/avbuben/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":109,"deletions":2075,"changed_files":12}},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":59089336,"login":"a-devops","gravatar_id":"","url":"https://api.github.com/orgs/a-devops","avatar_url":"https://avatars.githubusercontent.com/u/59089336?"}} +{"id":"17245515102","type":"PushEvent","actor":{"id":33043145,"login":"notnotjake","display_login":"notnotjake","gravatar_id":"","url":"https://api.github.com/users/notnotjake","avatar_url":"https://avatars.githubusercontent.com/u/33043145?"},"repo":{"id":367396143,"name":"notnotjake/dayticket","url":"https://api.github.com/repos/notnotjake/dayticket"},"payload":{"push_id":7562055189,"size":1,"distinct_size":1,"ref":"refs/heads/development","head":"b128cc152ed79d727f8ce05647802bd7fc90c551","before":"2bc02ebd36fcf2de40bf29f07e3c8bcd5874ce38","commits":[{"sha":"b128cc152ed79d727f8ce05647802bd7fc90c551","author":{"name":"notnotjake","email":"c8d99c2f7cd5f432c163abcd422672b9f77550bb@notnotjake.com"},"message":"Prints labor section\n\nUses regular wage for costs. Overtime is part of data structure and can be used in the future (on roadmap)","distinct":true,"url":"https://api.github.com/repos/notnotjake/dayticket/commits/b128cc152ed79d727f8ce05647802bd7fc90c551"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515111","type":"PushEvent","actor":{"id":81775975,"login":"foxly-it","display_login":"foxly-it","gravatar_id":"","url":"https://api.github.com/users/foxly-it","avatar_url":"https://avatars.githubusercontent.com/u/81775975?"},"repo":{"id":385229725,"name":"foxly-it/Portainer-Templates","url":"https://api.github.com/repos/foxly-it/Portainer-Templates"},"payload":{"push_id":7562055185,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"b7534b002f235cfe7d6c0b5e615fa1dacdc89108","before":"de50db88c5da331e4bd4ca8c592cc4b7c95f1f9d","commits":[{"sha":"b7534b002f235cfe7d6c0b5e615fa1dacdc89108","author":{"name":"Foxly IT Blog","email":"ccd3bccdb4d93606e2151586f02febac944e9057@users.noreply.github.com"},"message":"Update templates-2.0.json v1.0.1","distinct":true,"url":"https://api.github.com/repos/foxly-it/Portainer-Templates/commits/b7534b002f235cfe7d6c0b5e615fa1dacdc89108"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515117","type":"ForkEvent","actor":{"id":5567374,"login":"austonlee","display_login":"austonlee","gravatar_id":"","url":"https://api.github.com/users/austonlee","avatar_url":"https://avatars.githubusercontent.com/u/5567374?"},"repo":{"id":250884502,"name":"vercel/next-learn-starter","url":"https://api.github.com/repos/vercel/next-learn-starter"},"payload":{"forkee":{"id":388040571,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1NzE=","name":"next-learn-starter","full_name":"austonlee/next-learn-starter","private":false,"owner":{"login":"austonlee","id":5567374,"node_id":"MDQ6VXNlcjU1NjczNzQ=","avatar_url":"https://avatars.githubusercontent.com/u/5567374?v=4","gravatar_id":"","url":"https://api.github.com/users/austonlee","html_url":"https://github.com/austonlee","followers_url":"https://api.github.com/users/austonlee/followers","following_url":"https://api.github.com/users/austonlee/following{/other_user}","gists_url":"https://api.github.com/users/austonlee/gists{/gist_id}","starred_url":"https://api.github.com/users/austonlee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/austonlee/subscriptions","organizations_url":"https://api.github.com/users/austonlee/orgs","repos_url":"https://api.github.com/users/austonlee/repos","events_url":"https://api.github.com/users/austonlee/events{/privacy}","received_events_url":"https://api.github.com/users/austonlee/received_events","type":"User","site_admin":false},"html_url":"https://github.com/austonlee/next-learn-starter","description":"Learn Next.js Starter Code","fork":true,"url":"https://api.github.com/repos/austonlee/next-learn-starter","forks_url":"https://api.github.com/repos/austonlee/next-learn-starter/forks","keys_url":"https://api.github.com/repos/austonlee/next-learn-starter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/austonlee/next-learn-starter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/austonlee/next-learn-starter/teams","hooks_url":"https://api.github.com/repos/austonlee/next-learn-starter/hooks","issue_events_url":"https://api.github.com/repos/austonlee/next-learn-starter/issues/events{/number}","events_url":"https://api.github.com/repos/austonlee/next-learn-starter/events","assignees_url":"https://api.github.com/repos/austonlee/next-learn-starter/assignees{/user}","branches_url":"https://api.github.com/repos/austonlee/next-learn-starter/branches{/branch}","tags_url":"https://api.github.com/repos/austonlee/next-learn-starter/tags","blobs_url":"https://api.github.com/repos/austonlee/next-learn-starter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/austonlee/next-learn-starter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/austonlee/next-learn-starter/git/refs{/sha}","trees_url":"https://api.github.com/repos/austonlee/next-learn-starter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/austonlee/next-learn-starter/statuses/{sha}","languages_url":"https://api.github.com/repos/austonlee/next-learn-starter/languages","stargazers_url":"https://api.github.com/repos/austonlee/next-learn-starter/stargazers","contributors_url":"https://api.github.com/repos/austonlee/next-learn-starter/contributors","subscribers_url":"https://api.github.com/repos/austonlee/next-learn-starter/subscribers","subscription_url":"https://api.github.com/repos/austonlee/next-learn-starter/subscription","commits_url":"https://api.github.com/repos/austonlee/next-learn-starter/commits{/sha}","git_commits_url":"https://api.github.com/repos/austonlee/next-learn-starter/git/commits{/sha}","comments_url":"https://api.github.com/repos/austonlee/next-learn-starter/comments{/number}","issue_comment_url":"https://api.github.com/repos/austonlee/next-learn-starter/issues/comments{/number}","contents_url":"https://api.github.com/repos/austonlee/next-learn-starter/contents/{+path}","compare_url":"https://api.github.com/repos/austonlee/next-learn-starter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/austonlee/next-learn-starter/merges","archive_url":"https://api.github.com/repos/austonlee/next-learn-starter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/austonlee/next-learn-starter/downloads","issues_url":"https://api.github.com/repos/austonlee/next-learn-starter/issues{/number}","pulls_url":"https://api.github.com/repos/austonlee/next-learn-starter/pulls{/number}","milestones_url":"https://api.github.com/repos/austonlee/next-learn-starter/milestones{/number}","notifications_url":"https://api.github.com/repos/austonlee/next-learn-starter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/austonlee/next-learn-starter/labels{/name}","releases_url":"https://api.github.com/repos/austonlee/next-learn-starter/releases{/id}","deployments_url":"https://api.github.com/repos/austonlee/next-learn-starter/deployments","created_at":"2021-07-21T08:00:02Z","updated_at":"2021-07-20T20:34:32Z","pushed_at":"2021-07-19T18:32:44Z","git_url":"git://github.com/austonlee/next-learn-starter.git","ssh_url":"git@github.com:austonlee/next-learn-starter.git","clone_url":"https://github.com/austonlee/next-learn-starter.git","svn_url":"https://github.com/austonlee/next-learn-starter","homepage":"https://next-learn-starter.vercel.app/","size":159,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:02Z","org":{"id":14985020,"login":"vercel","gravatar_id":"","url":"https://api.github.com/orgs/vercel","avatar_url":"https://avatars.githubusercontent.com/u/14985020?"}} +{"id":"17245515122","type":"WatchEvent","actor":{"id":26083162,"login":"Kingcool759","display_login":"Kingcool759","gravatar_id":"","url":"https://api.github.com/users/Kingcool759","avatar_url":"https://avatars.githubusercontent.com/u/26083162?"},"repo":{"id":19148949,"name":"PhilJay/MPAndroidChart","url":"https://api.github.com/repos/PhilJay/MPAndroidChart"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515132","type":"PushEvent","actor":{"id":86361050,"login":"Fuggy","display_login":"Fuggy","gravatar_id":"","url":"https://api.github.com/users/Fuggy","avatar_url":"https://avatars.githubusercontent.com/u/86361050?"},"repo":{"id":379607199,"name":"Clismy/TinySummerProject","url":"https://api.github.com/repos/Clismy/TinySummerProject"},"payload":{"push_id":7562055187,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"6be463c82d5d8c9cb0da85566122084f5186e174","before":"698c7ce82394cc3b38f2f68104c04ef3d6083aa3","commits":[{"sha":"6be463c82d5d8c9cb0da85566122084f5186e174","author":{"name":"Fuggy","email":"6307ef61c0b40135fe59404c05335bddd135533f@gmail.com"},"message":"aaaa","distinct":true,"url":"https://api.github.com/repos/Clismy/TinySummerProject/commits/6be463c82d5d8c9cb0da85566122084f5186e174"}]},"public":true,"created_at":"2021-07-21T08:00:02Z"} +{"id":"17245515138","type":"PullRequestEvent","actor":{"id":11707729,"login":"gabrieldonadel","display_login":"gabrieldonadel","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","avatar_url":"https://avatars.githubusercontent.com/u/11707729?"},"repo":{"id":387916158,"name":"gabrieldonadel/pull-requests-limits","url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits"},"payload":{"action":"opened","number":5079,"pull_request":{"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5079","id":694141675,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNjc1","html_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5079","diff_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5079.diff","patch_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5079.patch","issue_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5079","number":5079,"state":"open","locked":false,"title":"Add file 5101.txt","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"body":null,"created_at":"2021-07-21T08:00:02Z","updated_at":"2021-07-21T08:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5079/commits","review_comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5079/comments","review_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5079/comments","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/d1e571b755e2a44a753c375c443a5c5a528679fb","head":{"label":"gabrieldonadel:5101","ref":"5101","sha":"d1e571b755e2a44a753c375c443a5c5a528679fb","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"repo":{"id":387916158,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MTYxNTg=","name":"pull-requests-limits","full_name":"gabrieldonadel/pull-requests-limits","private":false,"owner":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gabrieldonadel/pull-requests-limits","description":"Testing Github Pull requests limits","fork":false,"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits","forks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/forks","keys_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/teams","hooks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/hooks","issue_events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/events{/number}","events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/events","assignees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/assignees{/user}","branches_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/branches{/branch}","tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/tags","blobs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/refs{/sha}","trees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/{sha}","languages_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/languages","stargazers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/stargazers","contributors_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contributors","subscribers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscribers","subscription_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscription","commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/commits{/sha}","git_commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/commits{/sha}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/comments{/number}","issue_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/comments{/number}","contents_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contents/{+path}","compare_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/merges","archive_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/downloads","issues_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues{/number}","pulls_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls{/number}","milestones_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/milestones{/number}","notifications_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/labels{/name}","releases_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/releases{/id}","deployments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/deployments","created_at":"2021-07-20T21:17:40Z","updated_at":"2021-07-21T01:36:14Z","pushed_at":"2021-07-21T08:00:00Z","git_url":"git://github.com/gabrieldonadel/pull-requests-limits.git","ssh_url":"git@github.com:gabrieldonadel/pull-requests-limits.git","clone_url":"https://github.com/gabrieldonadel/pull-requests-limits.git","svn_url":"https://github.com/gabrieldonadel/pull-requests-limits","homepage":null,"size":1090,"stargazers_count":1,"watchers_count":1,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5077,"license":null,"forks":0,"open_issues":5077,"watchers":1,"default_branch":"master"}},"base":{"label":"gabrieldonadel:master","ref":"master","sha":"d82cf4fd9e97a60cfebab6b8f7f85f9f8380b29b","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"repo":{"id":387916158,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MTYxNTg=","name":"pull-requests-limits","full_name":"gabrieldonadel/pull-requests-limits","private":false,"owner":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gabrieldonadel/pull-requests-limits","description":"Testing Github Pull requests limits","fork":false,"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits","forks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/forks","keys_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/teams","hooks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/hooks","issue_events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/events{/number}","events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/events","assignees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/assignees{/user}","branches_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/branches{/branch}","tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/tags","blobs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/refs{/sha}","trees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/{sha}","languages_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/languages","stargazers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/stargazers","contributors_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contributors","subscribers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscribers","subscription_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscription","commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/commits{/sha}","git_commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/commits{/sha}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/comments{/number}","issue_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/comments{/number}","contents_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contents/{+path}","compare_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/merges","archive_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/downloads","issues_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues{/number}","pulls_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls{/number}","milestones_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/milestones{/number}","notifications_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/labels{/name}","releases_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/releases{/id}","deployments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/deployments","created_at":"2021-07-20T21:17:40Z","updated_at":"2021-07-21T01:36:14Z","pushed_at":"2021-07-21T08:00:00Z","git_url":"git://github.com/gabrieldonadel/pull-requests-limits.git","ssh_url":"git@github.com:gabrieldonadel/pull-requests-limits.git","clone_url":"https://github.com/gabrieldonadel/pull-requests-limits.git","svn_url":"https://github.com/gabrieldonadel/pull-requests-limits","homepage":null,"size":1090,"stargazers_count":1,"watchers_count":1,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5077,"license":null,"forks":0,"open_issues":5077,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5079"},"html":{"href":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5079"},"issue":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5079"},"comments":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5079/comments"},"review_comments":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5079/comments"},"review_comment":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5079/commits"},"statuses":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/d1e571b755e2a44a753c375c443a5c5a528679fb"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":0,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515143","type":"PullRequestEvent","actor":{"id":61073116,"login":"snehaa1989","display_login":"snehaa1989","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","avatar_url":"https://avatars.githubusercontent.com/u/61073116?"},"repo":{"id":388038822,"name":"snehaa1989/imgbot","url":"https://api.github.com/repos/snehaa1989/imgbot"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/snehaa1989/imgbot/pulls/1","id":694139218,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTM5MjE4","html_url":"https://github.com/snehaa1989/imgbot/pull/1","diff_url":"https://github.com/snehaa1989/imgbot/pull/1.diff","patch_url":"https://github.com/snehaa1989/imgbot/pull/1.patch","issue_url":"https://api.github.com/repos/snehaa1989/imgbot/issues/1","number":1,"state":"closed","locked":false,"title":"[ImgBot] Optimize images","user":{"login":"imgbot[bot]","id":31301654,"node_id":"MDM6Qm90MzEzMDE2NTQ=","avatar_url":"https://avatars.githubusercontent.com/in/4706?v=4","gravatar_id":"","url":"https://api.github.com/users/imgbot%5Bbot%5D","html_url":"https://github.com/apps/imgbot","followers_url":"https://api.github.com/users/imgbot%5Bbot%5D/followers","following_url":"https://api.github.com/users/imgbot%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/imgbot%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/imgbot%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/imgbot%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/imgbot%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/imgbot%5Bbot%5D/repos","events_url":"https://api.github.com/users/imgbot%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/imgbot%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"## Beep boop. Your images are optimized!\r\n\r\nYour image file size has been reduced!\r\n\r\n
    \r\n\r\nDetails\r\n\r\n\r\n| File | Before | After | Percent reduction |\r\n|:--|:--|:--|:--|\r\n| /demo-image.png | 392.53kb | 384.14kb | 2.14% |\r\n| /lofi1.jpg | 149.96kb | 147.09kb | 1.91% |\r\n| /violin.jpg | 20.80kb | 20.66kb | 0.69% |\r\n| | | | |\r\n| **Total :** | **563.29kb** | **551.89kb** | **2.02%** |\r\n
    \r\n\r\n---\r\n\r\n**Black Lives Matter** | [💰 donate](https://blm-bookmarks.carrd.co/#donate) | [🎓 learn](https://blm-bookmarks.carrd.co/#learn) | [✍🏾 sign](https://blm-bookmarks.carrd.co/#sign)\r\n\r\n[📝 docs](https://imgbot.net/docs) | [:octocat: repo](https://github.com/dabutvin/ImgBot) | [🙋🏾 issues](https://github.com/dabutvin/ImgBot/issues) | [🏅 swag](https://goo.gl/forms/1GX7wlhGEX8nkhGO2) | [🏪 marketplace](https://github.com/marketplace/imgbot)\r\n","created_at":"2021-07-21T07:56:15Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":"2021-07-21T08:00:02Z","merge_commit_sha":"a084470ead9361c81f02c529a450eeed19a4d92f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/snehaa1989/imgbot/pulls/1/commits","review_comments_url":"https://api.github.com/repos/snehaa1989/imgbot/pulls/1/comments","review_comment_url":"https://api.github.com/repos/snehaa1989/imgbot/pulls/comments{/number}","comments_url":"https://api.github.com/repos/snehaa1989/imgbot/issues/1/comments","statuses_url":"https://api.github.com/repos/snehaa1989/imgbot/statuses/dcd80cbf80a3abb5039fcb49649af7a7c49b9b31","head":{"label":"snehaa1989:imgbot","ref":"imgbot","sha":"dcd80cbf80a3abb5039fcb49649af7a7c49b9b31","user":{"login":"snehaa1989","id":61073116,"node_id":"MDQ6VXNlcjYxMDczMTE2","avatar_url":"https://avatars.githubusercontent.com/u/61073116?v=4","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","html_url":"https://github.com/snehaa1989","followers_url":"https://api.github.com/users/snehaa1989/followers","following_url":"https://api.github.com/users/snehaa1989/following{/other_user}","gists_url":"https://api.github.com/users/snehaa1989/gists{/gist_id}","starred_url":"https://api.github.com/users/snehaa1989/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snehaa1989/subscriptions","organizations_url":"https://api.github.com/users/snehaa1989/orgs","repos_url":"https://api.github.com/users/snehaa1989/repos","events_url":"https://api.github.com/users/snehaa1989/events{/privacy}","received_events_url":"https://api.github.com/users/snehaa1989/received_events","type":"User","site_admin":false},"repo":{"id":388038822,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMzg4MjI=","name":"imgbot","full_name":"snehaa1989/imgbot","private":false,"owner":{"login":"snehaa1989","id":61073116,"node_id":"MDQ6VXNlcjYxMDczMTE2","avatar_url":"https://avatars.githubusercontent.com/u/61073116?v=4","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","html_url":"https://github.com/snehaa1989","followers_url":"https://api.github.com/users/snehaa1989/followers","following_url":"https://api.github.com/users/snehaa1989/following{/other_user}","gists_url":"https://api.github.com/users/snehaa1989/gists{/gist_id}","starred_url":"https://api.github.com/users/snehaa1989/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snehaa1989/subscriptions","organizations_url":"https://api.github.com/users/snehaa1989/orgs","repos_url":"https://api.github.com/users/snehaa1989/repos","events_url":"https://api.github.com/users/snehaa1989/events{/privacy}","received_events_url":"https://api.github.com/users/snehaa1989/received_events","type":"User","site_admin":false},"html_url":"https://github.com/snehaa1989/imgbot","description":null,"fork":false,"url":"https://api.github.com/repos/snehaa1989/imgbot","forks_url":"https://api.github.com/repos/snehaa1989/imgbot/forks","keys_url":"https://api.github.com/repos/snehaa1989/imgbot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/snehaa1989/imgbot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/snehaa1989/imgbot/teams","hooks_url":"https://api.github.com/repos/snehaa1989/imgbot/hooks","issue_events_url":"https://api.github.com/repos/snehaa1989/imgbot/issues/events{/number}","events_url":"https://api.github.com/repos/snehaa1989/imgbot/events","assignees_url":"https://api.github.com/repos/snehaa1989/imgbot/assignees{/user}","branches_url":"https://api.github.com/repos/snehaa1989/imgbot/branches{/branch}","tags_url":"https://api.github.com/repos/snehaa1989/imgbot/tags","blobs_url":"https://api.github.com/repos/snehaa1989/imgbot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/snehaa1989/imgbot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/snehaa1989/imgbot/git/refs{/sha}","trees_url":"https://api.github.com/repos/snehaa1989/imgbot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/snehaa1989/imgbot/statuses/{sha}","languages_url":"https://api.github.com/repos/snehaa1989/imgbot/languages","stargazers_url":"https://api.github.com/repos/snehaa1989/imgbot/stargazers","contributors_url":"https://api.github.com/repos/snehaa1989/imgbot/contributors","subscribers_url":"https://api.github.com/repos/snehaa1989/imgbot/subscribers","subscription_url":"https://api.github.com/repos/snehaa1989/imgbot/subscription","commits_url":"https://api.github.com/repos/snehaa1989/imgbot/commits{/sha}","git_commits_url":"https://api.github.com/repos/snehaa1989/imgbot/git/commits{/sha}","comments_url":"https://api.github.com/repos/snehaa1989/imgbot/comments{/number}","issue_comment_url":"https://api.github.com/repos/snehaa1989/imgbot/issues/comments{/number}","contents_url":"https://api.github.com/repos/snehaa1989/imgbot/contents/{+path}","compare_url":"https://api.github.com/repos/snehaa1989/imgbot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/snehaa1989/imgbot/merges","archive_url":"https://api.github.com/repos/snehaa1989/imgbot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/snehaa1989/imgbot/downloads","issues_url":"https://api.github.com/repos/snehaa1989/imgbot/issues{/number}","pulls_url":"https://api.github.com/repos/snehaa1989/imgbot/pulls{/number}","milestones_url":"https://api.github.com/repos/snehaa1989/imgbot/milestones{/number}","notifications_url":"https://api.github.com/repos/snehaa1989/imgbot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/snehaa1989/imgbot/labels{/name}","releases_url":"https://api.github.com/repos/snehaa1989/imgbot/releases{/id}","deployments_url":"https://api.github.com/repos/snehaa1989/imgbot/deployments","created_at":"2021-07-21T07:53:41Z","updated_at":"2021-07-21T07:55:27Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/snehaa1989/imgbot.git","ssh_url":"git@github.com:snehaa1989/imgbot.git","clone_url":"https://github.com/snehaa1989/imgbot.git","svn_url":"https://github.com/snehaa1989/imgbot","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"base":{"label":"snehaa1989:main","ref":"main","sha":"edd4c4146633ac025ba60678d5e6d10a3e15f89e","user":{"login":"snehaa1989","id":61073116,"node_id":"MDQ6VXNlcjYxMDczMTE2","avatar_url":"https://avatars.githubusercontent.com/u/61073116?v=4","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","html_url":"https://github.com/snehaa1989","followers_url":"https://api.github.com/users/snehaa1989/followers","following_url":"https://api.github.com/users/snehaa1989/following{/other_user}","gists_url":"https://api.github.com/users/snehaa1989/gists{/gist_id}","starred_url":"https://api.github.com/users/snehaa1989/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snehaa1989/subscriptions","organizations_url":"https://api.github.com/users/snehaa1989/orgs","repos_url":"https://api.github.com/users/snehaa1989/repos","events_url":"https://api.github.com/users/snehaa1989/events{/privacy}","received_events_url":"https://api.github.com/users/snehaa1989/received_events","type":"User","site_admin":false},"repo":{"id":388038822,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMzg4MjI=","name":"imgbot","full_name":"snehaa1989/imgbot","private":false,"owner":{"login":"snehaa1989","id":61073116,"node_id":"MDQ6VXNlcjYxMDczMTE2","avatar_url":"https://avatars.githubusercontent.com/u/61073116?v=4","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","html_url":"https://github.com/snehaa1989","followers_url":"https://api.github.com/users/snehaa1989/followers","following_url":"https://api.github.com/users/snehaa1989/following{/other_user}","gists_url":"https://api.github.com/users/snehaa1989/gists{/gist_id}","starred_url":"https://api.github.com/users/snehaa1989/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snehaa1989/subscriptions","organizations_url":"https://api.github.com/users/snehaa1989/orgs","repos_url":"https://api.github.com/users/snehaa1989/repos","events_url":"https://api.github.com/users/snehaa1989/events{/privacy}","received_events_url":"https://api.github.com/users/snehaa1989/received_events","type":"User","site_admin":false},"html_url":"https://github.com/snehaa1989/imgbot","description":null,"fork":false,"url":"https://api.github.com/repos/snehaa1989/imgbot","forks_url":"https://api.github.com/repos/snehaa1989/imgbot/forks","keys_url":"https://api.github.com/repos/snehaa1989/imgbot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/snehaa1989/imgbot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/snehaa1989/imgbot/teams","hooks_url":"https://api.github.com/repos/snehaa1989/imgbot/hooks","issue_events_url":"https://api.github.com/repos/snehaa1989/imgbot/issues/events{/number}","events_url":"https://api.github.com/repos/snehaa1989/imgbot/events","assignees_url":"https://api.github.com/repos/snehaa1989/imgbot/assignees{/user}","branches_url":"https://api.github.com/repos/snehaa1989/imgbot/branches{/branch}","tags_url":"https://api.github.com/repos/snehaa1989/imgbot/tags","blobs_url":"https://api.github.com/repos/snehaa1989/imgbot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/snehaa1989/imgbot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/snehaa1989/imgbot/git/refs{/sha}","trees_url":"https://api.github.com/repos/snehaa1989/imgbot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/snehaa1989/imgbot/statuses/{sha}","languages_url":"https://api.github.com/repos/snehaa1989/imgbot/languages","stargazers_url":"https://api.github.com/repos/snehaa1989/imgbot/stargazers","contributors_url":"https://api.github.com/repos/snehaa1989/imgbot/contributors","subscribers_url":"https://api.github.com/repos/snehaa1989/imgbot/subscribers","subscription_url":"https://api.github.com/repos/snehaa1989/imgbot/subscription","commits_url":"https://api.github.com/repos/snehaa1989/imgbot/commits{/sha}","git_commits_url":"https://api.github.com/repos/snehaa1989/imgbot/git/commits{/sha}","comments_url":"https://api.github.com/repos/snehaa1989/imgbot/comments{/number}","issue_comment_url":"https://api.github.com/repos/snehaa1989/imgbot/issues/comments{/number}","contents_url":"https://api.github.com/repos/snehaa1989/imgbot/contents/{+path}","compare_url":"https://api.github.com/repos/snehaa1989/imgbot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/snehaa1989/imgbot/merges","archive_url":"https://api.github.com/repos/snehaa1989/imgbot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/snehaa1989/imgbot/downloads","issues_url":"https://api.github.com/repos/snehaa1989/imgbot/issues{/number}","pulls_url":"https://api.github.com/repos/snehaa1989/imgbot/pulls{/number}","milestones_url":"https://api.github.com/repos/snehaa1989/imgbot/milestones{/number}","notifications_url":"https://api.github.com/repos/snehaa1989/imgbot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/snehaa1989/imgbot/labels{/name}","releases_url":"https://api.github.com/repos/snehaa1989/imgbot/releases{/id}","deployments_url":"https://api.github.com/repos/snehaa1989/imgbot/deployments","created_at":"2021-07-21T07:53:41Z","updated_at":"2021-07-21T07:55:27Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/snehaa1989/imgbot.git","ssh_url":"git@github.com:snehaa1989/imgbot.git","clone_url":"https://github.com/snehaa1989/imgbot.git","svn_url":"https://github.com/snehaa1989/imgbot","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/snehaa1989/imgbot/pulls/1"},"html":{"href":"https://github.com/snehaa1989/imgbot/pull/1"},"issue":{"href":"https://api.github.com/repos/snehaa1989/imgbot/issues/1"},"comments":{"href":"https://api.github.com/repos/snehaa1989/imgbot/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/snehaa1989/imgbot/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/snehaa1989/imgbot/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/snehaa1989/imgbot/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/snehaa1989/imgbot/statuses/dcd80cbf80a3abb5039fcb49649af7a7c49b9b31"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"snehaa1989","id":61073116,"node_id":"MDQ6VXNlcjYxMDczMTE2","avatar_url":"https://avatars.githubusercontent.com/u/61073116?v=4","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","html_url":"https://github.com/snehaa1989","followers_url":"https://api.github.com/users/snehaa1989/followers","following_url":"https://api.github.com/users/snehaa1989/following{/other_user}","gists_url":"https://api.github.com/users/snehaa1989/gists{/gist_id}","starred_url":"https://api.github.com/users/snehaa1989/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snehaa1989/subscriptions","organizations_url":"https://api.github.com/users/snehaa1989/orgs","repos_url":"https://api.github.com/users/snehaa1989/repos","events_url":"https://api.github.com/users/snehaa1989/events{/privacy}","received_events_url":"https://api.github.com/users/snehaa1989/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":0,"deletions":0,"changed_files":3}},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515147","type":"IssuesEvent","actor":{"id":30256396,"login":"sundytt","display_login":"sundytt","gravatar_id":"","url":"https://api.github.com/users/sundytt","avatar_url":"https://avatars.githubusercontent.com/u/30256396?"},"repo":{"id":153050643,"name":"Tencent/bk-PaaS","url":"https://api.github.com/repos/Tencent/bk-PaaS"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/Tencent/bk-PaaS/issues/436","repository_url":"https://api.github.com/repos/Tencent/bk-PaaS","labels_url":"https://api.github.com/repos/Tencent/bk-PaaS/issues/436/labels{/name}","comments_url":"https://api.github.com/repos/Tencent/bk-PaaS/issues/436/comments","events_url":"https://api.github.com/repos/Tencent/bk-PaaS/issues/436/events","html_url":"https://github.com/Tencent/bk-PaaS/issues/436","id":949435843,"node_id":"MDU6SXNzdWU5NDk0MzU4NDM=","number":436,"title":"【lesscode】表单组件 加上v-model 可以获取表单值","user":{"login":"sundytt","id":30256396,"node_id":"MDQ6VXNlcjMwMjU2Mzk2","avatar_url":"https://avatars.githubusercontent.com/u/30256396?v=4","gravatar_id":"","url":"https://api.github.com/users/sundytt","html_url":"https://github.com/sundytt","followers_url":"https://api.github.com/users/sundytt/followers","following_url":"https://api.github.com/users/sundytt/following{/other_user}","gists_url":"https://api.github.com/users/sundytt/gists{/gist_id}","starred_url":"https://api.github.com/users/sundytt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sundytt/subscriptions","organizations_url":"https://api.github.com/users/sundytt/orgs","repos_url":"https://api.github.com/users/sundytt/repos","events_url":"https://api.github.com/users/sundytt/events{/privacy}","received_events_url":"https://api.github.com/users/sundytt/received_events","type":"User","site_admin":false},"labels":[{"id":2131446479,"node_id":"MDU6TGFiZWwyMTMxNDQ2NDc5","url":"https://api.github.com/repos/Tencent/bk-PaaS/labels/lesscode","name":"lesscode","color":"edbda8","default":false,"description":"lesscode bugs or features"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T08:00:02Z","updated_at":"2021-07-21T08:00:02Z","closed_at":null,"author_association":"COLLABORATOR","active_lock_reason":null,"body":"* [ ] 标签输入\r\n* [ ] 人员选择\r\n* [ ] 穿梭框,获取选择后的列表\r\n* [ ] rate","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":18461506,"login":"Tencent","gravatar_id":"","url":"https://api.github.com/orgs/Tencent","avatar_url":"https://avatars.githubusercontent.com/u/18461506?"}} +{"id":"17245515149","type":"PushEvent","actor":{"id":1473072,"login":"vitalets","display_login":"vitalets","gravatar_id":"","url":"https://api.github.com/users/vitalets","avatar_url":"https://avatars.githubusercontent.com/u/1473072?"},"repo":{"id":363393951,"name":"vitalets/skill-dev-proxy","url":"https://api.github.com/repos/vitalets/skill-dev-proxy"},"payload":{"push_id":7562055198,"size":2,"distinct_size":2,"ref":"refs/heads/main","head":"346e5aaca5b23b3eb8f9f7adb3bc7e0cab278d41","before":"ce389e66634841de39cd11e50ebc580a92da3616","commits":[{"sha":"a32f2a060540ebad7654f4e3bcb0afe395d7838c","author":{"name":"Vitaliy Potapov","email":"c9679860e330f3091c3e8d07027e1151860a1995@rambler.ru"},"message":"pkg: fix release","distinct":true,"url":"https://api.github.com/repos/vitalets/skill-dev-proxy/commits/a32f2a060540ebad7654f4e3bcb0afe395d7838c"},{"sha":"346e5aaca5b23b3eb8f9f7adb3bc7e0cab278d41","author":{"name":"Vitaliy Potapov","email":"c9679860e330f3091c3e8d07027e1151860a1995@rambler.ru"},"message":"2.0.0","distinct":true,"url":"https://api.github.com/repos/vitalets/skill-dev-proxy/commits/346e5aaca5b23b3eb8f9f7adb3bc7e0cab278d41"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515155","type":"PushEvent","actor":{"id":615952,"login":"billybobza","display_login":"billybobza","gravatar_id":"","url":"https://api.github.com/users/billybobza","avatar_url":"https://avatars.githubusercontent.com/u/615952?"},"repo":{"id":325888259,"name":"newstools/2021-the-chronicle","url":"https://api.github.com/repos/newstools/2021-the-chronicle"},"payload":{"push_id":7562055216,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"af0769c13e64fde267f804fd7a4b129cfd7c1bc0","before":"c0c791488c2ad413a4a000371b5b2869fcccd4eb","commits":[{"sha":"af0769c13e64fde267f804fd7a4b129cfd7c1bc0","author":{"name":"Billy Einkamerer","email":"051522d0c46404d8ba5b692a10a37b99b8186360@assemble.co.za"},"message":"Created Text For URL [www.chronicle.co.zw/byo-police-intercept-quantum-stolen-in-sa/]","distinct":true,"url":"https://api.github.com/repos/newstools/2021-the-chronicle/commits/af0769c13e64fde267f804fd7a4b129cfd7c1bc0"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":17193977,"login":"newstools","gravatar_id":"","url":"https://api.github.com/orgs/newstools","avatar_url":"https://avatars.githubusercontent.com/u/17193977?"}} +{"id":"17245515160","type":"PushEvent","actor":{"id":50026275,"login":"botberry","display_login":"botberry","gravatar_id":"","url":"https://api.github.com/users/botberry","avatar_url":"https://avatars.githubusercontent.com/u/50026275?"},"repo":{"id":162690887,"name":"strawberry-graphql/strawberry","url":"https://api.github.com/repos/strawberry-graphql/strawberry"},"payload":{"push_id":7562055214,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"d1f78230699f0443f75a85b90606d42feb52ae39","before":"cb31a3aeefdc58dac24a37a22d6c6bd0b9c9cf17","commits":[{"sha":"d1f78230699f0443f75a85b90606d42feb52ae39","author":{"name":"Botberry","email":"c71e7261d37a4f6ae4cfb0cbd79081310a237e67@strawberry.rocks"},"message":"Release 🍓 0.69.2","distinct":true,"url":"https://api.github.com/repos/strawberry-graphql/strawberry/commits/d1f78230699f0443f75a85b90606d42feb52ae39"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":48071860,"login":"strawberry-graphql","gravatar_id":"","url":"https://api.github.com/orgs/strawberry-graphql","avatar_url":"https://avatars.githubusercontent.com/u/48071860?"}} +{"id":"17245515161","type":"PushEvent","actor":{"id":78577301,"login":"Dhiyakhar","display_login":"Dhiyakhar","gravatar_id":"","url":"https://api.github.com/users/Dhiyakhar","avatar_url":"https://avatars.githubusercontent.com/u/78577301?"},"repo":{"id":385265283,"name":"Dhiyakhar/ulinsolution.github.io","url":"https://api.github.com/repos/Dhiyakhar/ulinsolution.github.io"},"payload":{"push_id":7562055209,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"4d4c17a4129c08d976a720f3c4fe9d8903ba4165","before":"c0440aaacb588e6748142c17da61ca2a697e3676","commits":[{"sha":"4d4c17a4129c08d976a720f3c4fe9d8903ba4165","author":{"name":"Quinque","email":"6dda6afbeb5267339624bc3c8e18e782788bb15d@users.noreply.github.com"},"message":"Update index.html","distinct":true,"url":"https://api.github.com/repos/Dhiyakhar/ulinsolution.github.io/commits/4d4c17a4129c08d976a720f3c4fe9d8903ba4165"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515165","type":"CreateEvent","actor":{"id":69674721,"login":"nishant26n","display_login":"nishant26n","gravatar_id":"","url":"https://api.github.com/users/nishant26n","avatar_url":"https://avatars.githubusercontent.com/u/69674721?"},"repo":{"id":388040574,"name":"nishant26n/todo-react","url":"https://api.github.com/repos/nishant26n/todo-react"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515166","type":"PushEvent","actor":{"id":68130091,"login":"digitalocean-e2e","display_login":"digitalocean-e2e","gravatar_id":"","url":"https://api.github.com/users/digitalocean-e2e","avatar_url":"https://avatars.githubusercontent.com/u/68130091?"},"repo":{"id":278740625,"name":"digitalocean-e2e/apps-deploy-on-push","url":"https://api.github.com/repos/digitalocean-e2e/apps-deploy-on-push"},"payload":{"push_id":7562055205,"size":1,"distinct_size":1,"ref":"refs/heads/goBl7XiAod","head":"1cf42bd65fc040bb85f25d1c89bcd51d0b85935e","before":"2d70374d81556aec591895093b062001debd84a7","commits":[{"sha":"1cf42bd65fc040bb85f25d1c89bcd51d0b85935e","author":{"name":"DigitalOcean E2E","email":"44491d4b74bd0174eebb1e35657a17a886edfb0c@digitalocean.com"},"message":"New commit on branch goBl7XiAod","distinct":true,"url":"https://api.github.com/repos/digitalocean-e2e/apps-deploy-on-push/commits/1cf42bd65fc040bb85f25d1c89bcd51d0b85935e"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515181","type":"PushEvent","actor":{"id":8651308,"login":"tadashi0713","display_login":"tadashi0713","gravatar_id":"","url":"https://api.github.com/users/tadashi0713","avatar_url":"https://avatars.githubusercontent.com/u/8651308?"},"repo":{"id":382344206,"name":"tadashi0713/flutter-orb","url":"https://api.github.com/repos/tadashi0713/flutter-orb"},"payload":{"push_id":7562055220,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"ff9ea0ae1db9e546ce8b7f14863ea031674b72c6","before":"34909c8f8eabb45ec58d5f1e10bd974301c73326","commits":[{"sha":"ff9ea0ae1db9e546ce8b7f14863ea031674b72c6","author":{"name":"Tadashi Nemoto","email":"70cc15cd743d408a333613bef762d1ae6915b9eb@gmail.com"},"message":"change flutter doctor","distinct":true,"url":"https://api.github.com/repos/tadashi0713/flutter-orb/commits/ff9ea0ae1db9e546ce8b7f14863ea031674b72c6"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515187","type":"PushEvent","actor":{"id":19972370,"login":"goutamp","display_login":"goutamp","gravatar_id":"","url":"https://api.github.com/users/goutamp","avatar_url":"https://avatars.githubusercontent.com/u/19972370?"},"repo":{"id":368109195,"name":"goutamp/sample3","url":"https://api.github.com/repos/goutamp/sample3"},"payload":{"push_id":7562055233,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"177c13a3bcb2e7ca16f1037b2a706d195a3894f7","before":"915814b97ac4b479c1ad1894ba1f66f7bc9fa5a6","commits":[{"sha":"177c13a3bcb2e7ca16f1037b2a706d195a3894f7","author":{"name":"Goutam Pramanick","email":"5d42db1c63d9d2080d94e2b5c81c92be76cd0d27@gmail.com"},"message":"Update requirements.txt","distinct":true,"url":"https://api.github.com/repos/goutamp/sample3/commits/177c13a3bcb2e7ca16f1037b2a706d195a3894f7"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515192","type":"PushEvent","actor":{"id":1381363,"login":"weskamm","display_login":"weskamm","gravatar_id":"","url":"https://api.github.com/users/weskamm","avatar_url":"https://avatars.githubusercontent.com/u/1381363?"},"repo":{"id":152080013,"name":"terrestris/react-geo-baseclient","url":"https://api.github.com/repos/terrestris/react-geo-baseclient"},"payload":{"push_id":7562055244,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"1fda6a3a2dd7ac6a586a28f025d2dfbd34e9bf95","before":"bff21bd42da42af3442ccd89f746aec4018b4273","commits":[{"sha":"1fda6a3a2dd7ac6a586a28f025d2dfbd34e9bf95","author":{"name":"Johannes Weskamm","email":"40c91adf413f390649564fd61a25503d7ec06601@terrestris.de"},"message":"Update CHANGELOG.md","distinct":true,"url":"https://api.github.com/repos/terrestris/react-geo-baseclient/commits/1fda6a3a2dd7ac6a586a28f025d2dfbd34e9bf95"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":1510416,"login":"terrestris","gravatar_id":"","url":"https://api.github.com/orgs/terrestris","avatar_url":"https://avatars.githubusercontent.com/u/1510416?"}} +{"id":"17245515193","type":"PullRequestEvent","actor":{"id":7659712,"login":"zenkins","display_login":"zenkins","gravatar_id":"","url":"https://api.github.com/users/zenkins","avatar_url":"https://avatars.githubusercontent.com/u/7659712?"},"repo":{"id":53574465,"name":"wireapp/wire-ios-cryptobox","url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox"},"payload":{"action":"closed","number":59,"pull_request":{"url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/59","id":694138563,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTM4NTYz","html_url":"https://github.com/wireapp/wire-ios-cryptobox/pull/59","diff_url":"https://github.com/wireapp/wire-ios-cryptobox/pull/59.diff","patch_url":"https://github.com/wireapp/wire-ios-cryptobox/pull/59.patch","issue_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/59","number":59,"state":"closed","locked":false,"title":"chore: bump components","user":{"login":"github-actions[bot]","id":41898282,"node_id":"MDM6Qm90NDE4OTgyODI=","avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4","gravatar_id":"","url":"https://api.github.com/users/github-actions%5Bbot%5D","html_url":"https://github.com/apps/github-actions","followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers","following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos","events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bump framework(s) version for: wireapp/wire-ios-utilities","created_at":"2021-07-21T07:55:10Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":"2021-07-21T08:00:02Z","merge_commit_sha":"f07ec3becc5f1b61a2f5312b1776fe526d25e678","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/59/commits","review_comments_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/59/comments","review_comment_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/comments{/number}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/59/comments","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/statuses/6ecbbd3bbba43d477bdd0b484ce9338049367934","head":{"label":"wireapp:chore/bump_2021-07-21-075451","ref":"chore/bump_2021-07-21-075451","sha":"6ecbbd3bbba43d477bdd0b484ce9338049367934","user":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"repo":{"id":53574465,"node_id":"MDEwOlJlcG9zaXRvcnk1MzU3NDQ2NQ==","name":"wire-ios-cryptobox","full_name":"wireapp/wire-ios-cryptobox","private":false,"owner":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/wireapp/wire-ios-cryptobox","description":null,"fork":false,"url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox","forks_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/forks","keys_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/teams","hooks_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/hooks","issue_events_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/events{/number}","events_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/events","assignees_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/assignees{/user}","branches_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/branches{/branch}","tags_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/tags","blobs_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/refs{/sha}","trees_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/statuses/{sha}","languages_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/languages","stargazers_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/stargazers","contributors_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/contributors","subscribers_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/subscribers","subscription_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/subscription","commits_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/commits{/sha}","git_commits_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/commits{/sha}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/comments{/number}","issue_comment_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/comments{/number}","contents_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/contents/{+path}","compare_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/merges","archive_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/downloads","issues_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues{/number}","pulls_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls{/number}","milestones_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/milestones{/number}","notifications_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/labels{/name}","releases_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/releases{/id}","deployments_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/deployments","created_at":"2016-03-10T10:04:06Z","updated_at":"2021-07-16T11:28:06Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/wireapp/wire-ios-cryptobox.git","ssh_url":"git@github.com:wireapp/wire-ios-cryptobox.git","clone_url":"https://github.com/wireapp/wire-ios-cryptobox.git","svn_url":"https://github.com/wireapp/wire-ios-cryptobox","homepage":"https://wire.com","size":24702,"stargazers_count":24,"watchers_count":24,"language":"Swift","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":10,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":10,"open_issues":0,"watchers":24,"default_branch":"develop"}},"base":{"label":"wireapp:develop","ref":"develop","sha":"fae8d887d8f43e12b46ae7ee22c3365b284e59db","user":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"repo":{"id":53574465,"node_id":"MDEwOlJlcG9zaXRvcnk1MzU3NDQ2NQ==","name":"wire-ios-cryptobox","full_name":"wireapp/wire-ios-cryptobox","private":false,"owner":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/wireapp/wire-ios-cryptobox","description":null,"fork":false,"url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox","forks_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/forks","keys_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/teams","hooks_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/hooks","issue_events_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/events{/number}","events_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/events","assignees_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/assignees{/user}","branches_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/branches{/branch}","tags_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/tags","blobs_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/refs{/sha}","trees_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/statuses/{sha}","languages_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/languages","stargazers_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/stargazers","contributors_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/contributors","subscribers_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/subscribers","subscription_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/subscription","commits_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/commits{/sha}","git_commits_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/git/commits{/sha}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/comments{/number}","issue_comment_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/comments{/number}","contents_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/contents/{+path}","compare_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/merges","archive_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/downloads","issues_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues{/number}","pulls_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls{/number}","milestones_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/milestones{/number}","notifications_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/labels{/name}","releases_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/releases{/id}","deployments_url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/deployments","created_at":"2016-03-10T10:04:06Z","updated_at":"2021-07-16T11:28:06Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/wireapp/wire-ios-cryptobox.git","ssh_url":"git@github.com:wireapp/wire-ios-cryptobox.git","clone_url":"https://github.com/wireapp/wire-ios-cryptobox.git","svn_url":"https://github.com/wireapp/wire-ios-cryptobox","homepage":"https://wire.com","size":24702,"stargazers_count":24,"watchers_count":24,"language":"Swift","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":10,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":10,"open_issues":0,"watchers":24,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/59"},"html":{"href":"https://github.com/wireapp/wire-ios-cryptobox/pull/59"},"issue":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/59"},"comments":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/issues/59/comments"},"review_comments":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/59/comments"},"review_comment":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/pulls/59/commits"},"statuses":{"href":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/statuses/6ecbbd3bbba43d477bdd0b484ce9338049367934"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"zenkins","id":7659712,"node_id":"MDQ6VXNlcjc2NTk3MTI=","avatar_url":"https://avatars.githubusercontent.com/u/7659712?v=4","gravatar_id":"","url":"https://api.github.com/users/zenkins","html_url":"https://github.com/zenkins","followers_url":"https://api.github.com/users/zenkins/followers","following_url":"https://api.github.com/users/zenkins/following{/other_user}","gists_url":"https://api.github.com/users/zenkins/gists{/gist_id}","starred_url":"https://api.github.com/users/zenkins/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zenkins/subscriptions","organizations_url":"https://api.github.com/users/zenkins/orgs","repos_url":"https://api.github.com/users/zenkins/repos","events_url":"https://api.github.com/users/zenkins/events{/privacy}","received_events_url":"https://api.github.com/users/zenkins/received_events","type":"User","site_admin":false},"comments":2,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":3,"deletions":3,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":16047324,"login":"wireapp","gravatar_id":"","url":"https://api.github.com/orgs/wireapp","avatar_url":"https://avatars.githubusercontent.com/u/16047324?"}} +{"id":"17245515199","type":"PullRequestEvent","actor":{"id":11852044,"login":"billypchan","display_login":"billypchan","gravatar_id":"","url":"https://api.github.com/users/billypchan","avatar_url":"https://avatars.githubusercontent.com/u/11852044?"},"repo":{"id":63856914,"name":"wireapp/wire-ios-link-preview","url":"https://api.github.com/repos/wireapp/wire-ios-link-preview"},"payload":{"action":"closed","number":80,"pull_request":{"url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/80","id":692806970,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyODA2OTcw","html_url":"https://github.com/wireapp/wire-ios-link-preview/pull/80","diff_url":"https://github.com/wireapp/wire-ios-link-preview/pull/80.diff","patch_url":"https://github.com/wireapp/wire-ios-link-preview/pull/80.patch","issue_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/80","number":80,"state":"closed","locked":false,"title":"chore: bump components","user":{"login":"github-actions[bot]","id":41898282,"node_id":"MDM6Qm90NDE4OTgyODI=","avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4","gravatar_id":"","url":"https://api.github.com/users/github-actions%5Bbot%5D","html_url":"https://github.com/apps/github-actions","followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers","following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos","events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bump framework(s) version for: wireapp/wire-ios-utilities","created_at":"2021-07-19T17:52:53Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":null,"merge_commit_sha":"03f9797f405a9f3ac2a8e8b8b550f9ea27007e7f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/80/commits","review_comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/80/comments","review_comment_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/comments{/number}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/80/comments","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/58d41c8de296d7dc7d2b3e173d82caf171efb291","head":{"label":"wireapp:chore/bump_2021-07-19-175234","ref":"chore/bump_2021-07-19-175234","sha":"58d41c8de296d7dc7d2b3e173d82caf171efb291","user":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"repo":{"id":63856914,"node_id":"MDEwOlJlcG9zaXRvcnk2Mzg1NjkxNA==","name":"wire-ios-link-preview","full_name":"wireapp/wire-ios-link-preview","private":false,"owner":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/wireapp/wire-ios-link-preview","description":"🏷 Open Graph data parser used to generate link previews","fork":false,"url":"https://api.github.com/repos/wireapp/wire-ios-link-preview","forks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/forks","keys_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/teams","hooks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/hooks","issue_events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/events{/number}","events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/events","assignees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/assignees{/user}","branches_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/branches{/branch}","tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/tags","blobs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/refs{/sha}","trees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/{sha}","languages_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/languages","stargazers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/stargazers","contributors_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contributors","subscribers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscribers","subscription_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscription","commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/commits{/sha}","git_commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/commits{/sha}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/comments{/number}","issue_comment_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/comments{/number}","contents_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contents/{+path}","compare_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/merges","archive_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/downloads","issues_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues{/number}","pulls_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls{/number}","milestones_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/milestones{/number}","notifications_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/labels{/name}","releases_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/releases{/id}","deployments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/deployments","created_at":"2016-07-21T09:40:14Z","updated_at":"2021-07-21T07:59:30Z","pushed_at":"2021-07-21T07:59:30Z","git_url":"git://github.com/wireapp/wire-ios-link-preview.git","ssh_url":"git@github.com:wireapp/wire-ios-link-preview.git","clone_url":"https://github.com/wireapp/wire-ios-link-preview.git","svn_url":"https://github.com/wireapp/wire-ios-link-preview","homepage":" https://wire.com ","size":1295,"stargazers_count":52,"watchers_count":52,"language":"Swift","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":11,"open_issues":4,"watchers":52,"default_branch":"develop"}},"base":{"label":"wireapp:develop","ref":"develop","sha":"dc8f49fb86e6a078579a176c7300920d12e9571a","user":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"repo":{"id":63856914,"node_id":"MDEwOlJlcG9zaXRvcnk2Mzg1NjkxNA==","name":"wire-ios-link-preview","full_name":"wireapp/wire-ios-link-preview","private":false,"owner":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/wireapp/wire-ios-link-preview","description":"🏷 Open Graph data parser used to generate link previews","fork":false,"url":"https://api.github.com/repos/wireapp/wire-ios-link-preview","forks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/forks","keys_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/teams","hooks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/hooks","issue_events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/events{/number}","events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/events","assignees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/assignees{/user}","branches_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/branches{/branch}","tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/tags","blobs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/refs{/sha}","trees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/{sha}","languages_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/languages","stargazers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/stargazers","contributors_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contributors","subscribers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscribers","subscription_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscription","commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/commits{/sha}","git_commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/commits{/sha}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/comments{/number}","issue_comment_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/comments{/number}","contents_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contents/{+path}","compare_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/merges","archive_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/downloads","issues_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues{/number}","pulls_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls{/number}","milestones_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/milestones{/number}","notifications_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/labels{/name}","releases_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/releases{/id}","deployments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/deployments","created_at":"2016-07-21T09:40:14Z","updated_at":"2021-07-21T07:59:30Z","pushed_at":"2021-07-21T07:59:30Z","git_url":"git://github.com/wireapp/wire-ios-link-preview.git","ssh_url":"git@github.com:wireapp/wire-ios-link-preview.git","clone_url":"https://github.com/wireapp/wire-ios-link-preview.git","svn_url":"https://github.com/wireapp/wire-ios-link-preview","homepage":" https://wire.com ","size":1295,"stargazers_count":52,"watchers_count":52,"language":"Swift","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":11,"open_issues":4,"watchers":52,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/80"},"html":{"href":"https://github.com/wireapp/wire-ios-link-preview/pull/80"},"issue":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/80"},"comments":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/80/comments"},"review_comments":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/80/comments"},"review_comment":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/80/commits"},"statuses":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/58d41c8de296d7dc7d2b3e173d82caf171efb291"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":1,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":3,"deletions":3,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":16047324,"login":"wireapp","gravatar_id":"","url":"https://api.github.com/orgs/wireapp","avatar_url":"https://avatars.githubusercontent.com/u/16047324?"}} +{"id":"17245515202","type":"PullRequestEvent","actor":{"id":39814207,"login":"pull[bot]","display_login":"pull","gravatar_id":"","url":"https://api.github.com/users/pull[bot]","avatar_url":"https://avatars.githubusercontent.com/u/39814207?"},"repo":{"id":375126034,"name":"rahil-sh/tutorials","url":"https://api.github.com/repos/rahil-sh/tutorials"},"payload":{"action":"closed","number":60,"pull_request":{"url":"https://api.github.com/repos/rahil-sh/tutorials/pulls/60","id":694141538,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNTM4","html_url":"https://github.com/rahil-sh/tutorials/pull/60","diff_url":"https://github.com/rahil-sh/tutorials/pull/60.diff","patch_url":"https://github.com/rahil-sh/tutorials/pull/60.patch","issue_url":"https://api.github.com/repos/rahil-sh/tutorials/issues/60","number":60,"state":"closed","locked":false,"title":"[pull] master from eugenp:master","user":{"login":"pull[bot]","id":39814207,"node_id":"MDM6Qm90Mzk4MTQyMDc=","avatar_url":"https://avatars.githubusercontent.com/in/12910?v=4","gravatar_id":"","url":"https://api.github.com/users/pull%5Bbot%5D","html_url":"https://github.com/apps/pull","followers_url":"https://api.github.com/users/pull%5Bbot%5D/followers","following_url":"https://api.github.com/users/pull%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/pull%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/pull%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pull%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/pull%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/pull%5Bbot%5D/repos","events_url":"https://api.github.com/users/pull%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/pull%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"See [Commits](/rahil-sh/tutorials/pull/60/commits) and [Changes](/rahil-sh/tutorials/pull/60/files) for more details.\n\n-----\nCreated by [ **pull[bot]**](https://github.com/wei/pull)\n\n_Can you help keep this open source service alive? **[💖 Please sponsor : )](https://prod.download/pull-pr-sponsor)**_","created_at":"2021-07-21T07:59:48Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":"2021-07-21T08:00:02Z","merge_commit_sha":"1a07f9d2bfa2ba750598e8da153f101f645e35fe","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":3073681260,"node_id":"MDU6TGFiZWwzMDczNjgxMjYw","url":"https://api.github.com/repos/rahil-sh/tutorials/labels/:arrow_heading_down:%20pull","name":":arrow_heading_down: pull","color":"ededed","default":false,"description":null}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/rahil-sh/tutorials/pulls/60/commits","review_comments_url":"https://api.github.com/repos/rahil-sh/tutorials/pulls/60/comments","review_comment_url":"https://api.github.com/repos/rahil-sh/tutorials/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rahil-sh/tutorials/issues/60/comments","statuses_url":"https://api.github.com/repos/rahil-sh/tutorials/statuses/1a07f9d2bfa2ba750598e8da153f101f645e35fe","head":{"label":"eugenp:master","ref":"master","sha":"1a07f9d2bfa2ba750598e8da153f101f645e35fe","user":{"login":"eugenp","id":1022859,"node_id":"MDQ6VXNlcjEwMjI4NTk=","avatar_url":"https://avatars.githubusercontent.com/u/1022859?v=4","gravatar_id":"","url":"https://api.github.com/users/eugenp","html_url":"https://github.com/eugenp","followers_url":"https://api.github.com/users/eugenp/followers","following_url":"https://api.github.com/users/eugenp/following{/other_user}","gists_url":"https://api.github.com/users/eugenp/gists{/gist_id}","starred_url":"https://api.github.com/users/eugenp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eugenp/subscriptions","organizations_url":"https://api.github.com/users/eugenp/orgs","repos_url":"https://api.github.com/users/eugenp/repos","events_url":"https://api.github.com/users/eugenp/events{/privacy}","received_events_url":"https://api.github.com/users/eugenp/received_events","type":"User","site_admin":false},"repo":{"id":9754983,"node_id":"MDEwOlJlcG9zaXRvcnk5NzU0OTgz","name":"tutorials","full_name":"eugenp/tutorials","private":false,"owner":{"login":"eugenp","id":1022859,"node_id":"MDQ6VXNlcjEwMjI4NTk=","avatar_url":"https://avatars.githubusercontent.com/u/1022859?v=4","gravatar_id":"","url":"https://api.github.com/users/eugenp","html_url":"https://github.com/eugenp","followers_url":"https://api.github.com/users/eugenp/followers","following_url":"https://api.github.com/users/eugenp/following{/other_user}","gists_url":"https://api.github.com/users/eugenp/gists{/gist_id}","starred_url":"https://api.github.com/users/eugenp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eugenp/subscriptions","organizations_url":"https://api.github.com/users/eugenp/orgs","repos_url":"https://api.github.com/users/eugenp/repos","events_url":"https://api.github.com/users/eugenp/events{/privacy}","received_events_url":"https://api.github.com/users/eugenp/received_events","type":"User","site_admin":false},"html_url":"https://github.com/eugenp/tutorials","description":"Just Announced - \"Learn Spring Security OAuth\": ","fork":false,"url":"https://api.github.com/repos/eugenp/tutorials","forks_url":"https://api.github.com/repos/eugenp/tutorials/forks","keys_url":"https://api.github.com/repos/eugenp/tutorials/keys{/key_id}","collaborators_url":"https://api.github.com/repos/eugenp/tutorials/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/eugenp/tutorials/teams","hooks_url":"https://api.github.com/repos/eugenp/tutorials/hooks","issue_events_url":"https://api.github.com/repos/eugenp/tutorials/issues/events{/number}","events_url":"https://api.github.com/repos/eugenp/tutorials/events","assignees_url":"https://api.github.com/repos/eugenp/tutorials/assignees{/user}","branches_url":"https://api.github.com/repos/eugenp/tutorials/branches{/branch}","tags_url":"https://api.github.com/repos/eugenp/tutorials/tags","blobs_url":"https://api.github.com/repos/eugenp/tutorials/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/eugenp/tutorials/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/eugenp/tutorials/git/refs{/sha}","trees_url":"https://api.github.com/repos/eugenp/tutorials/git/trees{/sha}","statuses_url":"https://api.github.com/repos/eugenp/tutorials/statuses/{sha}","languages_url":"https://api.github.com/repos/eugenp/tutorials/languages","stargazers_url":"https://api.github.com/repos/eugenp/tutorials/stargazers","contributors_url":"https://api.github.com/repos/eugenp/tutorials/contributors","subscribers_url":"https://api.github.com/repos/eugenp/tutorials/subscribers","subscription_url":"https://api.github.com/repos/eugenp/tutorials/subscription","commits_url":"https://api.github.com/repos/eugenp/tutorials/commits{/sha}","git_commits_url":"https://api.github.com/repos/eugenp/tutorials/git/commits{/sha}","comments_url":"https://api.github.com/repos/eugenp/tutorials/comments{/number}","issue_comment_url":"https://api.github.com/repos/eugenp/tutorials/issues/comments{/number}","contents_url":"https://api.github.com/repos/eugenp/tutorials/contents/{+path}","compare_url":"https://api.github.com/repos/eugenp/tutorials/compare/{base}...{head}","merges_url":"https://api.github.com/repos/eugenp/tutorials/merges","archive_url":"https://api.github.com/repos/eugenp/tutorials/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/eugenp/tutorials/downloads","issues_url":"https://api.github.com/repos/eugenp/tutorials/issues{/number}","pulls_url":"https://api.github.com/repos/eugenp/tutorials/pulls{/number}","milestones_url":"https://api.github.com/repos/eugenp/tutorials/milestones{/number}","notifications_url":"https://api.github.com/repos/eugenp/tutorials/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/eugenp/tutorials/labels{/name}","releases_url":"https://api.github.com/repos/eugenp/tutorials/releases{/id}","deployments_url":"https://api.github.com/repos/eugenp/tutorials/deployments","created_at":"2013-04-29T18:26:36Z","updated_at":"2021-07-21T07:27:14Z","pushed_at":"2021-07-21T03:17:19Z","git_url":"git://github.com/eugenp/tutorials.git","ssh_url":"git@github.com:eugenp/tutorials.git","clone_url":"https://github.com/eugenp/tutorials.git","svn_url":"https://github.com/eugenp/tutorials","homepage":"http://bit.ly/github-lsso","size":323346,"stargazers_count":26689,"watchers_count":26689,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":43022,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":60,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":43022,"open_issues":60,"watchers":26689,"default_branch":"master"}},"base":{"label":"rahil-sh:master","ref":"master","sha":"182dfd4e9f5f3d5a938eac648bea09a082ddc711","user":{"login":"rahil-sh","id":85578837,"node_id":"MDQ6VXNlcjg1NTc4ODM3","avatar_url":"https://avatars.githubusercontent.com/u/85578837?v=4","gravatar_id":"","url":"https://api.github.com/users/rahil-sh","html_url":"https://github.com/rahil-sh","followers_url":"https://api.github.com/users/rahil-sh/followers","following_url":"https://api.github.com/users/rahil-sh/following{/other_user}","gists_url":"https://api.github.com/users/rahil-sh/gists{/gist_id}","starred_url":"https://api.github.com/users/rahil-sh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rahil-sh/subscriptions","organizations_url":"https://api.github.com/users/rahil-sh/orgs","repos_url":"https://api.github.com/users/rahil-sh/repos","events_url":"https://api.github.com/users/rahil-sh/events{/privacy}","received_events_url":"https://api.github.com/users/rahil-sh/received_events","type":"User","site_admin":false},"repo":{"id":375126034,"node_id":"MDEwOlJlcG9zaXRvcnkzNzUxMjYwMzQ=","name":"tutorials","full_name":"rahil-sh/tutorials","private":false,"owner":{"login":"rahil-sh","id":85578837,"node_id":"MDQ6VXNlcjg1NTc4ODM3","avatar_url":"https://avatars.githubusercontent.com/u/85578837?v=4","gravatar_id":"","url":"https://api.github.com/users/rahil-sh","html_url":"https://github.com/rahil-sh","followers_url":"https://api.github.com/users/rahil-sh/followers","following_url":"https://api.github.com/users/rahil-sh/following{/other_user}","gists_url":"https://api.github.com/users/rahil-sh/gists{/gist_id}","starred_url":"https://api.github.com/users/rahil-sh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rahil-sh/subscriptions","organizations_url":"https://api.github.com/users/rahil-sh/orgs","repos_url":"https://api.github.com/users/rahil-sh/repos","events_url":"https://api.github.com/users/rahil-sh/events{/privacy}","received_events_url":"https://api.github.com/users/rahil-sh/received_events","type":"User","site_admin":false},"html_url":"https://github.com/rahil-sh/tutorials","description":"Just Announced - \"Learn Spring Security OAuth\": ","fork":true,"url":"https://api.github.com/repos/rahil-sh/tutorials","forks_url":"https://api.github.com/repos/rahil-sh/tutorials/forks","keys_url":"https://api.github.com/repos/rahil-sh/tutorials/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rahil-sh/tutorials/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rahil-sh/tutorials/teams","hooks_url":"https://api.github.com/repos/rahil-sh/tutorials/hooks","issue_events_url":"https://api.github.com/repos/rahil-sh/tutorials/issues/events{/number}","events_url":"https://api.github.com/repos/rahil-sh/tutorials/events","assignees_url":"https://api.github.com/repos/rahil-sh/tutorials/assignees{/user}","branches_url":"https://api.github.com/repos/rahil-sh/tutorials/branches{/branch}","tags_url":"https://api.github.com/repos/rahil-sh/tutorials/tags","blobs_url":"https://api.github.com/repos/rahil-sh/tutorials/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rahil-sh/tutorials/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rahil-sh/tutorials/git/refs{/sha}","trees_url":"https://api.github.com/repos/rahil-sh/tutorials/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rahil-sh/tutorials/statuses/{sha}","languages_url":"https://api.github.com/repos/rahil-sh/tutorials/languages","stargazers_url":"https://api.github.com/repos/rahil-sh/tutorials/stargazers","contributors_url":"https://api.github.com/repos/rahil-sh/tutorials/contributors","subscribers_url":"https://api.github.com/repos/rahil-sh/tutorials/subscribers","subscription_url":"https://api.github.com/repos/rahil-sh/tutorials/subscription","commits_url":"https://api.github.com/repos/rahil-sh/tutorials/commits{/sha}","git_commits_url":"https://api.github.com/repos/rahil-sh/tutorials/git/commits{/sha}","comments_url":"https://api.github.com/repos/rahil-sh/tutorials/comments{/number}","issue_comment_url":"https://api.github.com/repos/rahil-sh/tutorials/issues/comments{/number}","contents_url":"https://api.github.com/repos/rahil-sh/tutorials/contents/{+path}","compare_url":"https://api.github.com/repos/rahil-sh/tutorials/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rahil-sh/tutorials/merges","archive_url":"https://api.github.com/repos/rahil-sh/tutorials/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rahil-sh/tutorials/downloads","issues_url":"https://api.github.com/repos/rahil-sh/tutorials/issues{/number}","pulls_url":"https://api.github.com/repos/rahil-sh/tutorials/pulls{/number}","milestones_url":"https://api.github.com/repos/rahil-sh/tutorials/milestones{/number}","notifications_url":"https://api.github.com/repos/rahil-sh/tutorials/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rahil-sh/tutorials/labels{/name}","releases_url":"https://api.github.com/repos/rahil-sh/tutorials/releases{/id}","deployments_url":"https://api.github.com/repos/rahil-sh/tutorials/deployments","created_at":"2021-06-08T19:42:50Z","updated_at":"2021-07-21T02:00:55Z","pushed_at":"2021-07-21T07:59:58Z","git_url":"git://github.com/rahil-sh/tutorials.git","ssh_url":"git@github.com:rahil-sh/tutorials.git","clone_url":"https://github.com/rahil-sh/tutorials.git","svn_url":"https://github.com/rahil-sh/tutorials","homepage":"http://bit.ly/github-lsso","size":332769,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rahil-sh/tutorials/pulls/60"},"html":{"href":"https://github.com/rahil-sh/tutorials/pull/60"},"issue":{"href":"https://api.github.com/repos/rahil-sh/tutorials/issues/60"},"comments":{"href":"https://api.github.com/repos/rahil-sh/tutorials/issues/60/comments"},"review_comments":{"href":"https://api.github.com/repos/rahil-sh/tutorials/pulls/60/comments"},"review_comment":{"href":"https://api.github.com/repos/rahil-sh/tutorials/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rahil-sh/tutorials/pulls/60/commits"},"statuses":{"href":"https://api.github.com/repos/rahil-sh/tutorials/statuses/1a07f9d2bfa2ba750598e8da153f101f645e35fe"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"pull[bot]","id":39814207,"node_id":"MDM6Qm90Mzk4MTQyMDc=","avatar_url":"https://avatars.githubusercontent.com/in/12910?v=4","gravatar_id":"","url":"https://api.github.com/users/pull%5Bbot%5D","html_url":"https://github.com/apps/pull","followers_url":"https://api.github.com/users/pull%5Bbot%5D/followers","following_url":"https://api.github.com/users/pull%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/pull%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/pull%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pull%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/pull%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/pull%5Bbot%5D/repos","events_url":"https://api.github.com/users/pull%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/pull%5Bbot%5D/received_events","type":"Bot","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":53,"deletions":0,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515205","type":"PushEvent","actor":{"id":86876730,"login":"sfr404sfrzed","display_login":"sfr404sfrzed","gravatar_id":"","url":"https://api.github.com/users/sfr404sfrzed","avatar_url":"https://avatars.githubusercontent.com/u/86876730?"},"repo":{"id":383315825,"name":"sfr404sfrzed/-","url":"https://api.github.com/repos/sfr404sfrzed/-"},"payload":{"push_id":7562055222,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f233b944ce3ec0be6cf7038527cf14ddf15f2cc8","before":"fe2e016c130dc5f2de204512fd0ad5460ef1dbba","commits":[{"sha":"f233b944ce3ec0be6cf7038527cf14ddf15f2cc8","author":{"name":"sfr404sfrzed","email":"e8b38d236e779bd3d93aa607ce187e27372ff3f7@users.noreply.github.com"},"message":"Update .dabll.py","distinct":true,"url":"https://api.github.com/repos/sfr404sfrzed/-/commits/f233b944ce3ec0be6cf7038527cf14ddf15f2cc8"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515211","type":"PushEvent","actor":{"id":46501627,"login":"0xhunster","display_login":"0xhunster","gravatar_id":"","url":"https://api.github.com/users/0xhunster","avatar_url":"https://avatars.githubusercontent.com/u/46501627?"},"repo":{"id":292098379,"name":"0xhunster/0xhunster.github.io","url":"https://api.github.com/repos/0xhunster/0xhunster.github.io"},"payload":{"push_id":7562055227,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"7c959432ea27aa928568d20636819603ad0382a1","before":"f9d9ca2abe676b4fb597961c224202b50ffd90f7","commits":[{"sha":"7c959432ea27aa928568d20636819603ad0382a1","author":{"name":"Akash Sarkar","email":"e1eef10dc4a5729ad931ea352715b4ae9cd01075@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/0xhunster/0xhunster.github.io/commits/7c959432ea27aa928568d20636819603ad0382a1"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515212","type":"PushEvent","actor":{"id":10975639,"login":"qtprojectorg","display_login":"qtprojectorg","gravatar_id":"","url":"https://api.github.com/users/qtprojectorg","avatar_url":"https://avatars.githubusercontent.com/u/10975639?"},"repo":{"id":9881624,"name":"qt/qttranslations","url":"https://api.github.com/repos/qt/qttranslations"},"payload":{"push_id":7562055232,"size":1,"distinct_size":1,"ref":"refs/heads/dev","head":"535b068b191cbe3a56dc90ae3d1af869c3fc3ec9","before":"437130042058bf19481bf7b53dfc10a819642e05","commits":[{"sha":"535b068b191cbe3a56dc90ae3d1af869c3fc3ec9","author":{"name":"Qt Submodule Update Bot","email":"2f81ea16450dd03d9daf9b88cc53108e117fc79b@qt-project.org"},"message":"Update dependencies on 'dev' in qt/qttranslations\n\nChange-Id: I01975c33e7329939ba36a17baa85b8fad443c227\nReviewed-by: Qt Submodule Update Bot ","distinct":true,"url":"https://api.github.com/repos/qt/qttranslations/commits/535b068b191cbe3a56dc90ae3d1af869c3fc3ec9"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":159455,"login":"qt","gravatar_id":"","url":"https://api.github.com/orgs/qt","avatar_url":"https://avatars.githubusercontent.com/u/159455?"}} +{"id":"17245515217","type":"PushEvent","actor":{"id":59720881,"login":"J-hash-code","display_login":"J-hash-code","gravatar_id":"","url":"https://api.github.com/users/J-hash-code","avatar_url":"https://avatars.githubusercontent.com/u/59720881?"},"repo":{"id":385447829,"name":"J-hash-code/PoonSwimming","url":"https://api.github.com/repos/J-hash-code/PoonSwimming"},"payload":{"push_id":7562055241,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"06e8c5b12e6eb6ae5dc757eff391fb5111c02432","before":"61506c933217661dcf0a249e92b01b4ca2833204","commits":[{"sha":"06e8c5b12e6eb6ae5dc757eff391fb5111c02432","author":{"name":"J-hash-code","email":"991da99bcfc402530d397c6b753e7416e6ede3ee@users.noreply.github.com"},"message":"Update index.html","distinct":true,"url":"https://api.github.com/repos/J-hash-code/PoonSwimming/commits/06e8c5b12e6eb6ae5dc757eff391fb5111c02432"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515219","type":"PushEvent","actor":{"id":59730796,"login":"avbuben","display_login":"avbuben","gravatar_id":"","url":"https://api.github.com/users/avbuben","avatar_url":"https://avatars.githubusercontent.com/u/59730796?"},"repo":{"id":386308872,"name":"a-devops/ansible-postgresql-role","url":"https://api.github.com/repos/a-devops/ansible-postgresql-role"},"payload":{"push_id":7562055251,"size":2,"distinct_size":1,"ref":"refs/heads/master","head":"fbbdb8a7ff006efce63b7d5e59caf9b676ba6783","before":"cb3e6294c1a0d7a606f8fe87ccdf9dc17d7a3b1b","commits":[{"sha":"7416dd1f1a5bd012d3531c2ddb976eea6effa3f3","author":{"name":"Artur Buben","email":"853ff7c15ca2608fd8666e8315a14b21a726852e@admitad.com"},"message":"devops-2367 update role","distinct":false,"url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/commits/7416dd1f1a5bd012d3531c2ddb976eea6effa3f3"},{"sha":"fbbdb8a7ff006efce63b7d5e59caf9b676ba6783","author":{"name":"avbuben","email":"e14bbad8df6257e3228b2b59bf768b694e318bde@users.noreply.github.com"},"message":"Merge pull request #1 from a-devops/internal/devops-2367-update-role\n\ndevops-2367 update role","distinct":true,"url":"https://api.github.com/repos/a-devops/ansible-postgresql-role/commits/fbbdb8a7ff006efce63b7d5e59caf9b676ba6783"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":59089336,"login":"a-devops","gravatar_id":"","url":"https://api.github.com/orgs/a-devops","avatar_url":"https://avatars.githubusercontent.com/u/59089336?"}} +{"id":"17245515220","type":"CreateEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388040568,"name":"thatjohn01/753021085","url":"https://api.github.com/repos/thatjohn01/753021085"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":"西藏的孩子_PDF下载_鹰萨·罗布次仁著","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515236","type":"PushEvent","actor":{"id":8517910,"login":"LombiqBot","display_login":"LombiqBot","gravatar_id":"","url":"https://api.github.com/users/LombiqBot","avatar_url":"https://avatars.githubusercontent.com/u/8517910?"},"repo":{"id":49138417,"name":"Lombiq/Om.Orchard.SocialMetaTags","url":"https://api.github.com/repos/Lombiq/Om.Orchard.SocialMetaTags"},"payload":{"push_id":7562055260,"size":0,"distinct_size":0,"ref":"refs/heads/Orchard-1.10.Version","head":"305ee65dde62fcbda897fcff9ccce06fde76e5d8","before":"305ee65dde62fcbda897fcff9ccce06fde76e5d8","commits":[]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":8158177,"login":"Lombiq","gravatar_id":"","url":"https://api.github.com/orgs/Lombiq","avatar_url":"https://avatars.githubusercontent.com/u/8158177?"}} +{"id":"17245515246","type":"PullRequestEvent","actor":{"id":45119856,"login":"elephaint","display_login":"elephaint","gravatar_id":"","url":"https://api.github.com/users/elephaint","avatar_url":"https://avatars.githubusercontent.com/u/45119856?"},"repo":{"id":368168520,"name":"elephaint/pgbm","url":"https://api.github.com/repos/elephaint/pgbm"},"payload":{"action":"closed","number":2,"pull_request":{"url":"https://api.github.com/repos/elephaint/pgbm/pulls/2","id":687631920,"node_id":"MDExOlB1bGxSZXF1ZXN0Njg3NjMxOTIw","html_url":"https://github.com/elephaint/pgbm/pull/2","diff_url":"https://github.com/elephaint/pgbm/pull/2.diff","patch_url":"https://github.com/elephaint/pgbm/pull/2.patch","issue_url":"https://api.github.com/repos/elephaint/pgbm/issues/2","number":2,"state":"closed","locked":false,"title":"GitHub workflows for installing on Linux, Windows, and Mac with CPU only","user":{"login":"shubhaguha","id":8782581,"node_id":"MDQ6VXNlcjg3ODI1ODE=","avatar_url":"https://avatars.githubusercontent.com/u/8782581?v=4","gravatar_id":"","url":"https://api.github.com/users/shubhaguha","html_url":"https://github.com/shubhaguha","followers_url":"https://api.github.com/users/shubhaguha/followers","following_url":"https://api.github.com/users/shubhaguha/following{/other_user}","gists_url":"https://api.github.com/users/shubhaguha/gists{/gist_id}","starred_url":"https://api.github.com/users/shubhaguha/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shubhaguha/subscriptions","organizations_url":"https://api.github.com/users/shubhaguha/orgs","repos_url":"https://api.github.com/users/shubhaguha/repos","events_url":"https://api.github.com/users/shubhaguha/events{/privacy}","received_events_url":"https://api.github.com/users/shubhaguha/received_events","type":"User","site_admin":false},"body":"See project's \"Actions\" tab for some already successful runs: https://github.com/elephaint/pgbm/actions","created_at":"2021-07-12T08:39:37Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":"2021-07-21T08:00:02Z","merge_commit_sha":"d74fdc2777572de132b98d699005ee6e298b7cd4","assignee":null,"assignees":[],"requested_reviewers":[{"login":"sscdotopen","id":409707,"node_id":"MDQ6VXNlcjQwOTcwNw==","avatar_url":"https://avatars.githubusercontent.com/u/409707?v=4","gravatar_id":"","url":"https://api.github.com/users/sscdotopen","html_url":"https://github.com/sscdotopen","followers_url":"https://api.github.com/users/sscdotopen/followers","following_url":"https://api.github.com/users/sscdotopen/following{/other_user}","gists_url":"https://api.github.com/users/sscdotopen/gists{/gist_id}","starred_url":"https://api.github.com/users/sscdotopen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sscdotopen/subscriptions","organizations_url":"https://api.github.com/users/sscdotopen/orgs","repos_url":"https://api.github.com/users/sscdotopen/repos","events_url":"https://api.github.com/users/sscdotopen/events{/privacy}","received_events_url":"https://api.github.com/users/sscdotopen/received_events","type":"User","site_admin":false},{"login":"elephaint","id":45119856,"node_id":"MDQ6VXNlcjQ1MTE5ODU2","avatar_url":"https://avatars.githubusercontent.com/u/45119856?v=4","gravatar_id":"","url":"https://api.github.com/users/elephaint","html_url":"https://github.com/elephaint","followers_url":"https://api.github.com/users/elephaint/followers","following_url":"https://api.github.com/users/elephaint/following{/other_user}","gists_url":"https://api.github.com/users/elephaint/gists{/gist_id}","starred_url":"https://api.github.com/users/elephaint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elephaint/subscriptions","organizations_url":"https://api.github.com/users/elephaint/orgs","repos_url":"https://api.github.com/users/elephaint/repos","events_url":"https://api.github.com/users/elephaint/events{/privacy}","received_events_url":"https://api.github.com/users/elephaint/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/elephaint/pgbm/pulls/2/commits","review_comments_url":"https://api.github.com/repos/elephaint/pgbm/pulls/2/comments","review_comment_url":"https://api.github.com/repos/elephaint/pgbm/pulls/comments{/number}","comments_url":"https://api.github.com/repos/elephaint/pgbm/issues/2/comments","statuses_url":"https://api.github.com/repos/elephaint/pgbm/statuses/004f710484cf1300f2f1e92a358e187c143eed43","head":{"label":"elephaint:workflows","ref":"workflows","sha":"004f710484cf1300f2f1e92a358e187c143eed43","user":{"login":"elephaint","id":45119856,"node_id":"MDQ6VXNlcjQ1MTE5ODU2","avatar_url":"https://avatars.githubusercontent.com/u/45119856?v=4","gravatar_id":"","url":"https://api.github.com/users/elephaint","html_url":"https://github.com/elephaint","followers_url":"https://api.github.com/users/elephaint/followers","following_url":"https://api.github.com/users/elephaint/following{/other_user}","gists_url":"https://api.github.com/users/elephaint/gists{/gist_id}","starred_url":"https://api.github.com/users/elephaint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elephaint/subscriptions","organizations_url":"https://api.github.com/users/elephaint/orgs","repos_url":"https://api.github.com/users/elephaint/repos","events_url":"https://api.github.com/users/elephaint/events{/privacy}","received_events_url":"https://api.github.com/users/elephaint/received_events","type":"User","site_admin":false},"repo":{"id":368168520,"node_id":"MDEwOlJlcG9zaXRvcnkzNjgxNjg1MjA=","name":"pgbm","full_name":"elephaint/pgbm","private":false,"owner":{"login":"elephaint","id":45119856,"node_id":"MDQ6VXNlcjQ1MTE5ODU2","avatar_url":"https://avatars.githubusercontent.com/u/45119856?v=4","gravatar_id":"","url":"https://api.github.com/users/elephaint","html_url":"https://github.com/elephaint","followers_url":"https://api.github.com/users/elephaint/followers","following_url":"https://api.github.com/users/elephaint/following{/other_user}","gists_url":"https://api.github.com/users/elephaint/gists{/gist_id}","starred_url":"https://api.github.com/users/elephaint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elephaint/subscriptions","organizations_url":"https://api.github.com/users/elephaint/orgs","repos_url":"https://api.github.com/users/elephaint/repos","events_url":"https://api.github.com/users/elephaint/events{/privacy}","received_events_url":"https://api.github.com/users/elephaint/received_events","type":"User","site_admin":false},"html_url":"https://github.com/elephaint/pgbm","description":"Probabilistic Gradient Boosting Machines","fork":false,"url":"https://api.github.com/repos/elephaint/pgbm","forks_url":"https://api.github.com/repos/elephaint/pgbm/forks","keys_url":"https://api.github.com/repos/elephaint/pgbm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/elephaint/pgbm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/elephaint/pgbm/teams","hooks_url":"https://api.github.com/repos/elephaint/pgbm/hooks","issue_events_url":"https://api.github.com/repos/elephaint/pgbm/issues/events{/number}","events_url":"https://api.github.com/repos/elephaint/pgbm/events","assignees_url":"https://api.github.com/repos/elephaint/pgbm/assignees{/user}","branches_url":"https://api.github.com/repos/elephaint/pgbm/branches{/branch}","tags_url":"https://api.github.com/repos/elephaint/pgbm/tags","blobs_url":"https://api.github.com/repos/elephaint/pgbm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/elephaint/pgbm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/elephaint/pgbm/git/refs{/sha}","trees_url":"https://api.github.com/repos/elephaint/pgbm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/elephaint/pgbm/statuses/{sha}","languages_url":"https://api.github.com/repos/elephaint/pgbm/languages","stargazers_url":"https://api.github.com/repos/elephaint/pgbm/stargazers","contributors_url":"https://api.github.com/repos/elephaint/pgbm/contributors","subscribers_url":"https://api.github.com/repos/elephaint/pgbm/subscribers","subscription_url":"https://api.github.com/repos/elephaint/pgbm/subscription","commits_url":"https://api.github.com/repos/elephaint/pgbm/commits{/sha}","git_commits_url":"https://api.github.com/repos/elephaint/pgbm/git/commits{/sha}","comments_url":"https://api.github.com/repos/elephaint/pgbm/comments{/number}","issue_comment_url":"https://api.github.com/repos/elephaint/pgbm/issues/comments{/number}","contents_url":"https://api.github.com/repos/elephaint/pgbm/contents/{+path}","compare_url":"https://api.github.com/repos/elephaint/pgbm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/elephaint/pgbm/merges","archive_url":"https://api.github.com/repos/elephaint/pgbm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/elephaint/pgbm/downloads","issues_url":"https://api.github.com/repos/elephaint/pgbm/issues{/number}","pulls_url":"https://api.github.com/repos/elephaint/pgbm/pulls{/number}","milestones_url":"https://api.github.com/repos/elephaint/pgbm/milestones{/number}","notifications_url":"https://api.github.com/repos/elephaint/pgbm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/elephaint/pgbm/labels{/name}","releases_url":"https://api.github.com/repos/elephaint/pgbm/releases{/id}","deployments_url":"https://api.github.com/repos/elephaint/pgbm/deployments","created_at":"2021-05-17T11:57:25Z","updated_at":"2021-07-13T14:46:06Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/elephaint/pgbm.git","ssh_url":"git@github.com:elephaint/pgbm.git","clone_url":"https://github.com/elephaint/pgbm.git","svn_url":"https://github.com/elephaint/pgbm","homepage":null,"size":1059,"stargazers_count":51,"watchers_count":51,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":5,"open_issues":0,"watchers":51,"default_branch":"main"}},"base":{"label":"elephaint:main","ref":"main","sha":"82c79d35670ae91035e06285b0321455aff8fef9","user":{"login":"elephaint","id":45119856,"node_id":"MDQ6VXNlcjQ1MTE5ODU2","avatar_url":"https://avatars.githubusercontent.com/u/45119856?v=4","gravatar_id":"","url":"https://api.github.com/users/elephaint","html_url":"https://github.com/elephaint","followers_url":"https://api.github.com/users/elephaint/followers","following_url":"https://api.github.com/users/elephaint/following{/other_user}","gists_url":"https://api.github.com/users/elephaint/gists{/gist_id}","starred_url":"https://api.github.com/users/elephaint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elephaint/subscriptions","organizations_url":"https://api.github.com/users/elephaint/orgs","repos_url":"https://api.github.com/users/elephaint/repos","events_url":"https://api.github.com/users/elephaint/events{/privacy}","received_events_url":"https://api.github.com/users/elephaint/received_events","type":"User","site_admin":false},"repo":{"id":368168520,"node_id":"MDEwOlJlcG9zaXRvcnkzNjgxNjg1MjA=","name":"pgbm","full_name":"elephaint/pgbm","private":false,"owner":{"login":"elephaint","id":45119856,"node_id":"MDQ6VXNlcjQ1MTE5ODU2","avatar_url":"https://avatars.githubusercontent.com/u/45119856?v=4","gravatar_id":"","url":"https://api.github.com/users/elephaint","html_url":"https://github.com/elephaint","followers_url":"https://api.github.com/users/elephaint/followers","following_url":"https://api.github.com/users/elephaint/following{/other_user}","gists_url":"https://api.github.com/users/elephaint/gists{/gist_id}","starred_url":"https://api.github.com/users/elephaint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elephaint/subscriptions","organizations_url":"https://api.github.com/users/elephaint/orgs","repos_url":"https://api.github.com/users/elephaint/repos","events_url":"https://api.github.com/users/elephaint/events{/privacy}","received_events_url":"https://api.github.com/users/elephaint/received_events","type":"User","site_admin":false},"html_url":"https://github.com/elephaint/pgbm","description":"Probabilistic Gradient Boosting Machines","fork":false,"url":"https://api.github.com/repos/elephaint/pgbm","forks_url":"https://api.github.com/repos/elephaint/pgbm/forks","keys_url":"https://api.github.com/repos/elephaint/pgbm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/elephaint/pgbm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/elephaint/pgbm/teams","hooks_url":"https://api.github.com/repos/elephaint/pgbm/hooks","issue_events_url":"https://api.github.com/repos/elephaint/pgbm/issues/events{/number}","events_url":"https://api.github.com/repos/elephaint/pgbm/events","assignees_url":"https://api.github.com/repos/elephaint/pgbm/assignees{/user}","branches_url":"https://api.github.com/repos/elephaint/pgbm/branches{/branch}","tags_url":"https://api.github.com/repos/elephaint/pgbm/tags","blobs_url":"https://api.github.com/repos/elephaint/pgbm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/elephaint/pgbm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/elephaint/pgbm/git/refs{/sha}","trees_url":"https://api.github.com/repos/elephaint/pgbm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/elephaint/pgbm/statuses/{sha}","languages_url":"https://api.github.com/repos/elephaint/pgbm/languages","stargazers_url":"https://api.github.com/repos/elephaint/pgbm/stargazers","contributors_url":"https://api.github.com/repos/elephaint/pgbm/contributors","subscribers_url":"https://api.github.com/repos/elephaint/pgbm/subscribers","subscription_url":"https://api.github.com/repos/elephaint/pgbm/subscription","commits_url":"https://api.github.com/repos/elephaint/pgbm/commits{/sha}","git_commits_url":"https://api.github.com/repos/elephaint/pgbm/git/commits{/sha}","comments_url":"https://api.github.com/repos/elephaint/pgbm/comments{/number}","issue_comment_url":"https://api.github.com/repos/elephaint/pgbm/issues/comments{/number}","contents_url":"https://api.github.com/repos/elephaint/pgbm/contents/{+path}","compare_url":"https://api.github.com/repos/elephaint/pgbm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/elephaint/pgbm/merges","archive_url":"https://api.github.com/repos/elephaint/pgbm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/elephaint/pgbm/downloads","issues_url":"https://api.github.com/repos/elephaint/pgbm/issues{/number}","pulls_url":"https://api.github.com/repos/elephaint/pgbm/pulls{/number}","milestones_url":"https://api.github.com/repos/elephaint/pgbm/milestones{/number}","notifications_url":"https://api.github.com/repos/elephaint/pgbm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/elephaint/pgbm/labels{/name}","releases_url":"https://api.github.com/repos/elephaint/pgbm/releases{/id}","deployments_url":"https://api.github.com/repos/elephaint/pgbm/deployments","created_at":"2021-05-17T11:57:25Z","updated_at":"2021-07-13T14:46:06Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/elephaint/pgbm.git","ssh_url":"git@github.com:elephaint/pgbm.git","clone_url":"https://github.com/elephaint/pgbm.git","svn_url":"https://github.com/elephaint/pgbm","homepage":null,"size":1059,"stargazers_count":51,"watchers_count":51,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":5,"open_issues":0,"watchers":51,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/elephaint/pgbm/pulls/2"},"html":{"href":"https://github.com/elephaint/pgbm/pull/2"},"issue":{"href":"https://api.github.com/repos/elephaint/pgbm/issues/2"},"comments":{"href":"https://api.github.com/repos/elephaint/pgbm/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/elephaint/pgbm/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/elephaint/pgbm/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/elephaint/pgbm/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/elephaint/pgbm/statuses/004f710484cf1300f2f1e92a358e187c143eed43"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"elephaint","id":45119856,"node_id":"MDQ6VXNlcjQ1MTE5ODU2","avatar_url":"https://avatars.githubusercontent.com/u/45119856?v=4","gravatar_id":"","url":"https://api.github.com/users/elephaint","html_url":"https://github.com/elephaint","followers_url":"https://api.github.com/users/elephaint/followers","following_url":"https://api.github.com/users/elephaint/following{/other_user}","gists_url":"https://api.github.com/users/elephaint/gists{/gist_id}","starred_url":"https://api.github.com/users/elephaint/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elephaint/subscriptions","organizations_url":"https://api.github.com/users/elephaint/orgs","repos_url":"https://api.github.com/users/elephaint/repos","events_url":"https://api.github.com/users/elephaint/events{/privacy}","received_events_url":"https://api.github.com/users/elephaint/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":156,"deletions":0,"changed_files":3}},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515254","type":"PullRequestEvent","actor":{"id":813732,"login":"glasnt","display_login":"glasnt","gravatar_id":"","url":"https://api.github.com/users/glasnt","avatar_url":"https://avatars.githubusercontent.com/u/813732?"},"repo":{"id":387935031,"name":"glasnt/bookish-winner","url":"https://api.github.com/repos/glasnt/bookish-winner"},"payload":{"action":"closed","number":12,"pull_request":{"url":"https://api.github.com/repos/glasnt/bookish-winner/pulls/12","id":694131454,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTMxNDU0","html_url":"https://github.com/glasnt/bookish-winner/pull/12","diff_url":"https://github.com/glasnt/bookish-winner/pull/12.diff","patch_url":"https://github.com/glasnt/bookish-winner/pull/12.patch","issue_url":"https://api.github.com/repos/glasnt/bookish-winner/issues/12","number":12,"state":"closed","locked":false,"title":"Update app.py","user":{"login":"glasnt","id":813732,"node_id":"MDQ6VXNlcjgxMzczMg==","avatar_url":"https://avatars.githubusercontent.com/u/813732?v=4","gravatar_id":"","url":"https://api.github.com/users/glasnt","html_url":"https://github.com/glasnt","followers_url":"https://api.github.com/users/glasnt/followers","following_url":"https://api.github.com/users/glasnt/following{/other_user}","gists_url":"https://api.github.com/users/glasnt/gists{/gist_id}","starred_url":"https://api.github.com/users/glasnt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glasnt/subscriptions","organizations_url":"https://api.github.com/users/glasnt/orgs","repos_url":"https://api.github.com/users/glasnt/repos","events_url":"https://api.github.com/users/glasnt/events{/privacy}","received_events_url":"https://api.github.com/users/glasnt/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:43:38Z","updated_at":"2021-07-21T08:00:02Z","closed_at":"2021-07-21T08:00:02Z","merged_at":null,"merge_commit_sha":"e586fb4380189708c9499e6563932fb0ebb80217","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/glasnt/bookish-winner/pulls/12/commits","review_comments_url":"https://api.github.com/repos/glasnt/bookish-winner/pulls/12/comments","review_comment_url":"https://api.github.com/repos/glasnt/bookish-winner/pulls/comments{/number}","comments_url":"https://api.github.com/repos/glasnt/bookish-winner/issues/12/comments","statuses_url":"https://api.github.com/repos/glasnt/bookish-winner/statuses/8d70707b349a3915526354bf6044151b4cbd8692","head":{"label":"glasnt:glasnt-patch-11","ref":"glasnt-patch-11","sha":"8d70707b349a3915526354bf6044151b4cbd8692","user":{"login":"glasnt","id":813732,"node_id":"MDQ6VXNlcjgxMzczMg==","avatar_url":"https://avatars.githubusercontent.com/u/813732?v=4","gravatar_id":"","url":"https://api.github.com/users/glasnt","html_url":"https://github.com/glasnt","followers_url":"https://api.github.com/users/glasnt/followers","following_url":"https://api.github.com/users/glasnt/following{/other_user}","gists_url":"https://api.github.com/users/glasnt/gists{/gist_id}","starred_url":"https://api.github.com/users/glasnt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glasnt/subscriptions","organizations_url":"https://api.github.com/users/glasnt/orgs","repos_url":"https://api.github.com/users/glasnt/repos","events_url":"https://api.github.com/users/glasnt/events{/privacy}","received_events_url":"https://api.github.com/users/glasnt/received_events","type":"User","site_admin":false},"repo":{"id":387935031,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MzUwMzE=","name":"bookish-winner","full_name":"glasnt/bookish-winner","private":false,"owner":{"login":"glasnt","id":813732,"node_id":"MDQ6VXNlcjgxMzczMg==","avatar_url":"https://avatars.githubusercontent.com/u/813732?v=4","gravatar_id":"","url":"https://api.github.com/users/glasnt","html_url":"https://github.com/glasnt","followers_url":"https://api.github.com/users/glasnt/followers","following_url":"https://api.github.com/users/glasnt/following{/other_user}","gists_url":"https://api.github.com/users/glasnt/gists{/gist_id}","starred_url":"https://api.github.com/users/glasnt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glasnt/subscriptions","organizations_url":"https://api.github.com/users/glasnt/orgs","repos_url":"https://api.github.com/users/glasnt/repos","events_url":"https://api.github.com/users/glasnt/events{/privacy}","received_events_url":"https://api.github.com/users/glasnt/received_events","type":"User","site_admin":false},"html_url":"https://github.com/glasnt/bookish-winner","description":null,"fork":false,"url":"https://api.github.com/repos/glasnt/bookish-winner","forks_url":"https://api.github.com/repos/glasnt/bookish-winner/forks","keys_url":"https://api.github.com/repos/glasnt/bookish-winner/keys{/key_id}","collaborators_url":"https://api.github.com/repos/glasnt/bookish-winner/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/glasnt/bookish-winner/teams","hooks_url":"https://api.github.com/repos/glasnt/bookish-winner/hooks","issue_events_url":"https://api.github.com/repos/glasnt/bookish-winner/issues/events{/number}","events_url":"https://api.github.com/repos/glasnt/bookish-winner/events","assignees_url":"https://api.github.com/repos/glasnt/bookish-winner/assignees{/user}","branches_url":"https://api.github.com/repos/glasnt/bookish-winner/branches{/branch}","tags_url":"https://api.github.com/repos/glasnt/bookish-winner/tags","blobs_url":"https://api.github.com/repos/glasnt/bookish-winner/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/glasnt/bookish-winner/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/glasnt/bookish-winner/git/refs{/sha}","trees_url":"https://api.github.com/repos/glasnt/bookish-winner/git/trees{/sha}","statuses_url":"https://api.github.com/repos/glasnt/bookish-winner/statuses/{sha}","languages_url":"https://api.github.com/repos/glasnt/bookish-winner/languages","stargazers_url":"https://api.github.com/repos/glasnt/bookish-winner/stargazers","contributors_url":"https://api.github.com/repos/glasnt/bookish-winner/contributors","subscribers_url":"https://api.github.com/repos/glasnt/bookish-winner/subscribers","subscription_url":"https://api.github.com/repos/glasnt/bookish-winner/subscription","commits_url":"https://api.github.com/repos/glasnt/bookish-winner/commits{/sha}","git_commits_url":"https://api.github.com/repos/glasnt/bookish-winner/git/commits{/sha}","comments_url":"https://api.github.com/repos/glasnt/bookish-winner/comments{/number}","issue_comment_url":"https://api.github.com/repos/glasnt/bookish-winner/issues/comments{/number}","contents_url":"https://api.github.com/repos/glasnt/bookish-winner/contents/{+path}","compare_url":"https://api.github.com/repos/glasnt/bookish-winner/compare/{base}...{head}","merges_url":"https://api.github.com/repos/glasnt/bookish-winner/merges","archive_url":"https://api.github.com/repos/glasnt/bookish-winner/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/glasnt/bookish-winner/downloads","issues_url":"https://api.github.com/repos/glasnt/bookish-winner/issues{/number}","pulls_url":"https://api.github.com/repos/glasnt/bookish-winner/pulls{/number}","milestones_url":"https://api.github.com/repos/glasnt/bookish-winner/milestones{/number}","notifications_url":"https://api.github.com/repos/glasnt/bookish-winner/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/glasnt/bookish-winner/labels{/name}","releases_url":"https://api.github.com/repos/glasnt/bookish-winner/releases{/id}","deployments_url":"https://api.github.com/repos/glasnt/bookish-winner/deployments","created_at":"2021-07-20T23:12:03Z","updated_at":"2021-07-21T07:51:25Z","pushed_at":"2021-07-21T07:51:39Z","git_url":"git://github.com/glasnt/bookish-winner.git","ssh_url":"git@github.com:glasnt/bookish-winner.git","clone_url":"https://github.com/glasnt/bookish-winner.git","svn_url":"https://github.com/glasnt/bookish-winner","homepage":null,"size":33,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"base":{"label":"glasnt:main","ref":"main","sha":"d5a2f6d84ec2ce114e388da95b1dc07ba7243b1c","user":{"login":"glasnt","id":813732,"node_id":"MDQ6VXNlcjgxMzczMg==","avatar_url":"https://avatars.githubusercontent.com/u/813732?v=4","gravatar_id":"","url":"https://api.github.com/users/glasnt","html_url":"https://github.com/glasnt","followers_url":"https://api.github.com/users/glasnt/followers","following_url":"https://api.github.com/users/glasnt/following{/other_user}","gists_url":"https://api.github.com/users/glasnt/gists{/gist_id}","starred_url":"https://api.github.com/users/glasnt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glasnt/subscriptions","organizations_url":"https://api.github.com/users/glasnt/orgs","repos_url":"https://api.github.com/users/glasnt/repos","events_url":"https://api.github.com/users/glasnt/events{/privacy}","received_events_url":"https://api.github.com/users/glasnt/received_events","type":"User","site_admin":false},"repo":{"id":387935031,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MzUwMzE=","name":"bookish-winner","full_name":"glasnt/bookish-winner","private":false,"owner":{"login":"glasnt","id":813732,"node_id":"MDQ6VXNlcjgxMzczMg==","avatar_url":"https://avatars.githubusercontent.com/u/813732?v=4","gravatar_id":"","url":"https://api.github.com/users/glasnt","html_url":"https://github.com/glasnt","followers_url":"https://api.github.com/users/glasnt/followers","following_url":"https://api.github.com/users/glasnt/following{/other_user}","gists_url":"https://api.github.com/users/glasnt/gists{/gist_id}","starred_url":"https://api.github.com/users/glasnt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glasnt/subscriptions","organizations_url":"https://api.github.com/users/glasnt/orgs","repos_url":"https://api.github.com/users/glasnt/repos","events_url":"https://api.github.com/users/glasnt/events{/privacy}","received_events_url":"https://api.github.com/users/glasnt/received_events","type":"User","site_admin":false},"html_url":"https://github.com/glasnt/bookish-winner","description":null,"fork":false,"url":"https://api.github.com/repos/glasnt/bookish-winner","forks_url":"https://api.github.com/repos/glasnt/bookish-winner/forks","keys_url":"https://api.github.com/repos/glasnt/bookish-winner/keys{/key_id}","collaborators_url":"https://api.github.com/repos/glasnt/bookish-winner/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/glasnt/bookish-winner/teams","hooks_url":"https://api.github.com/repos/glasnt/bookish-winner/hooks","issue_events_url":"https://api.github.com/repos/glasnt/bookish-winner/issues/events{/number}","events_url":"https://api.github.com/repos/glasnt/bookish-winner/events","assignees_url":"https://api.github.com/repos/glasnt/bookish-winner/assignees{/user}","branches_url":"https://api.github.com/repos/glasnt/bookish-winner/branches{/branch}","tags_url":"https://api.github.com/repos/glasnt/bookish-winner/tags","blobs_url":"https://api.github.com/repos/glasnt/bookish-winner/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/glasnt/bookish-winner/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/glasnt/bookish-winner/git/refs{/sha}","trees_url":"https://api.github.com/repos/glasnt/bookish-winner/git/trees{/sha}","statuses_url":"https://api.github.com/repos/glasnt/bookish-winner/statuses/{sha}","languages_url":"https://api.github.com/repos/glasnt/bookish-winner/languages","stargazers_url":"https://api.github.com/repos/glasnt/bookish-winner/stargazers","contributors_url":"https://api.github.com/repos/glasnt/bookish-winner/contributors","subscribers_url":"https://api.github.com/repos/glasnt/bookish-winner/subscribers","subscription_url":"https://api.github.com/repos/glasnt/bookish-winner/subscription","commits_url":"https://api.github.com/repos/glasnt/bookish-winner/commits{/sha}","git_commits_url":"https://api.github.com/repos/glasnt/bookish-winner/git/commits{/sha}","comments_url":"https://api.github.com/repos/glasnt/bookish-winner/comments{/number}","issue_comment_url":"https://api.github.com/repos/glasnt/bookish-winner/issues/comments{/number}","contents_url":"https://api.github.com/repos/glasnt/bookish-winner/contents/{+path}","compare_url":"https://api.github.com/repos/glasnt/bookish-winner/compare/{base}...{head}","merges_url":"https://api.github.com/repos/glasnt/bookish-winner/merges","archive_url":"https://api.github.com/repos/glasnt/bookish-winner/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/glasnt/bookish-winner/downloads","issues_url":"https://api.github.com/repos/glasnt/bookish-winner/issues{/number}","pulls_url":"https://api.github.com/repos/glasnt/bookish-winner/pulls{/number}","milestones_url":"https://api.github.com/repos/glasnt/bookish-winner/milestones{/number}","notifications_url":"https://api.github.com/repos/glasnt/bookish-winner/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/glasnt/bookish-winner/labels{/name}","releases_url":"https://api.github.com/repos/glasnt/bookish-winner/releases{/id}","deployments_url":"https://api.github.com/repos/glasnt/bookish-winner/deployments","created_at":"2021-07-20T23:12:03Z","updated_at":"2021-07-21T07:51:25Z","pushed_at":"2021-07-21T07:51:39Z","git_url":"git://github.com/glasnt/bookish-winner.git","ssh_url":"git@github.com:glasnt/bookish-winner.git","clone_url":"https://github.com/glasnt/bookish-winner.git","svn_url":"https://github.com/glasnt/bookish-winner","homepage":null,"size":33,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/glasnt/bookish-winner/pulls/12"},"html":{"href":"https://github.com/glasnt/bookish-winner/pull/12"},"issue":{"href":"https://api.github.com/repos/glasnt/bookish-winner/issues/12"},"comments":{"href":"https://api.github.com/repos/glasnt/bookish-winner/issues/12/comments"},"review_comments":{"href":"https://api.github.com/repos/glasnt/bookish-winner/pulls/12/comments"},"review_comment":{"href":"https://api.github.com/repos/glasnt/bookish-winner/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/glasnt/bookish-winner/pulls/12/commits"},"statuses":{"href":"https://api.github.com/repos/glasnt/bookish-winner/statuses/8d70707b349a3915526354bf6044151b4cbd8692"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515256","type":"CreateEvent","actor":{"id":8182807,"login":"todor-ivanov","display_login":"todor-ivanov","gravatar_id":"","url":"https://api.github.com/users/todor-ivanov","avatar_url":"https://avatars.githubusercontent.com/u/8182807?"},"repo":{"id":313302929,"name":"todor-ivanov/CMSKubernetes","url":"https://api.github.com/repos/todor-ivanov/CMSKubernetes"},"payload":{"ref":"MSUnmerged_FixMissingParams","ref_type":"branch","master_branch":"master","description":"Set of instructions and examples to deploy CMS data-services to Kubernetes cluster","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515257","type":"PushEvent","actor":{"id":32797107,"login":"hemanthm4u","display_login":"hemanthm4u","gravatar_id":"","url":"https://api.github.com/users/hemanthm4u","avatar_url":"https://avatars.githubusercontent.com/u/32797107?"},"repo":{"id":386977462,"name":"hemanthm4u/newmaven","url":"https://api.github.com/repos/hemanthm4u/newmaven"},"payload":{"push_id":7562055276,"size":1,"distinct_size":1,"ref":"refs/heads/demo","head":"bd08dbd9c81439917a745a6134a9a4d4d1687dcb","before":"a6493e4e2e23311448b3a4c9507fb4a7d82600b7","commits":[{"sha":"bd08dbd9c81439917a745a6134a9a4d4d1687dcb","author":{"name":"hemanthm4u","email":"07bf1a12383834977fea5217a8c8773485f11a1b@users.noreply.github.com"},"message":"Update index.jsp","distinct":true,"url":"https://api.github.com/repos/hemanthm4u/newmaven/commits/bd08dbd9c81439917a745a6134a9a4d4d1687dcb"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515259","type":"CreateEvent","actor":{"id":46904574,"login":"flinkbot","display_login":"flinkbot","gravatar_id":"","url":"https://api.github.com/users/flinkbot","avatar_url":"https://avatars.githubusercontent.com/u/46904574?"},"repo":{"id":195830211,"name":"flink-ci/flink","url":"https://api.github.com/repos/flink-ci/flink"},"payload":{"ref":"ci_16531_82207c5b0f2f7b37e495040574c7d308d9a66b48","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":52584586,"login":"flink-ci","gravatar_id":"","url":"https://api.github.com/orgs/flink-ci","avatar_url":"https://avatars.githubusercontent.com/u/52584586?"}} +{"id":"17245515273","type":"PushEvent","actor":{"id":72529532,"login":"madelynarana","display_login":"madelynarana","gravatar_id":"","url":"https://api.github.com/users/madelynarana","avatar_url":"https://avatars.githubusercontent.com/u/72529532?"},"repo":{"id":388036698,"name":"madelynarana/cat_preloader","url":"https://api.github.com/repos/madelynarana/cat_preloader"},"payload":{"push_id":7562055246,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"6f9311c3a6edb3496003d05218cc89edfe12371a","before":"ab91b8a89d1b2a9e6ca3be2ae12eb8f4fb2cdbfd","commits":[{"sha":"6f9311c3a6edb3496003d05218cc89edfe12371a","author":{"name":"Madeleine","email":"554e6fa20ccc3e74776eca67b699508d06fc6169@gmail.com"},"message":":purple_heart: Animations.","distinct":true,"url":"https://api.github.com/repos/madelynarana/cat_preloader/commits/6f9311c3a6edb3496003d05218cc89edfe12371a"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515274","type":"PushEvent","actor":{"id":1354510,"login":"leo91000","display_login":"leo91000","gravatar_id":"","url":"https://api.github.com/users/leo91000","avatar_url":"https://avatars.githubusercontent.com/u/1354510?"},"repo":{"id":372306529,"name":"leo91000/covid-japan-informations","url":"https://api.github.com/repos/leo91000/covid-japan-informations"},"payload":{"push_id":7562055261,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"44f0883638c005b9767c8e90c2ebb43cc56b83b4","before":"df1c1006be1acc1f9ab8884d3139332215295226","commits":[{"sha":"44f0883638c005b9767c8e90c2ebb43cc56b83b4","author":{"name":"Léo Coletta","email":"c44abbda51c039fd3030f3ac7bea105b5e3f239d@gmail.com"},"message":"update 2021-07-21T08:00:02.225Z","distinct":true,"url":"https://api.github.com/repos/leo91000/covid-japan-informations/commits/44f0883638c005b9767c8e90c2ebb43cc56b83b4"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515276","type":"PushEvent","actor":{"id":86951355,"login":"cotxliang","display_login":"cotxliang","gravatar_id":"","url":"https://api.github.com/users/cotxliang","avatar_url":"https://avatars.githubusercontent.com/u/86951355?"},"repo":{"id":388038643,"name":"cotxliang/hotspot-app","url":"https://api.github.com/repos/cotxliang/hotspot-app"},"payload":{"push_id":7562055288,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"268462a8055d733ee2d3fb52e607c7d8df733576","before":"64081d16157ec166fc8283dca5e07ca78069473b","commits":[{"sha":"268462a8055d733ee2d3fb52e607c7d8df733576","author":{"name":"Liang Zhenwei","email":"174af0397a2703315e7903b8a2f46ac03e6025a5@users.noreply.github.com"},"message":"Create index.ts\n\nMy ID has been added to the original one. I don't know if it's suitable.","distinct":true,"url":"https://api.github.com/repos/cotxliang/hotspot-app/commits/268462a8055d733ee2d3fb52e607c7d8df733576"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515277","type":"PushEvent","actor":{"id":44251083,"login":"hangfaichao","display_login":"hangfaichao","gravatar_id":"","url":"https://api.github.com/users/hangfaichao","avatar_url":"https://avatars.githubusercontent.com/u/44251083?"},"repo":{"id":154464459,"name":"hangfaichao/hangfaichao.github.io","url":"https://api.github.com/repos/hangfaichao/hangfaichao.github.io"},"payload":{"push_id":7562055290,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"4198cddda652309449659d7aa9a6010c5cd8f620","before":"0f84258e06eb915e4367265f7a5d8b4a6037b188","commits":[{"sha":"4198cddda652309449659d7aa9a6010c5cd8f620","author":{"name":"wubidi","email":"e4630d3165e307420b2c56b281a186ad8b29eea1@pinduoduo.com"},"message":"Site updated: 2021-07-21 15:52:44","distinct":true,"url":"https://api.github.com/repos/hangfaichao/hangfaichao.github.io/commits/4198cddda652309449659d7aa9a6010c5cd8f620"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515282","type":"PullRequestEvent","actor":{"id":11852044,"login":"billypchan","display_login":"billypchan","gravatar_id":"","url":"https://api.github.com/users/billypchan","avatar_url":"https://avatars.githubusercontent.com/u/11852044?"},"repo":{"id":63856914,"name":"wireapp/wire-ios-link-preview","url":"https://api.github.com/repos/wireapp/wire-ios-link-preview"},"payload":{"action":"closed","number":81,"pull_request":{"url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/81","id":693671439,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNjcxNDM5","html_url":"https://github.com/wireapp/wire-ios-link-preview/pull/81","diff_url":"https://github.com/wireapp/wire-ios-link-preview/pull/81.diff","patch_url":"https://github.com/wireapp/wire-ios-link-preview/pull/81.patch","issue_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/81","number":81,"state":"closed","locked":false,"title":"chore: bump components","user":{"login":"github-actions[bot]","id":41898282,"node_id":"MDM6Qm90NDE4OTgyODI=","avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4","gravatar_id":"","url":"https://api.github.com/users/github-actions%5Bbot%5D","html_url":"https://github.com/apps/github-actions","followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers","following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos","events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bump framework(s) version for: wireapp/wire-ios-utilities","created_at":"2021-07-20T17:52:46Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:03Z","merged_at":null,"merge_commit_sha":"4f24ceec2283128132182199a82efaea038b5d43","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/81/commits","review_comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/81/comments","review_comment_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/comments{/number}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/81/comments","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/c7dbff67f9edccf2c77879888adb1095a43b7b96","head":{"label":"wireapp:chore/bump_2021-07-20-175229","ref":"chore/bump_2021-07-20-175229","sha":"c7dbff67f9edccf2c77879888adb1095a43b7b96","user":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"repo":{"id":63856914,"node_id":"MDEwOlJlcG9zaXRvcnk2Mzg1NjkxNA==","name":"wire-ios-link-preview","full_name":"wireapp/wire-ios-link-preview","private":false,"owner":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/wireapp/wire-ios-link-preview","description":"🏷 Open Graph data parser used to generate link previews","fork":false,"url":"https://api.github.com/repos/wireapp/wire-ios-link-preview","forks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/forks","keys_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/teams","hooks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/hooks","issue_events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/events{/number}","events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/events","assignees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/assignees{/user}","branches_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/branches{/branch}","tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/tags","blobs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/refs{/sha}","trees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/{sha}","languages_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/languages","stargazers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/stargazers","contributors_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contributors","subscribers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscribers","subscription_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscription","commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/commits{/sha}","git_commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/commits{/sha}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/comments{/number}","issue_comment_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/comments{/number}","contents_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contents/{+path}","compare_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/merges","archive_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/downloads","issues_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues{/number}","pulls_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls{/number}","milestones_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/milestones{/number}","notifications_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/labels{/name}","releases_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/releases{/id}","deployments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/deployments","created_at":"2016-07-21T09:40:14Z","updated_at":"2021-07-21T07:59:30Z","pushed_at":"2021-07-21T07:59:30Z","git_url":"git://github.com/wireapp/wire-ios-link-preview.git","ssh_url":"git@github.com:wireapp/wire-ios-link-preview.git","clone_url":"https://github.com/wireapp/wire-ios-link-preview.git","svn_url":"https://github.com/wireapp/wire-ios-link-preview","homepage":" https://wire.com ","size":1295,"stargazers_count":52,"watchers_count":52,"language":"Swift","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":11,"open_issues":4,"watchers":52,"default_branch":"develop"}},"base":{"label":"wireapp:develop","ref":"develop","sha":"dc8f49fb86e6a078579a176c7300920d12e9571a","user":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"repo":{"id":63856914,"node_id":"MDEwOlJlcG9zaXRvcnk2Mzg1NjkxNA==","name":"wire-ios-link-preview","full_name":"wireapp/wire-ios-link-preview","private":false,"owner":{"login":"wireapp","id":16047324,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDQ3MzI0","avatar_url":"https://avatars.githubusercontent.com/u/16047324?v=4","gravatar_id":"","url":"https://api.github.com/users/wireapp","html_url":"https://github.com/wireapp","followers_url":"https://api.github.com/users/wireapp/followers","following_url":"https://api.github.com/users/wireapp/following{/other_user}","gists_url":"https://api.github.com/users/wireapp/gists{/gist_id}","starred_url":"https://api.github.com/users/wireapp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wireapp/subscriptions","organizations_url":"https://api.github.com/users/wireapp/orgs","repos_url":"https://api.github.com/users/wireapp/repos","events_url":"https://api.github.com/users/wireapp/events{/privacy}","received_events_url":"https://api.github.com/users/wireapp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/wireapp/wire-ios-link-preview","description":"🏷 Open Graph data parser used to generate link previews","fork":false,"url":"https://api.github.com/repos/wireapp/wire-ios-link-preview","forks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/forks","keys_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/teams","hooks_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/hooks","issue_events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/events{/number}","events_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/events","assignees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/assignees{/user}","branches_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/branches{/branch}","tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/tags","blobs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/refs{/sha}","trees_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/{sha}","languages_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/languages","stargazers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/stargazers","contributors_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contributors","subscribers_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscribers","subscription_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/subscription","commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/commits{/sha}","git_commits_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/git/commits{/sha}","comments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/comments{/number}","issue_comment_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/comments{/number}","contents_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/contents/{+path}","compare_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/merges","archive_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/downloads","issues_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues{/number}","pulls_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls{/number}","milestones_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/milestones{/number}","notifications_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/labels{/name}","releases_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/releases{/id}","deployments_url":"https://api.github.com/repos/wireapp/wire-ios-link-preview/deployments","created_at":"2016-07-21T09:40:14Z","updated_at":"2021-07-21T07:59:30Z","pushed_at":"2021-07-21T07:59:30Z","git_url":"git://github.com/wireapp/wire-ios-link-preview.git","ssh_url":"git@github.com:wireapp/wire-ios-link-preview.git","clone_url":"https://github.com/wireapp/wire-ios-link-preview.git","svn_url":"https://github.com/wireapp/wire-ios-link-preview","homepage":" https://wire.com ","size":1295,"stargazers_count":52,"watchers_count":52,"language":"Swift","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":11,"open_issues":4,"watchers":52,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/81"},"html":{"href":"https://github.com/wireapp/wire-ios-link-preview/pull/81"},"issue":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/81"},"comments":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/issues/81/comments"},"review_comments":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/81/comments"},"review_comment":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/pulls/81/commits"},"statuses":{"href":"https://api.github.com/repos/wireapp/wire-ios-link-preview/statuses/c7dbff67f9edccf2c77879888adb1095a43b7b96"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":1,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":3,"deletions":3,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":16047324,"login":"wireapp","gravatar_id":"","url":"https://api.github.com/orgs/wireapp","avatar_url":"https://avatars.githubusercontent.com/u/16047324?"}} +{"id":"17245515287","type":"PullRequestEvent","actor":{"id":9197716,"login":"ChrisRG","display_login":"ChrisRG","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","avatar_url":"https://avatars.githubusercontent.com/u/9197716?"},"repo":{"id":387739023,"name":"ChrisRG/lewagon","url":"https://api.github.com/repos/ChrisRG/lewagon"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/ChrisRG/lewagon/pulls/1","id":694140513,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQwNTEz","html_url":"https://github.com/ChrisRG/lewagon/pull/1","diff_url":"https://github.com/ChrisRG/lewagon/pull/1.diff","patch_url":"https://github.com/ChrisRG/lewagon/pull/1.patch","issue_url":"https://api.github.com/repos/ChrisRG/lewagon/issues/1","number":1,"state":"closed","locked":false,"title":"finished signup logic","user":{"login":"MarcelSF","id":39573049,"node_id":"MDQ6VXNlcjM5NTczMDQ5","avatar_url":"https://avatars.githubusercontent.com/u/39573049?v=4","gravatar_id":"","url":"https://api.github.com/users/MarcelSF","html_url":"https://github.com/MarcelSF","followers_url":"https://api.github.com/users/MarcelSF/followers","following_url":"https://api.github.com/users/MarcelSF/following{/other_user}","gists_url":"https://api.github.com/users/MarcelSF/gists{/gist_id}","starred_url":"https://api.github.com/users/MarcelSF/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarcelSF/subscriptions","organizations_url":"https://api.github.com/users/MarcelSF/orgs","repos_url":"https://api.github.com/users/MarcelSF/repos","events_url":"https://api.github.com/users/MarcelSF/events{/privacy}","received_events_url":"https://api.github.com/users/MarcelSF/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T07:58:17Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:03Z","merged_at":"2021-07-21T08:00:03Z","merge_commit_sha":"802fb53c6b970df7d94dee03b772d8d0f1766f4f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/ChrisRG/lewagon/pulls/1/commits","review_comments_url":"https://api.github.com/repos/ChrisRG/lewagon/pulls/1/comments","review_comment_url":"https://api.github.com/repos/ChrisRG/lewagon/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ChrisRG/lewagon/issues/1/comments","statuses_url":"https://api.github.com/repos/ChrisRG/lewagon/statuses/345771f53451684968e72336b285dc8e37869eb1","head":{"label":"ChrisRG:sign-up-logic","ref":"sign-up-logic","sha":"345771f53451684968e72336b285dc8e37869eb1","user":{"login":"ChrisRG","id":9197716,"node_id":"MDQ6VXNlcjkxOTc3MTY=","avatar_url":"https://avatars.githubusercontent.com/u/9197716?v=4","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","html_url":"https://github.com/ChrisRG","followers_url":"https://api.github.com/users/ChrisRG/followers","following_url":"https://api.github.com/users/ChrisRG/following{/other_user}","gists_url":"https://api.github.com/users/ChrisRG/gists{/gist_id}","starred_url":"https://api.github.com/users/ChrisRG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChrisRG/subscriptions","organizations_url":"https://api.github.com/users/ChrisRG/orgs","repos_url":"https://api.github.com/users/ChrisRG/repos","events_url":"https://api.github.com/users/ChrisRG/events{/privacy}","received_events_url":"https://api.github.com/users/ChrisRG/received_events","type":"User","site_admin":false},"repo":{"id":387739023,"node_id":"MDEwOlJlcG9zaXRvcnkzODc3MzkwMjM=","name":"lewagon","full_name":"ChrisRG/lewagon","private":false,"owner":{"login":"ChrisRG","id":9197716,"node_id":"MDQ6VXNlcjkxOTc3MTY=","avatar_url":"https://avatars.githubusercontent.com/u/9197716?v=4","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","html_url":"https://github.com/ChrisRG","followers_url":"https://api.github.com/users/ChrisRG/followers","following_url":"https://api.github.com/users/ChrisRG/following{/other_user}","gists_url":"https://api.github.com/users/ChrisRG/gists{/gist_id}","starred_url":"https://api.github.com/users/ChrisRG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChrisRG/subscriptions","organizations_url":"https://api.github.com/users/ChrisRG/orgs","repos_url":"https://api.github.com/users/ChrisRG/repos","events_url":"https://api.github.com/users/ChrisRG/events{/privacy}","received_events_url":"https://api.github.com/users/ChrisRG/received_events","type":"User","site_admin":false},"html_url":"https://github.com/ChrisRG/lewagon","description":null,"fork":false,"url":"https://api.github.com/repos/ChrisRG/lewagon","forks_url":"https://api.github.com/repos/ChrisRG/lewagon/forks","keys_url":"https://api.github.com/repos/ChrisRG/lewagon/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ChrisRG/lewagon/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ChrisRG/lewagon/teams","hooks_url":"https://api.github.com/repos/ChrisRG/lewagon/hooks","issue_events_url":"https://api.github.com/repos/ChrisRG/lewagon/issues/events{/number}","events_url":"https://api.github.com/repos/ChrisRG/lewagon/events","assignees_url":"https://api.github.com/repos/ChrisRG/lewagon/assignees{/user}","branches_url":"https://api.github.com/repos/ChrisRG/lewagon/branches{/branch}","tags_url":"https://api.github.com/repos/ChrisRG/lewagon/tags","blobs_url":"https://api.github.com/repos/ChrisRG/lewagon/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ChrisRG/lewagon/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ChrisRG/lewagon/git/refs{/sha}","trees_url":"https://api.github.com/repos/ChrisRG/lewagon/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ChrisRG/lewagon/statuses/{sha}","languages_url":"https://api.github.com/repos/ChrisRG/lewagon/languages","stargazers_url":"https://api.github.com/repos/ChrisRG/lewagon/stargazers","contributors_url":"https://api.github.com/repos/ChrisRG/lewagon/contributors","subscribers_url":"https://api.github.com/repos/ChrisRG/lewagon/subscribers","subscription_url":"https://api.github.com/repos/ChrisRG/lewagon/subscription","commits_url":"https://api.github.com/repos/ChrisRG/lewagon/commits{/sha}","git_commits_url":"https://api.github.com/repos/ChrisRG/lewagon/git/commits{/sha}","comments_url":"https://api.github.com/repos/ChrisRG/lewagon/comments{/number}","issue_comment_url":"https://api.github.com/repos/ChrisRG/lewagon/issues/comments{/number}","contents_url":"https://api.github.com/repos/ChrisRG/lewagon/contents/{+path}","compare_url":"https://api.github.com/repos/ChrisRG/lewagon/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ChrisRG/lewagon/merges","archive_url":"https://api.github.com/repos/ChrisRG/lewagon/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ChrisRG/lewagon/downloads","issues_url":"https://api.github.com/repos/ChrisRG/lewagon/issues{/number}","pulls_url":"https://api.github.com/repos/ChrisRG/lewagon/pulls{/number}","milestones_url":"https://api.github.com/repos/ChrisRG/lewagon/milestones{/number}","notifications_url":"https://api.github.com/repos/ChrisRG/lewagon/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ChrisRG/lewagon/labels{/name}","releases_url":"https://api.github.com/repos/ChrisRG/lewagon/releases{/id}","deployments_url":"https://api.github.com/repos/ChrisRG/lewagon/deployments","created_at":"2021-07-20T09:23:45Z","updated_at":"2021-07-20T13:56:53Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/ChrisRG/lewagon.git","ssh_url":"git@github.com:ChrisRG/lewagon.git","clone_url":"https://github.com/ChrisRG/lewagon.git","svn_url":"https://github.com/ChrisRG/lewagon","homepage":null,"size":124,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"base":{"label":"ChrisRG:main","ref":"main","sha":"bdd0f038633581b1395b49cc1c2dd18ba666ea19","user":{"login":"ChrisRG","id":9197716,"node_id":"MDQ6VXNlcjkxOTc3MTY=","avatar_url":"https://avatars.githubusercontent.com/u/9197716?v=4","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","html_url":"https://github.com/ChrisRG","followers_url":"https://api.github.com/users/ChrisRG/followers","following_url":"https://api.github.com/users/ChrisRG/following{/other_user}","gists_url":"https://api.github.com/users/ChrisRG/gists{/gist_id}","starred_url":"https://api.github.com/users/ChrisRG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChrisRG/subscriptions","organizations_url":"https://api.github.com/users/ChrisRG/orgs","repos_url":"https://api.github.com/users/ChrisRG/repos","events_url":"https://api.github.com/users/ChrisRG/events{/privacy}","received_events_url":"https://api.github.com/users/ChrisRG/received_events","type":"User","site_admin":false},"repo":{"id":387739023,"node_id":"MDEwOlJlcG9zaXRvcnkzODc3MzkwMjM=","name":"lewagon","full_name":"ChrisRG/lewagon","private":false,"owner":{"login":"ChrisRG","id":9197716,"node_id":"MDQ6VXNlcjkxOTc3MTY=","avatar_url":"https://avatars.githubusercontent.com/u/9197716?v=4","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","html_url":"https://github.com/ChrisRG","followers_url":"https://api.github.com/users/ChrisRG/followers","following_url":"https://api.github.com/users/ChrisRG/following{/other_user}","gists_url":"https://api.github.com/users/ChrisRG/gists{/gist_id}","starred_url":"https://api.github.com/users/ChrisRG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChrisRG/subscriptions","organizations_url":"https://api.github.com/users/ChrisRG/orgs","repos_url":"https://api.github.com/users/ChrisRG/repos","events_url":"https://api.github.com/users/ChrisRG/events{/privacy}","received_events_url":"https://api.github.com/users/ChrisRG/received_events","type":"User","site_admin":false},"html_url":"https://github.com/ChrisRG/lewagon","description":null,"fork":false,"url":"https://api.github.com/repos/ChrisRG/lewagon","forks_url":"https://api.github.com/repos/ChrisRG/lewagon/forks","keys_url":"https://api.github.com/repos/ChrisRG/lewagon/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ChrisRG/lewagon/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ChrisRG/lewagon/teams","hooks_url":"https://api.github.com/repos/ChrisRG/lewagon/hooks","issue_events_url":"https://api.github.com/repos/ChrisRG/lewagon/issues/events{/number}","events_url":"https://api.github.com/repos/ChrisRG/lewagon/events","assignees_url":"https://api.github.com/repos/ChrisRG/lewagon/assignees{/user}","branches_url":"https://api.github.com/repos/ChrisRG/lewagon/branches{/branch}","tags_url":"https://api.github.com/repos/ChrisRG/lewagon/tags","blobs_url":"https://api.github.com/repos/ChrisRG/lewagon/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ChrisRG/lewagon/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ChrisRG/lewagon/git/refs{/sha}","trees_url":"https://api.github.com/repos/ChrisRG/lewagon/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ChrisRG/lewagon/statuses/{sha}","languages_url":"https://api.github.com/repos/ChrisRG/lewagon/languages","stargazers_url":"https://api.github.com/repos/ChrisRG/lewagon/stargazers","contributors_url":"https://api.github.com/repos/ChrisRG/lewagon/contributors","subscribers_url":"https://api.github.com/repos/ChrisRG/lewagon/subscribers","subscription_url":"https://api.github.com/repos/ChrisRG/lewagon/subscription","commits_url":"https://api.github.com/repos/ChrisRG/lewagon/commits{/sha}","git_commits_url":"https://api.github.com/repos/ChrisRG/lewagon/git/commits{/sha}","comments_url":"https://api.github.com/repos/ChrisRG/lewagon/comments{/number}","issue_comment_url":"https://api.github.com/repos/ChrisRG/lewagon/issues/comments{/number}","contents_url":"https://api.github.com/repos/ChrisRG/lewagon/contents/{+path}","compare_url":"https://api.github.com/repos/ChrisRG/lewagon/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ChrisRG/lewagon/merges","archive_url":"https://api.github.com/repos/ChrisRG/lewagon/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ChrisRG/lewagon/downloads","issues_url":"https://api.github.com/repos/ChrisRG/lewagon/issues{/number}","pulls_url":"https://api.github.com/repos/ChrisRG/lewagon/pulls{/number}","milestones_url":"https://api.github.com/repos/ChrisRG/lewagon/milestones{/number}","notifications_url":"https://api.github.com/repos/ChrisRG/lewagon/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ChrisRG/lewagon/labels{/name}","releases_url":"https://api.github.com/repos/ChrisRG/lewagon/releases{/id}","deployments_url":"https://api.github.com/repos/ChrisRG/lewagon/deployments","created_at":"2021-07-20T09:23:45Z","updated_at":"2021-07-20T13:56:53Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/ChrisRG/lewagon.git","ssh_url":"git@github.com:ChrisRG/lewagon.git","clone_url":"https://github.com/ChrisRG/lewagon.git","svn_url":"https://github.com/ChrisRG/lewagon","homepage":null,"size":124,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/ChrisRG/lewagon/pulls/1"},"html":{"href":"https://github.com/ChrisRG/lewagon/pull/1"},"issue":{"href":"https://api.github.com/repos/ChrisRG/lewagon/issues/1"},"comments":{"href":"https://api.github.com/repos/ChrisRG/lewagon/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/ChrisRG/lewagon/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/ChrisRG/lewagon/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ChrisRG/lewagon/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/ChrisRG/lewagon/statuses/345771f53451684968e72336b285dc8e37869eb1"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"ChrisRG","id":9197716,"node_id":"MDQ6VXNlcjkxOTc3MTY=","avatar_url":"https://avatars.githubusercontent.com/u/9197716?v=4","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","html_url":"https://github.com/ChrisRG","followers_url":"https://api.github.com/users/ChrisRG/followers","following_url":"https://api.github.com/users/ChrisRG/following{/other_user}","gists_url":"https://api.github.com/users/ChrisRG/gists{/gist_id}","starred_url":"https://api.github.com/users/ChrisRG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChrisRG/subscriptions","organizations_url":"https://api.github.com/users/ChrisRG/orgs","repos_url":"https://api.github.com/users/ChrisRG/repos","events_url":"https://api.github.com/users/ChrisRG/events{/privacy}","received_events_url":"https://api.github.com/users/ChrisRG/received_events","type":"User","site_admin":false},"comments":1,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":154,"deletions":9,"changed_files":7}},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515293","type":"PushEvent","actor":{"id":2687598,"login":"milesholt","display_login":"milesholt","gravatar_id":"","url":"https://api.github.com/users/milesholt","avatar_url":"https://avatars.githubusercontent.com/u/2687598?"},"repo":{"id":247480564,"name":"milesholt/autotrade1","url":"https://api.github.com/repos/milesholt/autotrade1"},"payload":{"push_id":7562055289,"size":1,"distinct_size":1,"ref":"refs/heads/version2","head":"6220697f7cb539c1750e2e933a0319994e3ae253","before":"aa3efb330607e0d9cd4e298455f1f22fc11f3791","commits":[{"sha":"6220697f7cb539c1750e2e933a0319994e3ae253","author":{"name":"milesholt","email":"1a73af9e7ae00182733b2292511b814be66f065f@milesholt.co.uk"},"message":"File updated - July 21, 2021 9:00 AM","distinct":true,"url":"https://api.github.com/repos/milesholt/autotrade1/commits/6220697f7cb539c1750e2e933a0319994e3ae253"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515295","type":"PushEvent","actor":{"id":67330171,"login":"tomas-laubr-smart-software","display_login":"tomas-laubr-smart-software","gravatar_id":"","url":"https://api.github.com/users/tomas-laubr-smart-software","avatar_url":"https://avatars.githubusercontent.com/u/67330171?"},"repo":{"id":387468472,"name":"soliteapay/spring-data-mongodb-datatables","url":"https://api.github.com/repos/soliteapay/spring-data-mongodb-datatables"},"payload":{"push_id":7562055281,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"a2f7de50913829407640dab5ea1afa17d54edde2","before":"29aee06c40920402944625279e933ab2e147c242","commits":[{"sha":"a2f7de50913829407640dab5ea1afa17d54edde2","author":{"name":"Tomáš Laubr","email":"78890522402760c642b263830372260c88c880da@smart-software.cz"},"message":"support multiple additional and prefitering criteria","distinct":true,"url":"https://api.github.com/repos/soliteapay/spring-data-mongodb-datatables/commits/a2f7de50913829407640dab5ea1afa17d54edde2"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":67330327,"login":"soliteapay","gravatar_id":"","url":"https://api.github.com/orgs/soliteapay","avatar_url":"https://avatars.githubusercontent.com/u/67330327?"}} +{"id":"17245515300","type":"PushEvent","actor":{"id":61073116,"login":"snehaa1989","display_login":"snehaa1989","gravatar_id":"","url":"https://api.github.com/users/snehaa1989","avatar_url":"https://avatars.githubusercontent.com/u/61073116?"},"repo":{"id":388038822,"name":"snehaa1989/imgbot","url":"https://api.github.com/repos/snehaa1989/imgbot"},"payload":{"push_id":7562055293,"size":2,"distinct_size":1,"ref":"refs/heads/main","head":"a084470ead9361c81f02c529a450eeed19a4d92f","before":"edd4c4146633ac025ba60678d5e6d10a3e15f89e","commits":[{"sha":"dcd80cbf80a3abb5039fcb49649af7a7c49b9b31","author":{"name":"ImgBotApp","email":"858471a511451575f9769176c7c5b94525007a21@gmail.com"},"message":"[ImgBot] Optimize images\n\n*Total -- 563.29kb -> 551.89kb (2.02%)\n\n/demo-image.png -- 392.53kb -> 384.14kb (2.14%)\n/lofi1.jpg -- 149.96kb -> 147.09kb (1.91%)\n/violin.jpg -- 20.80kb -> 20.66kb (0.69%)\n\nSigned-off-by: ImgBotApp ","distinct":false,"url":"https://api.github.com/repos/snehaa1989/imgbot/commits/dcd80cbf80a3abb5039fcb49649af7a7c49b9b31"},{"sha":"a084470ead9361c81f02c529a450eeed19a4d92f","author":{"name":"snehaa1989","email":"f580984500418b0c13e6eddfa1a4f8655e21f05f@users.noreply.github.com"},"message":"Merge pull request #1 from snehaa1989/imgbot\n\n[ImgBot] Optimize images","distinct":true,"url":"https://api.github.com/repos/snehaa1989/imgbot/commits/a084470ead9361c81f02c529a450eeed19a4d92f"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515305","type":"PullRequestEvent","actor":{"id":526307,"login":"endorama","display_login":"endorama","gravatar_id":"","url":"https://api.github.com/users/endorama","avatar_url":"https://avatars.githubusercontent.com/u/526307?"},"repo":{"id":16554739,"name":"elastic/beats","url":"https://api.github.com/repos/elastic/beats"},"payload":{"action":"closed","number":26932,"pull_request":{"url":"https://api.github.com/repos/elastic/beats/pulls/26932","id":691446799,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkxNDQ2Nzk5","html_url":"https://github.com/elastic/beats/pull/26932","diff_url":"https://github.com/elastic/beats/pull/26932.diff","patch_url":"https://github.com/elastic/beats/pull/26932.patch","issue_url":"https://api.github.com/repos/elastic/beats/issues/26932","number":26932,"state":"closed","locked":false,"title":"[7.x](backport #26870) [gcp/billing] always quote table name identifier","user":{"login":"mergify[bot]","id":37929162,"node_id":"MDM6Qm90Mzc5MjkxNjI=","avatar_url":"https://avatars.githubusercontent.com/in/10562?v=4","gravatar_id":"","url":"https://api.github.com/users/mergify%5Bbot%5D","html_url":"https://github.com/apps/mergify","followers_url":"https://api.github.com/users/mergify%5Bbot%5D/followers","following_url":"https://api.github.com/users/mergify%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/mergify%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/mergify%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mergify%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/mergify%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/mergify%5Bbot%5D/repos","events_url":"https://api.github.com/users/mergify%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/mergify%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"This is an automatic backport of pull request #26870 done by [Mergify](https://mergify.io).\n\n\n---\n\n\n
    \nMergify commands and options\n\n
    \n\nMore conditions and actions can be found in the [documentation](https://docs.mergify.io/).\n\nYou can also trigger Mergify actions by commenting on this pull request:\n\n- `@Mergifyio refresh` will re-evaluate the rules\n- `@Mergifyio rebase` will rebase this PR on its base branch\n- `@Mergifyio update` will merge the base branch into this PR\n- `@Mergifyio backport ` will backport this PR on `` branch\n\nAdditionally, on Mergify [dashboard](https://dashboard.mergify.io/) you can:\n\n- look at your merge queues\n- generate the Mergify configuration with the config editor.\n\nFinally, you can contact us on https://mergify.io/\n
    \n","created_at":"2021-07-16T12:19:27Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:02Z","merged_at":"2021-07-21T08:00:02Z","merge_commit_sha":"9f0cc3a673a75248ad7d7e0005c0c5303fbae609","assignee":{"login":"endorama","id":526307,"node_id":"MDQ6VXNlcjUyNjMwNw==","avatar_url":"https://avatars.githubusercontent.com/u/526307?v=4","gravatar_id":"","url":"https://api.github.com/users/endorama","html_url":"https://github.com/endorama","followers_url":"https://api.github.com/users/endorama/followers","following_url":"https://api.github.com/users/endorama/following{/other_user}","gists_url":"https://api.github.com/users/endorama/gists{/gist_id}","starred_url":"https://api.github.com/users/endorama/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/endorama/subscriptions","organizations_url":"https://api.github.com/users/endorama/orgs","repos_url":"https://api.github.com/users/endorama/repos","events_url":"https://api.github.com/users/endorama/events{/privacy}","received_events_url":"https://api.github.com/users/endorama/received_events","type":"User","site_admin":false},"assignees":[{"login":"endorama","id":526307,"node_id":"MDQ6VXNlcjUyNjMwNw==","avatar_url":"https://avatars.githubusercontent.com/u/526307?v=4","gravatar_id":"","url":"https://api.github.com/users/endorama","html_url":"https://github.com/endorama","followers_url":"https://api.github.com/users/endorama/followers","following_url":"https://api.github.com/users/endorama/following{/other_user}","gists_url":"https://api.github.com/users/endorama/gists{/gist_id}","starred_url":"https://api.github.com/users/endorama/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/endorama/subscriptions","organizations_url":"https://api.github.com/users/endorama/orgs","repos_url":"https://api.github.com/users/endorama/repos","events_url":"https://api.github.com/users/endorama/events{/privacy}","received_events_url":"https://api.github.com/users/endorama/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":1135227315,"node_id":"MDU6TGFiZWwxMTM1MjI3MzE1","url":"https://api.github.com/repos/elastic/beats/labels/Team:Integrations","name":"Team:Integrations","color":"1d76db","default":false,"description":"Label for the Integrations team"},{"id":306831030,"node_id":"MDU6TGFiZWwzMDY4MzEwMzA=","url":"https://api.github.com/repos/elastic/beats/labels/backport","name":"backport","color":"e99695","default":false,"description":null},{"id":2974730535,"node_id":"MDU6TGFiZWwyOTc0NzMwNTM1","url":"https://api.github.com/repos/elastic/beats/labels/v7.15.0","name":"v7.15.0","color":"ededed","default":false,"description":null}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/elastic/beats/pulls/26932/commits","review_comments_url":"https://api.github.com/repos/elastic/beats/pulls/26932/comments","review_comment_url":"https://api.github.com/repos/elastic/beats/pulls/comments{/number}","comments_url":"https://api.github.com/repos/elastic/beats/issues/26932/comments","statuses_url":"https://api.github.com/repos/elastic/beats/statuses/97f57284c05ff6f347ee3c7cb57c15c11e9e63a8","head":{"label":"elastic:mergify/bp/7.x/pr-26870","ref":"mergify/bp/7.x/pr-26870","sha":"97f57284c05ff6f347ee3c7cb57c15c11e9e63a8","user":{"login":"elastic","id":6764390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NjQzOTA=","avatar_url":"https://avatars.githubusercontent.com/u/6764390?v=4","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"repo":{"id":16554739,"node_id":"MDEwOlJlcG9zaXRvcnkxNjU1NDczOQ==","name":"beats","full_name":"elastic/beats","private":false,"owner":{"login":"elastic","id":6764390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NjQzOTA=","avatar_url":"https://avatars.githubusercontent.com/u/6764390?v=4","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/elastic/beats","description":":tropical_fish: Beats - Lightweight shippers for Elasticsearch & Logstash ","fork":false,"url":"https://api.github.com/repos/elastic/beats","forks_url":"https://api.github.com/repos/elastic/beats/forks","keys_url":"https://api.github.com/repos/elastic/beats/keys{/key_id}","collaborators_url":"https://api.github.com/repos/elastic/beats/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/elastic/beats/teams","hooks_url":"https://api.github.com/repos/elastic/beats/hooks","issue_events_url":"https://api.github.com/repos/elastic/beats/issues/events{/number}","events_url":"https://api.github.com/repos/elastic/beats/events","assignees_url":"https://api.github.com/repos/elastic/beats/assignees{/user}","branches_url":"https://api.github.com/repos/elastic/beats/branches{/branch}","tags_url":"https://api.github.com/repos/elastic/beats/tags","blobs_url":"https://api.github.com/repos/elastic/beats/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/elastic/beats/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/elastic/beats/git/refs{/sha}","trees_url":"https://api.github.com/repos/elastic/beats/git/trees{/sha}","statuses_url":"https://api.github.com/repos/elastic/beats/statuses/{sha}","languages_url":"https://api.github.com/repos/elastic/beats/languages","stargazers_url":"https://api.github.com/repos/elastic/beats/stargazers","contributors_url":"https://api.github.com/repos/elastic/beats/contributors","subscribers_url":"https://api.github.com/repos/elastic/beats/subscribers","subscription_url":"https://api.github.com/repos/elastic/beats/subscription","commits_url":"https://api.github.com/repos/elastic/beats/commits{/sha}","git_commits_url":"https://api.github.com/repos/elastic/beats/git/commits{/sha}","comments_url":"https://api.github.com/repos/elastic/beats/comments{/number}","issue_comment_url":"https://api.github.com/repos/elastic/beats/issues/comments{/number}","contents_url":"https://api.github.com/repos/elastic/beats/contents/{+path}","compare_url":"https://api.github.com/repos/elastic/beats/compare/{base}...{head}","merges_url":"https://api.github.com/repos/elastic/beats/merges","archive_url":"https://api.github.com/repos/elastic/beats/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/elastic/beats/downloads","issues_url":"https://api.github.com/repos/elastic/beats/issues{/number}","pulls_url":"https://api.github.com/repos/elastic/beats/pulls{/number}","milestones_url":"https://api.github.com/repos/elastic/beats/milestones{/number}","notifications_url":"https://api.github.com/repos/elastic/beats/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/elastic/beats/labels{/name}","releases_url":"https://api.github.com/repos/elastic/beats/releases{/id}","deployments_url":"https://api.github.com/repos/elastic/beats/deployments","created_at":"2014-02-05T19:01:02Z","updated_at":"2021-07-21T07:11:22Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/elastic/beats.git","ssh_url":"git@github.com:elastic/beats.git","clone_url":"https://github.com/elastic/beats.git","svn_url":"https://github.com/elastic/beats","homepage":"https://www.elastic.co/products/beats","size":335922,"stargazers_count":10120,"watchers_count":10120,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":4004,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1621,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":4004,"open_issues":1621,"watchers":10120,"default_branch":"master"}},"base":{"label":"elastic:7.x","ref":"7.x","sha":"61b3b739eb13766111046863f8faa3c18efde8f5","user":{"login":"elastic","id":6764390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NjQzOTA=","avatar_url":"https://avatars.githubusercontent.com/u/6764390?v=4","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"repo":{"id":16554739,"node_id":"MDEwOlJlcG9zaXRvcnkxNjU1NDczOQ==","name":"beats","full_name":"elastic/beats","private":false,"owner":{"login":"elastic","id":6764390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NjQzOTA=","avatar_url":"https://avatars.githubusercontent.com/u/6764390?v=4","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/elastic/beats","description":":tropical_fish: Beats - Lightweight shippers for Elasticsearch & Logstash ","fork":false,"url":"https://api.github.com/repos/elastic/beats","forks_url":"https://api.github.com/repos/elastic/beats/forks","keys_url":"https://api.github.com/repos/elastic/beats/keys{/key_id}","collaborators_url":"https://api.github.com/repos/elastic/beats/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/elastic/beats/teams","hooks_url":"https://api.github.com/repos/elastic/beats/hooks","issue_events_url":"https://api.github.com/repos/elastic/beats/issues/events{/number}","events_url":"https://api.github.com/repos/elastic/beats/events","assignees_url":"https://api.github.com/repos/elastic/beats/assignees{/user}","branches_url":"https://api.github.com/repos/elastic/beats/branches{/branch}","tags_url":"https://api.github.com/repos/elastic/beats/tags","blobs_url":"https://api.github.com/repos/elastic/beats/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/elastic/beats/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/elastic/beats/git/refs{/sha}","trees_url":"https://api.github.com/repos/elastic/beats/git/trees{/sha}","statuses_url":"https://api.github.com/repos/elastic/beats/statuses/{sha}","languages_url":"https://api.github.com/repos/elastic/beats/languages","stargazers_url":"https://api.github.com/repos/elastic/beats/stargazers","contributors_url":"https://api.github.com/repos/elastic/beats/contributors","subscribers_url":"https://api.github.com/repos/elastic/beats/subscribers","subscription_url":"https://api.github.com/repos/elastic/beats/subscription","commits_url":"https://api.github.com/repos/elastic/beats/commits{/sha}","git_commits_url":"https://api.github.com/repos/elastic/beats/git/commits{/sha}","comments_url":"https://api.github.com/repos/elastic/beats/comments{/number}","issue_comment_url":"https://api.github.com/repos/elastic/beats/issues/comments{/number}","contents_url":"https://api.github.com/repos/elastic/beats/contents/{+path}","compare_url":"https://api.github.com/repos/elastic/beats/compare/{base}...{head}","merges_url":"https://api.github.com/repos/elastic/beats/merges","archive_url":"https://api.github.com/repos/elastic/beats/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/elastic/beats/downloads","issues_url":"https://api.github.com/repos/elastic/beats/issues{/number}","pulls_url":"https://api.github.com/repos/elastic/beats/pulls{/number}","milestones_url":"https://api.github.com/repos/elastic/beats/milestones{/number}","notifications_url":"https://api.github.com/repos/elastic/beats/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/elastic/beats/labels{/name}","releases_url":"https://api.github.com/repos/elastic/beats/releases{/id}","deployments_url":"https://api.github.com/repos/elastic/beats/deployments","created_at":"2014-02-05T19:01:02Z","updated_at":"2021-07-21T07:11:22Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/elastic/beats.git","ssh_url":"git@github.com:elastic/beats.git","clone_url":"https://github.com/elastic/beats.git","svn_url":"https://github.com/elastic/beats","homepage":"https://www.elastic.co/products/beats","size":335922,"stargazers_count":10120,"watchers_count":10120,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":4004,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1621,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":4004,"open_issues":1621,"watchers":10120,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/elastic/beats/pulls/26932"},"html":{"href":"https://github.com/elastic/beats/pull/26932"},"issue":{"href":"https://api.github.com/repos/elastic/beats/issues/26932"},"comments":{"href":"https://api.github.com/repos/elastic/beats/issues/26932/comments"},"review_comments":{"href":"https://api.github.com/repos/elastic/beats/pulls/26932/comments"},"review_comment":{"href":"https://api.github.com/repos/elastic/beats/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/elastic/beats/pulls/26932/commits"},"statuses":{"href":"https://api.github.com/repos/elastic/beats/statuses/97f57284c05ff6f347ee3c7cb57c15c11e9e63a8"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"endorama","id":526307,"node_id":"MDQ6VXNlcjUyNjMwNw==","avatar_url":"https://avatars.githubusercontent.com/u/526307?v=4","gravatar_id":"","url":"https://api.github.com/users/endorama","html_url":"https://github.com/endorama","followers_url":"https://api.github.com/users/endorama/followers","following_url":"https://api.github.com/users/endorama/following{/other_user}","gists_url":"https://api.github.com/users/endorama/gists{/gist_id}","starred_url":"https://api.github.com/users/endorama/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/endorama/subscriptions","organizations_url":"https://api.github.com/users/endorama/orgs","repos_url":"https://api.github.com/users/endorama/repos","events_url":"https://api.github.com/users/endorama/events{/privacy}","received_events_url":"https://api.github.com/users/endorama/received_events","type":"User","site_admin":false},"comments":6,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":47,"deletions":17,"changed_files":3}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":6764390,"login":"elastic","gravatar_id":"","url":"https://api.github.com/orgs/elastic","avatar_url":"https://avatars.githubusercontent.com/u/6764390?"}} +{"id":"17245515315","type":"IssueCommentEvent","actor":{"id":4973094,"login":"kmilos","display_login":"kmilos","gravatar_id":"","url":"https://api.github.com/users/kmilos","avatar_url":"https://avatars.githubusercontent.com/u/4973094?"},"repo":{"id":3791835,"name":"darktable-org/darktable","url":"https://api.github.com/repos/darktable-org/darktable"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/darktable-org/darktable/issues/9581","repository_url":"https://api.github.com/repos/darktable-org/darktable","labels_url":"https://api.github.com/repos/darktable-org/darktable/issues/9581/labels{/name}","comments_url":"https://api.github.com/repos/darktable-org/darktable/issues/9581/comments","events_url":"https://api.github.com/repos/darktable-org/darktable/issues/9581/events","html_url":"https://github.com/darktable-org/darktable/issues/9581","id":949172676,"node_id":"MDU6SXNzdWU5NDkxNzI2NzY=","number":9581,"title":"Nikon D780 Tethering does not work","user":{"login":"duelle","id":7212186,"node_id":"MDQ6VXNlcjcyMTIxODY=","avatar_url":"https://avatars.githubusercontent.com/u/7212186?v=4","gravatar_id":"","url":"https://api.github.com/users/duelle","html_url":"https://github.com/duelle","followers_url":"https://api.github.com/users/duelle/followers","following_url":"https://api.github.com/users/duelle/following{/other_user}","gists_url":"https://api.github.com/users/duelle/gists{/gist_id}","starred_url":"https://api.github.com/users/duelle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/duelle/subscriptions","organizations_url":"https://api.github.com/users/duelle/orgs","repos_url":"https://api.github.com/users/duelle/repos","events_url":"https://api.github.com/users/duelle/events{/privacy}","received_events_url":"https://api.github.com/users/duelle/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-20T22:55:09Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"Even though the troubleshooting steps (using `gphoto2`) seem to work perfectly fine, it is not possible to use tethering with `darktable`. The option to use tethering is not available in the _import_ module and selecting the tethering view results in the error message `no camera with tethering support available for use...`.\r\n\r\n**To Reproduce**\r\n1. Connect Nikon D780 with PC via USB cable \r\n2. Turn on Nikon D780\r\n3. Run `darktable`\r\n4. Check `import` module (only `mount camera` option available)\r\n5. Open `tethering` view (see message: _no camera with tethering support available for use..._)\r\n\r\n**Expected behavior**\r\n- Allow tethering with Nikon D780 as it seems to be supported by `gphoto2`\r\n- See `switch to tethered shooting` option in `import` module\r\n- Availability of actual tethererd shooting in the `tethering` view\r\n\r\n\r\n**Platform**\r\n* darktable version : 3.6.0\r\n* OS : Linux Kernel 5.12.15-arch1-1\r\n* Linux - Distro : ArchLinux\r\n* Memory : 16 GB\r\n* Graphics card : NVidia GeForce RTX 2060\r\n* Graphics driver : \r\n* OpenCL installed : yes\r\n* OpenCL activated : yes\r\n* Xorg : 1.20.12-1\r\n* Desktop : i3wm\r\n\r\n\r\n**Additional context**\r\n- I was able to *successfully* use tethering via `gphoto2` in the troubleshooting process as provided here: https://www.darktable.org/usermanual/en/tethering/troubleshooting/\r\n- Nonetheless, tethering with `darktable` seems not to work.\r\n\r\n**Further information**\r\nAs instructed in the troubleshooting section for tethering, I logged the output of `darktable -d camctl 2>1 >camctl.log` which is the following:\r\n```\r\n% cat camctl.log \r\n0.803433 [camera_control] creating new context 0x5636e41833a0\r\n0.823962 [camera_control] loaded 2562 camera drivers.\r\n1.344988 [camera_control] loaded 38 port drivers.\r\n1.358053 [camera_control] 1 cameras connected\r\n1.358062 [camera_control] found new Nikon DSC D780 on port usb:001,018\r\n18.097786 [camera_control] destroy darktable camcontrol\r\n```\r\n\r\n**Remarks**\r\nIn case there is any further information missing that could be of help for resolving this issue just let me know. I'll happily try to support to fix this to the best of my knowledge and abilities.","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/darktable-org/darktable/issues/comments/883977128","html_url":"https://github.com/darktable-org/darktable/issues/9581#issuecomment-883977128","issue_url":"https://api.github.com/repos/darktable-org/darktable/issues/9581","id":883977128,"node_id":"IC_kwDOADnb2840sGuo","user":{"login":"kmilos","id":4973094,"node_id":"MDQ6VXNlcjQ5NzMwOTQ=","avatar_url":"https://avatars.githubusercontent.com/u/4973094?v=4","gravatar_id":"","url":"https://api.github.com/users/kmilos","html_url":"https://github.com/kmilos","followers_url":"https://api.github.com/users/kmilos/followers","following_url":"https://api.github.com/users/kmilos/following{/other_user}","gists_url":"https://api.github.com/users/kmilos/gists{/gist_id}","starred_url":"https://api.github.com/users/kmilos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kmilos/subscriptions","organizations_url":"https://api.github.com/users/kmilos/orgs","repos_url":"https://api.github.com/users/kmilos/repos","events_url":"https://api.github.com/users/kmilos/events{/privacy}","received_events_url":"https://api.github.com/users/kmilos/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:03Z","updated_at":"2021-07-21T08:00:03Z","author_association":"CONTRIBUTOR","body":"@ptilopteri D780 is supported at the most basic level, otherwise it wouldn't be in the list at all.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":1561544,"login":"darktable-org","gravatar_id":"","url":"https://api.github.com/orgs/darktable-org","avatar_url":"https://avatars.githubusercontent.com/u/1561544?"}} +{"id":"17245515318","type":"PushEvent","actor":{"id":7659712,"login":"zenkins","display_login":"zenkins","gravatar_id":"","url":"https://api.github.com/users/zenkins","avatar_url":"https://avatars.githubusercontent.com/u/7659712?"},"repo":{"id":53574465,"name":"wireapp/wire-ios-cryptobox","url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox"},"payload":{"push_id":7562055301,"size":1,"distinct_size":1,"ref":"refs/heads/develop","head":"f07ec3becc5f1b61a2f5312b1776fe526d25e678","before":"fae8d887d8f43e12b46ae7ee22c3365b284e59db","commits":[{"sha":"f07ec3becc5f1b61a2f5312b1776fe526d25e678","author":{"name":"github-actions[bot]","email":"17973dcf2e10b67f63417c88ca4c460b2c9439f2@users.noreply.github.com"},"message":"bump components (#59)\n\nCo-authored-by: runner ","distinct":true,"url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox/commits/f07ec3becc5f1b61a2f5312b1776fe526d25e678"}]},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":16047324,"login":"wireapp","gravatar_id":"","url":"https://api.github.com/orgs/wireapp","avatar_url":"https://avatars.githubusercontent.com/u/16047324?"}} +{"id":"17245515321","type":"PushEvent","actor":{"id":52447250,"login":"hualongfeng","display_login":"hualongfeng","gravatar_id":"","url":"https://api.github.com/users/hualongfeng","avatar_url":"https://avatars.githubusercontent.com/u/52447250?"},"repo":{"id":198813393,"name":"hualongfeng/ceph","url":"https://api.github.com/repos/hualongfeng/ceph"},"payload":{"push_id":7562055287,"size":1,"distinct_size":1,"ref":"refs/heads/teuthology","head":"7c9d134016e907f18e221570bc1b6a87c806214d","before":"07a5aead483d9580157411a07802b2987fcd9b83","commits":[{"sha":"7c9d134016e907f18e221570bc1b6a87c806214d","author":{"name":"Feng Hualong","email":"e5b342ebb6e7e1eeaf2a20301724c9c39bbce28c@intel.com"},"message":"alone\n\nSigned-off-by: Feng Hualong ","distinct":true,"url":"https://api.github.com/repos/hualongfeng/ceph/commits/7c9d134016e907f18e221570bc1b6a87c806214d"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515348","type":"PullRequestReviewEvent","actor":{"id":2869758,"login":"codingpotato","display_login":"codingpotato","gravatar_id":"","url":"https://api.github.com/users/codingpotato","avatar_url":"https://avatars.githubusercontent.com/u/2869758?"},"repo":{"id":376259206,"name":"aha-001/SeedLang","url":"https://api.github.com/repos/aha-001/SeedLang"},"payload":{"action":"created","review":{"id":711364924,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzY0OTI0","user":{"login":"codingpotato","id":2869758,"node_id":"MDQ6VXNlcjI4Njk3NTg=","avatar_url":"https://avatars.githubusercontent.com/u/2869758?v=4","gravatar_id":"","url":"https://api.github.com/users/codingpotato","html_url":"https://github.com/codingpotato","followers_url":"https://api.github.com/users/codingpotato/followers","following_url":"https://api.github.com/users/codingpotato/following{/other_user}","gists_url":"https://api.github.com/users/codingpotato/gists{/gist_id}","starred_url":"https://api.github.com/users/codingpotato/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codingpotato/subscriptions","organizations_url":"https://api.github.com/users/codingpotato/orgs","repos_url":"https://api.github.com/users/codingpotato/repos","events_url":"https://api.github.com/users/codingpotato/events{/privacy}","received_events_url":"https://api.github.com/users/codingpotato/received_events","type":"User","site_admin":false},"body":"LGTM","commit_id":"7669a8bbbda63d6420e8efc1c623a2be5e9a1574","submitted_at":"2021-07-21T08:00:03Z","state":"approved","html_url":"https://github.com/aha-001/SeedLang/pull/28#pullrequestreview-711364924","pull_request_url":"https://api.github.com/repos/aha-001/SeedLang/pulls/28","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/aha-001/SeedLang/pull/28#pullrequestreview-711364924"},"pull_request":{"href":"https://api.github.com/repos/aha-001/SeedLang/pulls/28"}}},"pull_request":{"url":"https://api.github.com/repos/aha-001/SeedLang/pulls/28","id":693975056,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzOTc1MDU2","html_url":"https://github.com/aha-001/SeedLang/pull/28","diff_url":"https://github.com/aha-001/SeedLang/pull/28.diff","patch_url":"https://github.com/aha-001/SeedLang/pull/28.patch","issue_url":"https://api.github.com/repos/aha-001/SeedLang/issues/28","number":28,"state":"open","locked":false,"title":"Add Windows batch for the ANTLR generator","user":{"login":"wixette","id":5819968,"node_id":"MDQ6VXNlcjU4MTk5Njg=","avatar_url":"https://avatars.githubusercontent.com/u/5819968?v=4","gravatar_id":"","url":"https://api.github.com/users/wixette","html_url":"https://github.com/wixette","followers_url":"https://api.github.com/users/wixette/followers","following_url":"https://api.github.com/users/wixette/following{/other_user}","gists_url":"https://api.github.com/users/wixette/gists{/gist_id}","starred_url":"https://api.github.com/users/wixette/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wixette/subscriptions","organizations_url":"https://api.github.com/users/wixette/orgs","repos_url":"https://api.github.com/users/wixette/repos","events_url":"https://api.github.com/users/wixette/events{/privacy}","received_events_url":"https://api.github.com/users/wixette/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T01:29:56Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"014e0656be337f521beb20a64d95aee5393b8a33","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/aha-001/SeedLang/pulls/28/commits","review_comments_url":"https://api.github.com/repos/aha-001/SeedLang/pulls/28/comments","review_comment_url":"https://api.github.com/repos/aha-001/SeedLang/pulls/comments{/number}","comments_url":"https://api.github.com/repos/aha-001/SeedLang/issues/28/comments","statuses_url":"https://api.github.com/repos/aha-001/SeedLang/statuses/7669a8bbbda63d6420e8efc1c623a2be5e9a1574","head":{"label":"aha-001:wip_wixette_script","ref":"wip_wixette_script","sha":"7669a8bbbda63d6420e8efc1c623a2be5e9a1574","user":{"login":"aha-001","id":83710579,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNzEwNTc5","avatar_url":"https://avatars.githubusercontent.com/u/83710579?v=4","gravatar_id":"","url":"https://api.github.com/users/aha-001","html_url":"https://github.com/aha-001","followers_url":"https://api.github.com/users/aha-001/followers","following_url":"https://api.github.com/users/aha-001/following{/other_user}","gists_url":"https://api.github.com/users/aha-001/gists{/gist_id}","starred_url":"https://api.github.com/users/aha-001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aha-001/subscriptions","organizations_url":"https://api.github.com/users/aha-001/orgs","repos_url":"https://api.github.com/users/aha-001/repos","events_url":"https://api.github.com/users/aha-001/events{/privacy}","received_events_url":"https://api.github.com/users/aha-001/received_events","type":"Organization","site_admin":false},"repo":{"id":376259206,"node_id":"MDEwOlJlcG9zaXRvcnkzNzYyNTkyMDY=","name":"SeedLang","full_name":"aha-001/SeedLang","private":false,"owner":{"login":"aha-001","id":83710579,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNzEwNTc5","avatar_url":"https://avatars.githubusercontent.com/u/83710579?v=4","gravatar_id":"","url":"https://api.github.com/users/aha-001","html_url":"https://github.com/aha-001","followers_url":"https://api.github.com/users/aha-001/followers","following_url":"https://api.github.com/users/aha-001/following{/other_user}","gists_url":"https://api.github.com/users/aha-001/gists{/gist_id}","starred_url":"https://api.github.com/users/aha-001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aha-001/subscriptions","organizations_url":"https://api.github.com/users/aha-001/orgs","repos_url":"https://api.github.com/users/aha-001/repos","events_url":"https://api.github.com/users/aha-001/events{/privacy}","received_events_url":"https://api.github.com/users/aha-001/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/aha-001/SeedLang","description":"A visualizable low-code programming environment.","fork":false,"url":"https://api.github.com/repos/aha-001/SeedLang","forks_url":"https://api.github.com/repos/aha-001/SeedLang/forks","keys_url":"https://api.github.com/repos/aha-001/SeedLang/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aha-001/SeedLang/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aha-001/SeedLang/teams","hooks_url":"https://api.github.com/repos/aha-001/SeedLang/hooks","issue_events_url":"https://api.github.com/repos/aha-001/SeedLang/issues/events{/number}","events_url":"https://api.github.com/repos/aha-001/SeedLang/events","assignees_url":"https://api.github.com/repos/aha-001/SeedLang/assignees{/user}","branches_url":"https://api.github.com/repos/aha-001/SeedLang/branches{/branch}","tags_url":"https://api.github.com/repos/aha-001/SeedLang/tags","blobs_url":"https://api.github.com/repos/aha-001/SeedLang/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aha-001/SeedLang/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aha-001/SeedLang/git/refs{/sha}","trees_url":"https://api.github.com/repos/aha-001/SeedLang/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aha-001/SeedLang/statuses/{sha}","languages_url":"https://api.github.com/repos/aha-001/SeedLang/languages","stargazers_url":"https://api.github.com/repos/aha-001/SeedLang/stargazers","contributors_url":"https://api.github.com/repos/aha-001/SeedLang/contributors","subscribers_url":"https://api.github.com/repos/aha-001/SeedLang/subscribers","subscription_url":"https://api.github.com/repos/aha-001/SeedLang/subscription","commits_url":"https://api.github.com/repos/aha-001/SeedLang/commits{/sha}","git_commits_url":"https://api.github.com/repos/aha-001/SeedLang/git/commits{/sha}","comments_url":"https://api.github.com/repos/aha-001/SeedLang/comments{/number}","issue_comment_url":"https://api.github.com/repos/aha-001/SeedLang/issues/comments{/number}","contents_url":"https://api.github.com/repos/aha-001/SeedLang/contents/{+path}","compare_url":"https://api.github.com/repos/aha-001/SeedLang/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aha-001/SeedLang/merges","archive_url":"https://api.github.com/repos/aha-001/SeedLang/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aha-001/SeedLang/downloads","issues_url":"https://api.github.com/repos/aha-001/SeedLang/issues{/number}","pulls_url":"https://api.github.com/repos/aha-001/SeedLang/pulls{/number}","milestones_url":"https://api.github.com/repos/aha-001/SeedLang/milestones{/number}","notifications_url":"https://api.github.com/repos/aha-001/SeedLang/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aha-001/SeedLang/labels{/name}","releases_url":"https://api.github.com/repos/aha-001/SeedLang/releases{/id}","deployments_url":"https://api.github.com/repos/aha-001/SeedLang/deployments","created_at":"2021-06-12T10:16:20Z","updated_at":"2021-07-20T23:41:35Z","pushed_at":"2021-07-21T01:29:56Z","git_url":"git://github.com/aha-001/SeedLang.git","ssh_url":"git@github.com:aha-001/SeedLang.git","clone_url":"https://github.com/aha-001/SeedLang.git","svn_url":"https://github.com/aha-001/SeedLang","homepage":"","size":209,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":5,"watchers":0,"default_branch":"main"}},"base":{"label":"aha-001:main","ref":"main","sha":"aaeff9dafdaf05bbe436a489f66a7ae1975faca3","user":{"login":"aha-001","id":83710579,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNzEwNTc5","avatar_url":"https://avatars.githubusercontent.com/u/83710579?v=4","gravatar_id":"","url":"https://api.github.com/users/aha-001","html_url":"https://github.com/aha-001","followers_url":"https://api.github.com/users/aha-001/followers","following_url":"https://api.github.com/users/aha-001/following{/other_user}","gists_url":"https://api.github.com/users/aha-001/gists{/gist_id}","starred_url":"https://api.github.com/users/aha-001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aha-001/subscriptions","organizations_url":"https://api.github.com/users/aha-001/orgs","repos_url":"https://api.github.com/users/aha-001/repos","events_url":"https://api.github.com/users/aha-001/events{/privacy}","received_events_url":"https://api.github.com/users/aha-001/received_events","type":"Organization","site_admin":false},"repo":{"id":376259206,"node_id":"MDEwOlJlcG9zaXRvcnkzNzYyNTkyMDY=","name":"SeedLang","full_name":"aha-001/SeedLang","private":false,"owner":{"login":"aha-001","id":83710579,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNzEwNTc5","avatar_url":"https://avatars.githubusercontent.com/u/83710579?v=4","gravatar_id":"","url":"https://api.github.com/users/aha-001","html_url":"https://github.com/aha-001","followers_url":"https://api.github.com/users/aha-001/followers","following_url":"https://api.github.com/users/aha-001/following{/other_user}","gists_url":"https://api.github.com/users/aha-001/gists{/gist_id}","starred_url":"https://api.github.com/users/aha-001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aha-001/subscriptions","organizations_url":"https://api.github.com/users/aha-001/orgs","repos_url":"https://api.github.com/users/aha-001/repos","events_url":"https://api.github.com/users/aha-001/events{/privacy}","received_events_url":"https://api.github.com/users/aha-001/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/aha-001/SeedLang","description":"A visualizable low-code programming environment.","fork":false,"url":"https://api.github.com/repos/aha-001/SeedLang","forks_url":"https://api.github.com/repos/aha-001/SeedLang/forks","keys_url":"https://api.github.com/repos/aha-001/SeedLang/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aha-001/SeedLang/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aha-001/SeedLang/teams","hooks_url":"https://api.github.com/repos/aha-001/SeedLang/hooks","issue_events_url":"https://api.github.com/repos/aha-001/SeedLang/issues/events{/number}","events_url":"https://api.github.com/repos/aha-001/SeedLang/events","assignees_url":"https://api.github.com/repos/aha-001/SeedLang/assignees{/user}","branches_url":"https://api.github.com/repos/aha-001/SeedLang/branches{/branch}","tags_url":"https://api.github.com/repos/aha-001/SeedLang/tags","blobs_url":"https://api.github.com/repos/aha-001/SeedLang/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aha-001/SeedLang/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aha-001/SeedLang/git/refs{/sha}","trees_url":"https://api.github.com/repos/aha-001/SeedLang/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aha-001/SeedLang/statuses/{sha}","languages_url":"https://api.github.com/repos/aha-001/SeedLang/languages","stargazers_url":"https://api.github.com/repos/aha-001/SeedLang/stargazers","contributors_url":"https://api.github.com/repos/aha-001/SeedLang/contributors","subscribers_url":"https://api.github.com/repos/aha-001/SeedLang/subscribers","subscription_url":"https://api.github.com/repos/aha-001/SeedLang/subscription","commits_url":"https://api.github.com/repos/aha-001/SeedLang/commits{/sha}","git_commits_url":"https://api.github.com/repos/aha-001/SeedLang/git/commits{/sha}","comments_url":"https://api.github.com/repos/aha-001/SeedLang/comments{/number}","issue_comment_url":"https://api.github.com/repos/aha-001/SeedLang/issues/comments{/number}","contents_url":"https://api.github.com/repos/aha-001/SeedLang/contents/{+path}","compare_url":"https://api.github.com/repos/aha-001/SeedLang/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aha-001/SeedLang/merges","archive_url":"https://api.github.com/repos/aha-001/SeedLang/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aha-001/SeedLang/downloads","issues_url":"https://api.github.com/repos/aha-001/SeedLang/issues{/number}","pulls_url":"https://api.github.com/repos/aha-001/SeedLang/pulls{/number}","milestones_url":"https://api.github.com/repos/aha-001/SeedLang/milestones{/number}","notifications_url":"https://api.github.com/repos/aha-001/SeedLang/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aha-001/SeedLang/labels{/name}","releases_url":"https://api.github.com/repos/aha-001/SeedLang/releases{/id}","deployments_url":"https://api.github.com/repos/aha-001/SeedLang/deployments","created_at":"2021-06-12T10:16:20Z","updated_at":"2021-07-20T23:41:35Z","pushed_at":"2021-07-21T01:29:56Z","git_url":"git://github.com/aha-001/SeedLang.git","ssh_url":"git@github.com:aha-001/SeedLang.git","clone_url":"https://github.com/aha-001/SeedLang.git","svn_url":"https://github.com/aha-001/SeedLang","homepage":"","size":209,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":5,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/aha-001/SeedLang/pulls/28"},"html":{"href":"https://github.com/aha-001/SeedLang/pull/28"},"issue":{"href":"https://api.github.com/repos/aha-001/SeedLang/issues/28"},"comments":{"href":"https://api.github.com/repos/aha-001/SeedLang/issues/28/comments"},"review_comments":{"href":"https://api.github.com/repos/aha-001/SeedLang/pulls/28/comments"},"review_comment":{"href":"https://api.github.com/repos/aha-001/SeedLang/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/aha-001/SeedLang/pulls/28/commits"},"statuses":{"href":"https://api.github.com/repos/aha-001/SeedLang/statuses/7669a8bbbda63d6420e8efc1c623a2be5e9a1574"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":83710579,"login":"aha-001","gravatar_id":"","url":"https://api.github.com/orgs/aha-001","avatar_url":"https://avatars.githubusercontent.com/u/83710579?"}} +{"id":"17245515316","type":"PushEvent","actor":{"id":87750170,"login":"Kuma-3","display_login":"Kuma-3","gravatar_id":"","url":"https://api.github.com/users/Kuma-3","avatar_url":"https://avatars.githubusercontent.com/u/87750170?"},"repo":{"id":388027199,"name":"Kuma-3/practice","url":"https://api.github.com/repos/Kuma-3/practice"},"payload":{"push_id":7562055310,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"8ba3d32281fd904c816356c0059e27f69fe31bc0","before":"9d33a5b00780ad35711465c66f3b10e8e44de1d5","commits":[{"sha":"8ba3d32281fd904c816356c0059e27f69fe31bc0","author":{"name":"Kumagai","email":"c64c4dcd3c69f6f1d2c3abb8ed17a2dcbe5204f6@users.noreply.github.com"},"message":"Update test.txt","distinct":true,"url":"https://api.github.com/repos/Kuma-3/practice/commits/8ba3d32281fd904c816356c0059e27f69fe31bc0"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515324","type":"PullRequestEvent","actor":{"id":57535432,"login":"hi-Long","display_login":"hi-Long","gravatar_id":"","url":"https://api.github.com/users/hi-Long","avatar_url":"https://avatars.githubusercontent.com/u/57535432?"},"repo":{"id":387646214,"name":"awesome-academy/OE38-FE-PR1-Long","url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long"},"payload":{"action":"closed","number":1,"pull_request":{"url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/1","id":693240156,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzMjQwMTU2","html_url":"https://github.com/awesome-academy/OE38-FE-PR1-Long/pull/1","diff_url":"https://github.com/awesome-academy/OE38-FE-PR1-Long/pull/1.diff","patch_url":"https://github.com/awesome-academy/OE38-FE-PR1-Long/pull/1.patch","issue_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues/1","number":1,"state":"closed","locked":false,"title":"Ref [Tracker][38673] Initial project structure.","user":{"login":"hi-Long","id":57535432,"node_id":"MDQ6VXNlcjU3NTM1NDMy","avatar_url":"https://avatars.githubusercontent.com/u/57535432?v=4","gravatar_id":"","url":"https://api.github.com/users/hi-Long","html_url":"https://github.com/hi-Long","followers_url":"https://api.github.com/users/hi-Long/followers","following_url":"https://api.github.com/users/hi-Long/following{/other_user}","gists_url":"https://api.github.com/users/hi-Long/gists{/gist_id}","starred_url":"https://api.github.com/users/hi-Long/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hi-Long/subscriptions","organizations_url":"https://api.github.com/users/hi-Long/orgs","repos_url":"https://api.github.com/users/hi-Long/repos","events_url":"https://api.github.com/users/hi-Long/events{/privacy}","received_events_url":"https://api.github.com/users/hi-Long/received_events","type":"User","site_admin":false},"body":"Link redmine: https://edu-redmine.sun-asterisk.vn/issues/38673","created_at":"2021-07-20T07:48:53Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:03Z","merged_at":null,"merge_commit_sha":"6a62a50387a68ca188ecca9e26e8bd1a8641234e","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/1/commits","review_comments_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/1/comments","review_comment_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/comments{/number}","comments_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues/1/comments","statuses_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/statuses/d02e823a4d582f17e8ac3385b1e8b88662bf67c5","head":{"label":"hi-Long:master","ref":"master","sha":"d02e823a4d582f17e8ac3385b1e8b88662bf67c5","user":{"login":"hi-Long","id":57535432,"node_id":"MDQ6VXNlcjU3NTM1NDMy","avatar_url":"https://avatars.githubusercontent.com/u/57535432?v=4","gravatar_id":"","url":"https://api.github.com/users/hi-Long","html_url":"https://github.com/hi-Long","followers_url":"https://api.github.com/users/hi-Long/followers","following_url":"https://api.github.com/users/hi-Long/following{/other_user}","gists_url":"https://api.github.com/users/hi-Long/gists{/gist_id}","starred_url":"https://api.github.com/users/hi-Long/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hi-Long/subscriptions","organizations_url":"https://api.github.com/users/hi-Long/orgs","repos_url":"https://api.github.com/users/hi-Long/repos","events_url":"https://api.github.com/users/hi-Long/events{/privacy}","received_events_url":"https://api.github.com/users/hi-Long/received_events","type":"User","site_admin":false},"repo":{"id":387669152,"node_id":"MDEwOlJlcG9zaXRvcnkzODc2NjkxNTI=","name":"OE38-FE-PR1-Long","full_name":"hi-Long/OE38-FE-PR1-Long","private":false,"owner":{"login":"hi-Long","id":57535432,"node_id":"MDQ6VXNlcjU3NTM1NDMy","avatar_url":"https://avatars.githubusercontent.com/u/57535432?v=4","gravatar_id":"","url":"https://api.github.com/users/hi-Long","html_url":"https://github.com/hi-Long","followers_url":"https://api.github.com/users/hi-Long/followers","following_url":"https://api.github.com/users/hi-Long/following{/other_user}","gists_url":"https://api.github.com/users/hi-Long/gists{/gist_id}","starred_url":"https://api.github.com/users/hi-Long/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hi-Long/subscriptions","organizations_url":"https://api.github.com/users/hi-Long/orgs","repos_url":"https://api.github.com/users/hi-Long/repos","events_url":"https://api.github.com/users/hi-Long/events{/privacy}","received_events_url":"https://api.github.com/users/hi-Long/received_events","type":"User","site_admin":false},"html_url":"https://github.com/hi-Long/OE38-FE-PR1-Long","description":null,"fork":true,"url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long","forks_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/forks","keys_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/teams","hooks_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/hooks","issue_events_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/issues/events{/number}","events_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/events","assignees_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/assignees{/user}","branches_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/branches{/branch}","tags_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/tags","blobs_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/git/refs{/sha}","trees_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/statuses/{sha}","languages_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/languages","stargazers_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/stargazers","contributors_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/contributors","subscribers_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/subscribers","subscription_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/subscription","commits_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/commits{/sha}","git_commits_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/git/commits{/sha}","comments_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/comments{/number}","issue_comment_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/issues/comments{/number}","contents_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/contents/{+path}","compare_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/merges","archive_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/downloads","issues_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/issues{/number}","pulls_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/pulls{/number}","milestones_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/milestones{/number}","notifications_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/labels{/name}","releases_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/releases{/id}","deployments_url":"https://api.github.com/repos/hi-Long/OE38-FE-PR1-Long/deployments","created_at":"2021-07-20T04:13:58Z","updated_at":"2021-07-21T07:46:17Z","pushed_at":"2021-07-21T07:46:14Z","git_url":"git://github.com/hi-Long/OE38-FE-PR1-Long.git","ssh_url":"git@github.com:hi-Long/OE38-FE-PR1-Long.git","clone_url":"https://github.com/hi-Long/OE38-FE-PR1-Long.git","svn_url":"https://github.com/hi-Long/OE38-FE-PR1-Long","homepage":null,"size":5,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"awesome-academy:master","ref":"master","sha":"3fe32f03f97245a1573fc969975cabd8b81baa20","user":{"login":"awesome-academy","id":33895223,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMzODk1MjIz","avatar_url":"https://avatars.githubusercontent.com/u/33895223?v=4","gravatar_id":"","url":"https://api.github.com/users/awesome-academy","html_url":"https://github.com/awesome-academy","followers_url":"https://api.github.com/users/awesome-academy/followers","following_url":"https://api.github.com/users/awesome-academy/following{/other_user}","gists_url":"https://api.github.com/users/awesome-academy/gists{/gist_id}","starred_url":"https://api.github.com/users/awesome-academy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/awesome-academy/subscriptions","organizations_url":"https://api.github.com/users/awesome-academy/orgs","repos_url":"https://api.github.com/users/awesome-academy/repos","events_url":"https://api.github.com/users/awesome-academy/events{/privacy}","received_events_url":"https://api.github.com/users/awesome-academy/received_events","type":"Organization","site_admin":false},"repo":{"id":387646214,"node_id":"MDEwOlJlcG9zaXRvcnkzODc2NDYyMTQ=","name":"OE38-FE-PR1-Long","full_name":"awesome-academy/OE38-FE-PR1-Long","private":false,"owner":{"login":"awesome-academy","id":33895223,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMzODk1MjIz","avatar_url":"https://avatars.githubusercontent.com/u/33895223?v=4","gravatar_id":"","url":"https://api.github.com/users/awesome-academy","html_url":"https://github.com/awesome-academy","followers_url":"https://api.github.com/users/awesome-academy/followers","following_url":"https://api.github.com/users/awesome-academy/following{/other_user}","gists_url":"https://api.github.com/users/awesome-academy/gists{/gist_id}","starred_url":"https://api.github.com/users/awesome-academy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/awesome-academy/subscriptions","organizations_url":"https://api.github.com/users/awesome-academy/orgs","repos_url":"https://api.github.com/users/awesome-academy/repos","events_url":"https://api.github.com/users/awesome-academy/events{/privacy}","received_events_url":"https://api.github.com/users/awesome-academy/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/awesome-academy/OE38-FE-PR1-Long","description":null,"fork":false,"url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long","forks_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/forks","keys_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/keys{/key_id}","collaborators_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/teams","hooks_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/hooks","issue_events_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues/events{/number}","events_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/events","assignees_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/assignees{/user}","branches_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/branches{/branch}","tags_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/tags","blobs_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/git/refs{/sha}","trees_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/git/trees{/sha}","statuses_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/statuses/{sha}","languages_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/languages","stargazers_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/stargazers","contributors_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/contributors","subscribers_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/subscribers","subscription_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/subscription","commits_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/commits{/sha}","git_commits_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/git/commits{/sha}","comments_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/comments{/number}","issue_comment_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues/comments{/number}","contents_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/contents/{+path}","compare_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/compare/{base}...{head}","merges_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/merges","archive_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/downloads","issues_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues{/number}","pulls_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls{/number}","milestones_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/milestones{/number}","notifications_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/labels{/name}","releases_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/releases{/id}","deployments_url":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/deployments","created_at":"2021-07-20T02:14:07Z","updated_at":"2021-07-20T02:14:10Z","pushed_at":"2021-07-21T07:46:16Z","git_url":"git://github.com/awesome-academy/OE38-FE-PR1-Long.git","ssh_url":"git@github.com:awesome-academy/OE38-FE-PR1-Long.git","clone_url":"https://github.com/awesome-academy/OE38-FE-PR1-Long.git","svn_url":"https://github.com/awesome-academy/OE38-FE-PR1-Long","homepage":null,"size":1,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":1,"open_issues":0,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/1"},"html":{"href":"https://github.com/awesome-academy/OE38-FE-PR1-Long/pull/1"},"issue":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues/1"},"comments":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/awesome-academy/OE38-FE-PR1-Long/statuses/d02e823a4d582f17e8ac3385b1e8b88662bf67c5"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":true,"rebaseable":false,"mergeable_state":"clean","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":2133,"deletions":0,"changed_files":57}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":33895223,"login":"awesome-academy","gravatar_id":"","url":"https://api.github.com/orgs/awesome-academy","avatar_url":"https://avatars.githubusercontent.com/u/33895223?"}} +{"id":"17245515333","type":"PushEvent","actor":{"id":85554053,"login":"ruben6p","display_login":"ruben6p","gravatar_id":"","url":"https://api.github.com/users/ruben6p","avatar_url":"https://avatars.githubusercontent.com/u/85554053?"},"repo":{"id":383771555,"name":"PcMant/proyectoFinalFrontend-EOI2021","url":"https://api.github.com/repos/PcMant/proyectoFinalFrontend-EOI2021"},"payload":{"push_id":7562055295,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"5f254102903fac391504574b8211697c4a49fbb3","before":"97417f4198fe9cd7887017803dfb9815bae5a437","commits":[{"sha":"5f254102903fac391504574b8211697c4a49fbb3","author":{"name":"ruben6p","email":"dac8f4352a845a58601d9c4c8763a244b6f9bc6e@users.noreply.github.com"},"message":"form","distinct":true,"url":"https://api.github.com/repos/PcMant/proyectoFinalFrontend-EOI2021/commits/5f254102903fac391504574b8211697c4a49fbb3"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515339","type":"ForkEvent","actor":{"id":63769993,"login":"jzy-byte-cmd","display_login":"jzy-byte-cmd","gravatar_id":"","url":"https://api.github.com/users/jzy-byte-cmd","avatar_url":"https://avatars.githubusercontent.com/u/63769993?"},"repo":{"id":262296122,"name":"PaddlePaddle/PaddleOCR","url":"https://api.github.com/repos/PaddlePaddle/PaddleOCR"},"payload":{"forkee":{"id":388040576,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1NzY=","name":"PaddleOCR","full_name":"jzy-byte-cmd/PaddleOCR","private":false,"owner":{"login":"jzy-byte-cmd","id":63769993,"node_id":"MDQ6VXNlcjYzNzY5OTkz","avatar_url":"https://avatars.githubusercontent.com/u/63769993?v=4","gravatar_id":"","url":"https://api.github.com/users/jzy-byte-cmd","html_url":"https://github.com/jzy-byte-cmd","followers_url":"https://api.github.com/users/jzy-byte-cmd/followers","following_url":"https://api.github.com/users/jzy-byte-cmd/following{/other_user}","gists_url":"https://api.github.com/users/jzy-byte-cmd/gists{/gist_id}","starred_url":"https://api.github.com/users/jzy-byte-cmd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jzy-byte-cmd/subscriptions","organizations_url":"https://api.github.com/users/jzy-byte-cmd/orgs","repos_url":"https://api.github.com/users/jzy-byte-cmd/repos","events_url":"https://api.github.com/users/jzy-byte-cmd/events{/privacy}","received_events_url":"https://api.github.com/users/jzy-byte-cmd/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jzy-byte-cmd/PaddleOCR","description":"Awesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80+ languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices)","fork":true,"url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR","forks_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/forks","keys_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/teams","hooks_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/hooks","issue_events_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/issues/events{/number}","events_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/events","assignees_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/assignees{/user}","branches_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/branches{/branch}","tags_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/tags","blobs_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/git/refs{/sha}","trees_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/statuses/{sha}","languages_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/languages","stargazers_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/stargazers","contributors_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/contributors","subscribers_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/subscribers","subscription_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/subscription","commits_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/commits{/sha}","git_commits_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/git/commits{/sha}","comments_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/comments{/number}","issue_comment_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/issues/comments{/number}","contents_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/contents/{+path}","compare_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/merges","archive_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/downloads","issues_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/issues{/number}","pulls_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/pulls{/number}","milestones_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/milestones{/number}","notifications_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/labels{/name}","releases_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/releases{/id}","deployments_url":"https://api.github.com/repos/jzy-byte-cmd/PaddleOCR/deployments","created_at":"2021-07-21T08:00:03Z","updated_at":"2021-07-21T07:05:49Z","pushed_at":"2021-07-21T07:50:18Z","git_url":"git://github.com/jzy-byte-cmd/PaddleOCR.git","ssh_url":"git@github.com:jzy-byte-cmd/PaddleOCR.git","clone_url":"https://github.com/jzy-byte-cmd/PaddleOCR.git","svn_url":"https://github.com/jzy-byte-cmd/PaddleOCR","homepage":"","size":124201,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":23534030,"login":"PaddlePaddle","gravatar_id":"","url":"https://api.github.com/orgs/PaddlePaddle","avatar_url":"https://avatars.githubusercontent.com/u/23534030?"}} +{"id":"17245515342","type":"CreateEvent","actor":{"id":76052938,"login":"karthick3-ux","display_login":"karthick3-ux","gravatar_id":"","url":"https://api.github.com/users/karthick3-ux","avatar_url":"https://avatars.githubusercontent.com/u/76052938?"},"repo":{"id":388040575,"name":"karthick3-ux/karthick3-ux","url":"https://api.github.com/repos/karthick3-ux/karthick3-ux"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":"Config files for my GitHub profile.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515358","type":"PushEvent","actor":{"id":1317792,"login":"xndcn","display_login":"xndcn","gravatar_id":"","url":"https://api.github.com/users/xndcn","avatar_url":"https://avatars.githubusercontent.com/u/1317792?"},"repo":{"id":10446275,"name":"xndcn/smzdm.com","url":"https://api.github.com/repos/xndcn/smzdm.com"},"payload":{"push_id":7562055345,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"822c6e347309b216d274f7a584043d52aa264654","before":"b15945ef5f2a60b4b5af9e0db9d5f80b16d9d317","commits":[{"sha":"822c6e347309b216d274f7a584043d52aa264654","author":{"name":"xndcn","email":"1e09e78f2250d149e0e983feae098f2c71429797@gmail.com"},"message":"30 items update","distinct":true,"url":"https://api.github.com/repos/xndcn/smzdm.com/commits/822c6e347309b216d274f7a584043d52aa264654"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515362","type":"PushEvent","actor":{"id":4264707,"login":"markdgray","display_login":"markdgray","gravatar_id":"","url":"https://api.github.com/users/markdgray","avatar_url":"https://avatars.githubusercontent.com/u/4264707?"},"repo":{"id":336240020,"name":"markdgray/metallb","url":"https://api.github.com/repos/markdgray/metallb"},"payload":{"push_id":7562055318,"size":1,"distinct_size":1,"ref":"refs/heads/feat/frr_design","head":"80fb248e8892322fd6eb0b53b29440be4100f1eb","before":"4921ce2503f25e44cd3f98e70385a2a34249ea9a","commits":[{"sha":"80fb248e8892322fd6eb0b53b29440be4100f1eb","author":{"name":"Mark Gray","email":"f9a4292a438d6a2a42926ff0dd671855d2f3c640@redhat.com"},"message":"Update design/0001-frr.md\r\n\r\nskip ci\n\nCo-authored-by: Johannes Liebermann ","distinct":true,"url":"https://api.github.com/repos/markdgray/metallb/commits/80fb248e8892322fd6eb0b53b29440be4100f1eb"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515369","type":"PushEvent","actor":{"id":87694730,"login":"aji-tama","display_login":"aji-tama","gravatar_id":"","url":"https://api.github.com/users/aji-tama","avatar_url":"https://avatars.githubusercontent.com/u/87694730?"},"repo":{"id":388040508,"name":"aji-tama/Moonmap_cartesian","url":"https://api.github.com/repos/aji-tama/Moonmap_cartesian"},"payload":{"push_id":7562055368,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"53f72e3da51a7413f1b598ff9582e8a8854cf4ef","before":"86f5c5b0644575a5674584ddb100d59703d5dceb","commits":[{"sha":"53f72e3da51a7413f1b598ff9582e8a8854cf4ef","author":{"name":"aji-tama","email":"1a1fc93994fffa4514f2c86e7d35d51a367548bc@users.noreply.github.com"},"message":"Add files via upload","distinct":true,"url":"https://api.github.com/repos/aji-tama/Moonmap_cartesian/commits/53f72e3da51a7413f1b598ff9582e8a8854cf4ef"}]},"public":true,"created_at":"2021-07-21T08:00:03Z"} +{"id":"17245515406","type":"PullRequestReviewEvent","actor":{"id":6417047,"login":"AlexeySachkov","display_login":"AlexeySachkov","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","avatar_url":"https://avatars.githubusercontent.com/u/6417047?"},"repo":{"id":127136978,"name":"KhronosGroup/SPIRV-LLVM-Translator","url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator"},"payload":{"action":"created","review":{"id":711364920,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzY0OTIw","user":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"body":null,"commit_id":"dbc54dd752c04bb65e8d2e2aedaaac22c247266e","submitted_at":"2021-07-21T08:00:03Z","state":"commented","html_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119#pullrequestreview-711364920","pull_request_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119","author_association":"CONTRIBUTOR","_links":{"html":{"href":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119#pullrequestreview-711364920"},"pull_request":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119"}}},"pull_request":{"url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119","id":693465500,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDY1NTAw","html_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119","diff_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119.diff","patch_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119.patch","issue_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119","number":1119,"state":"open","locked":false,"title":"Improve llvm.memmove translation","user":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"body":"The main change comparing to previous approach: we do not look for the underlying type which is being copied anymore. Instead, we directly allocate required amount of bytes and copy them.","created_at":"2021-07-20T13:26:14Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1df177eccdd4f5230bed2491a8e681f5963ea9d7","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/commits","review_comments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/comments","review_comment_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/comments{/number}","comments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119/comments","statuses_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/statuses/dbc54dd752c04bb65e8d2e2aedaaac22c247266e","head":{"label":"AlexeySachkov:private/asachkov/memmove-translation-fixes","ref":"private/asachkov/memmove-translation-fixes","sha":"dbc54dd752c04bb65e8d2e2aedaaac22c247266e","user":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"repo":{"id":160663481,"node_id":"MDEwOlJlcG9zaXRvcnkxNjA2NjM0ODE=","name":"SPIRV-LLVM-Translator","full_name":"AlexeySachkov/SPIRV-LLVM-Translator","private":false,"owner":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"html_url":"https://github.com/AlexeySachkov/SPIRV-LLVM-Translator","description":"A tool and a library for bi-directional translation between SPIR-V and LLVM IR","fork":true,"url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator","forks_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/forks","keys_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/teams","hooks_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/hooks","issue_events_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/issues/events{/number}","events_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/events","assignees_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/assignees{/user}","branches_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/branches{/branch}","tags_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/tags","blobs_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/refs{/sha}","trees_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/statuses/{sha}","languages_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/languages","stargazers_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/stargazers","contributors_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/contributors","subscribers_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/subscribers","subscription_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/subscription","commits_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/commits{/sha}","git_commits_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/commits{/sha}","comments_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/comments{/number}","issue_comment_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/issues/comments{/number}","contents_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/contents/{+path}","compare_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/merges","archive_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/downloads","issues_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/issues{/number}","pulls_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/pulls{/number}","milestones_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/milestones{/number}","notifications_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/labels{/name}","releases_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/releases{/id}","deployments_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/deployments","created_at":"2018-12-06T11:12:57Z","updated_at":"2021-02-17T09:49:57Z","pushed_at":"2021-07-21T07:59:11Z","git_url":"git://github.com/AlexeySachkov/SPIRV-LLVM-Translator.git","ssh_url":"git@github.com:AlexeySachkov/SPIRV-LLVM-Translator.git","clone_url":"https://github.com/AlexeySachkov/SPIRV-LLVM-Translator.git","svn_url":"https://github.com/AlexeySachkov/SPIRV-LLVM-Translator","homepage":null,"size":8654,"stargazers_count":0,"watchers_count":0,"language":"LLVM","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"KhronosGroup:master","ref":"master","sha":"6795cab62f3ed36e9a5cec235c62a8d92a40cc8f","user":{"login":"KhronosGroup","id":1608701,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDg3MDE=","avatar_url":"https://avatars.githubusercontent.com/u/1608701?v=4","gravatar_id":"","url":"https://api.github.com/users/KhronosGroup","html_url":"https://github.com/KhronosGroup","followers_url":"https://api.github.com/users/KhronosGroup/followers","following_url":"https://api.github.com/users/KhronosGroup/following{/other_user}","gists_url":"https://api.github.com/users/KhronosGroup/gists{/gist_id}","starred_url":"https://api.github.com/users/KhronosGroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KhronosGroup/subscriptions","organizations_url":"https://api.github.com/users/KhronosGroup/orgs","repos_url":"https://api.github.com/users/KhronosGroup/repos","events_url":"https://api.github.com/users/KhronosGroup/events{/privacy}","received_events_url":"https://api.github.com/users/KhronosGroup/received_events","type":"Organization","site_admin":false},"repo":{"id":127136978,"node_id":"MDEwOlJlcG9zaXRvcnkxMjcxMzY5Nzg=","name":"SPIRV-LLVM-Translator","full_name":"KhronosGroup/SPIRV-LLVM-Translator","private":false,"owner":{"login":"KhronosGroup","id":1608701,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDg3MDE=","avatar_url":"https://avatars.githubusercontent.com/u/1608701?v=4","gravatar_id":"","url":"https://api.github.com/users/KhronosGroup","html_url":"https://github.com/KhronosGroup","followers_url":"https://api.github.com/users/KhronosGroup/followers","following_url":"https://api.github.com/users/KhronosGroup/following{/other_user}","gists_url":"https://api.github.com/users/KhronosGroup/gists{/gist_id}","starred_url":"https://api.github.com/users/KhronosGroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KhronosGroup/subscriptions","organizations_url":"https://api.github.com/users/KhronosGroup/orgs","repos_url":"https://api.github.com/users/KhronosGroup/repos","events_url":"https://api.github.com/users/KhronosGroup/events{/privacy}","received_events_url":"https://api.github.com/users/KhronosGroup/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator","description":"A tool and a library for bi-directional translation between SPIR-V and LLVM IR","fork":false,"url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator","forks_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/forks","keys_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/teams","hooks_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/hooks","issue_events_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/events{/number}","events_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/events","assignees_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/assignees{/user}","branches_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/branches{/branch}","tags_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/tags","blobs_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/refs{/sha}","trees_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/statuses/{sha}","languages_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/languages","stargazers_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/stargazers","contributors_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/contributors","subscribers_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/subscribers","subscription_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/subscription","commits_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/commits{/sha}","git_commits_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/commits{/sha}","comments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/comments{/number}","issue_comment_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/comments{/number}","contents_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/contents/{+path}","compare_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/merges","archive_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/downloads","issues_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues{/number}","pulls_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls{/number}","milestones_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/milestones{/number}","notifications_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/labels{/name}","releases_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/releases{/id}","deployments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/deployments","created_at":"2018-03-28T12:29:24Z","updated_at":"2021-07-19T17:01:10Z","pushed_at":"2021-07-21T07:59:13Z","git_url":"git://github.com/KhronosGroup/SPIRV-LLVM-Translator.git","ssh_url":"git@github.com:KhronosGroup/SPIRV-LLVM-Translator.git","clone_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator.git","svn_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator","homepage":null,"size":9402,"stargazers_count":248,"watchers_count":248,"language":"LLVM","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":113,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":104,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":113,"open_issues":104,"watchers":248,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119"},"html":{"href":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119"},"issue":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119"},"comments":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119/comments"},"review_comments":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/comments"},"review_comment":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/commits"},"statuses":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/statuses/dbc54dd752c04bb65e8d2e2aedaaac22c247266e"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":1608701,"login":"KhronosGroup","gravatar_id":"","url":"https://api.github.com/orgs/KhronosGroup","avatar_url":"https://avatars.githubusercontent.com/u/1608701?"}} +{"id":"17245515408","type":"PullRequestEvent","actor":{"id":39814207,"login":"pull[bot]","display_login":"pull","gravatar_id":"","url":"https://api.github.com/users/pull[bot]","avatar_url":"https://avatars.githubusercontent.com/u/39814207?"},"repo":{"id":381937845,"name":"sandutsar/PowerShell","url":"https://api.github.com/repos/sandutsar/PowerShell"},"payload":{"action":"opened","number":24,"pull_request":{"url":"https://api.github.com/repos/sandutsar/PowerShell/pulls/24","id":694141689,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNjg5","html_url":"https://github.com/sandutsar/PowerShell/pull/24","diff_url":"https://github.com/sandutsar/PowerShell/pull/24.diff","patch_url":"https://github.com/sandutsar/PowerShell/pull/24.patch","issue_url":"https://api.github.com/repos/sandutsar/PowerShell/issues/24","number":24,"state":"open","locked":false,"title":"[pull] master from PowerShell:master","user":{"login":"pull[bot]","id":39814207,"node_id":"MDM6Qm90Mzk4MTQyMDc=","avatar_url":"https://avatars.githubusercontent.com/in/12910?v=4","gravatar_id":"","url":"https://api.github.com/users/pull%5Bbot%5D","html_url":"https://github.com/apps/pull","followers_url":"https://api.github.com/users/pull%5Bbot%5D/followers","following_url":"https://api.github.com/users/pull%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/pull%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/pull%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pull%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/pull%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/pull%5Bbot%5D/repos","events_url":"https://api.github.com/users/pull%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/pull%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"See Commits and Changes for more details.\n\n-----\nCreated by [ **pull[bot]**](https://github.com/wei/pull)\n\n_Can you help keep this open source service alive? **[💖 Please sponsor : )](https://prod.download/pull-pr-sponsor)**_","created_at":"2021-07-21T08:00:03Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/sandutsar/PowerShell/pulls/24/commits","review_comments_url":"https://api.github.com/repos/sandutsar/PowerShell/pulls/24/comments","review_comment_url":"https://api.github.com/repos/sandutsar/PowerShell/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sandutsar/PowerShell/issues/24/comments","statuses_url":"https://api.github.com/repos/sandutsar/PowerShell/statuses/408c4fd9ade57634c2ed1fbc2fd72a1e80b2a04a","head":{"label":"PowerShell:master","ref":"master","sha":"408c4fd9ade57634c2ed1fbc2fd72a1e80b2a04a","user":{"login":"PowerShell","id":11524380,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTI0Mzgw","avatar_url":"https://avatars.githubusercontent.com/u/11524380?v=4","gravatar_id":"","url":"https://api.github.com/users/PowerShell","html_url":"https://github.com/PowerShell","followers_url":"https://api.github.com/users/PowerShell/followers","following_url":"https://api.github.com/users/PowerShell/following{/other_user}","gists_url":"https://api.github.com/users/PowerShell/gists{/gist_id}","starred_url":"https://api.github.com/users/PowerShell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PowerShell/subscriptions","organizations_url":"https://api.github.com/users/PowerShell/orgs","repos_url":"https://api.github.com/users/PowerShell/repos","events_url":"https://api.github.com/users/PowerShell/events{/privacy}","received_events_url":"https://api.github.com/users/PowerShell/received_events","type":"Organization","site_admin":false},"repo":{"id":49609581,"node_id":"MDEwOlJlcG9zaXRvcnk0OTYwOTU4MQ==","name":"PowerShell","full_name":"PowerShell/PowerShell","private":false,"owner":{"login":"PowerShell","id":11524380,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTI0Mzgw","avatar_url":"https://avatars.githubusercontent.com/u/11524380?v=4","gravatar_id":"","url":"https://api.github.com/users/PowerShell","html_url":"https://github.com/PowerShell","followers_url":"https://api.github.com/users/PowerShell/followers","following_url":"https://api.github.com/users/PowerShell/following{/other_user}","gists_url":"https://api.github.com/users/PowerShell/gists{/gist_id}","starred_url":"https://api.github.com/users/PowerShell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PowerShell/subscriptions","organizations_url":"https://api.github.com/users/PowerShell/orgs","repos_url":"https://api.github.com/users/PowerShell/repos","events_url":"https://api.github.com/users/PowerShell/events{/privacy}","received_events_url":"https://api.github.com/users/PowerShell/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/PowerShell/PowerShell","description":"PowerShell for every system!","fork":false,"url":"https://api.github.com/repos/PowerShell/PowerShell","forks_url":"https://api.github.com/repos/PowerShell/PowerShell/forks","keys_url":"https://api.github.com/repos/PowerShell/PowerShell/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PowerShell/PowerShell/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PowerShell/PowerShell/teams","hooks_url":"https://api.github.com/repos/PowerShell/PowerShell/hooks","issue_events_url":"https://api.github.com/repos/PowerShell/PowerShell/issues/events{/number}","events_url":"https://api.github.com/repos/PowerShell/PowerShell/events","assignees_url":"https://api.github.com/repos/PowerShell/PowerShell/assignees{/user}","branches_url":"https://api.github.com/repos/PowerShell/PowerShell/branches{/branch}","tags_url":"https://api.github.com/repos/PowerShell/PowerShell/tags","blobs_url":"https://api.github.com/repos/PowerShell/PowerShell/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PowerShell/PowerShell/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PowerShell/PowerShell/git/refs{/sha}","trees_url":"https://api.github.com/repos/PowerShell/PowerShell/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PowerShell/PowerShell/statuses/{sha}","languages_url":"https://api.github.com/repos/PowerShell/PowerShell/languages","stargazers_url":"https://api.github.com/repos/PowerShell/PowerShell/stargazers","contributors_url":"https://api.github.com/repos/PowerShell/PowerShell/contributors","subscribers_url":"https://api.github.com/repos/PowerShell/PowerShell/subscribers","subscription_url":"https://api.github.com/repos/PowerShell/PowerShell/subscription","commits_url":"https://api.github.com/repos/PowerShell/PowerShell/commits{/sha}","git_commits_url":"https://api.github.com/repos/PowerShell/PowerShell/git/commits{/sha}","comments_url":"https://api.github.com/repos/PowerShell/PowerShell/comments{/number}","issue_comment_url":"https://api.github.com/repos/PowerShell/PowerShell/issues/comments{/number}","contents_url":"https://api.github.com/repos/PowerShell/PowerShell/contents/{+path}","compare_url":"https://api.github.com/repos/PowerShell/PowerShell/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PowerShell/PowerShell/merges","archive_url":"https://api.github.com/repos/PowerShell/PowerShell/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PowerShell/PowerShell/downloads","issues_url":"https://api.github.com/repos/PowerShell/PowerShell/issues{/number}","pulls_url":"https://api.github.com/repos/PowerShell/PowerShell/pulls{/number}","milestones_url":"https://api.github.com/repos/PowerShell/PowerShell/milestones{/number}","notifications_url":"https://api.github.com/repos/PowerShell/PowerShell/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PowerShell/PowerShell/labels{/name}","releases_url":"https://api.github.com/repos/PowerShell/PowerShell/releases{/id}","deployments_url":"https://api.github.com/repos/PowerShell/PowerShell/deployments","created_at":"2016-01-13T23:41:35Z","updated_at":"2021-07-21T07:42:45Z","pushed_at":"2021-07-21T06:34:39Z","git_url":"git://github.com/PowerShell/PowerShell.git","ssh_url":"git@github.com:PowerShell/PowerShell.git","clone_url":"https://github.com/PowerShell/PowerShell.git","svn_url":"https://github.com/PowerShell/PowerShell","homepage":"https://microsoft.com/PowerShell","size":80056,"stargazers_count":27823,"watchers_count":27823,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":4393,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3106,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":4393,"open_issues":3106,"watchers":27823,"default_branch":"master"}},"base":{"label":"sandutsar:master","ref":"master","sha":"ac762c1cce10b962394b9e7589ce8a2e3f8cebb1","user":{"login":"sandutsar","id":76633456,"node_id":"MDQ6VXNlcjc2NjMzNDU2","avatar_url":"https://avatars.githubusercontent.com/u/76633456?v=4","gravatar_id":"","url":"https://api.github.com/users/sandutsar","html_url":"https://github.com/sandutsar","followers_url":"https://api.github.com/users/sandutsar/followers","following_url":"https://api.github.com/users/sandutsar/following{/other_user}","gists_url":"https://api.github.com/users/sandutsar/gists{/gist_id}","starred_url":"https://api.github.com/users/sandutsar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sandutsar/subscriptions","organizations_url":"https://api.github.com/users/sandutsar/orgs","repos_url":"https://api.github.com/users/sandutsar/repos","events_url":"https://api.github.com/users/sandutsar/events{/privacy}","received_events_url":"https://api.github.com/users/sandutsar/received_events","type":"User","site_admin":false},"repo":{"id":381937845,"node_id":"MDEwOlJlcG9zaXRvcnkzODE5Mzc4NDU=","name":"PowerShell","full_name":"sandutsar/PowerShell","private":false,"owner":{"login":"sandutsar","id":76633456,"node_id":"MDQ6VXNlcjc2NjMzNDU2","avatar_url":"https://avatars.githubusercontent.com/u/76633456?v=4","gravatar_id":"","url":"https://api.github.com/users/sandutsar","html_url":"https://github.com/sandutsar","followers_url":"https://api.github.com/users/sandutsar/followers","following_url":"https://api.github.com/users/sandutsar/following{/other_user}","gists_url":"https://api.github.com/users/sandutsar/gists{/gist_id}","starred_url":"https://api.github.com/users/sandutsar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sandutsar/subscriptions","organizations_url":"https://api.github.com/users/sandutsar/orgs","repos_url":"https://api.github.com/users/sandutsar/repos","events_url":"https://api.github.com/users/sandutsar/events{/privacy}","received_events_url":"https://api.github.com/users/sandutsar/received_events","type":"User","site_admin":false},"html_url":"https://github.com/sandutsar/PowerShell","description":"PowerShell for every system!","fork":true,"url":"https://api.github.com/repos/sandutsar/PowerShell","forks_url":"https://api.github.com/repos/sandutsar/PowerShell/forks","keys_url":"https://api.github.com/repos/sandutsar/PowerShell/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sandutsar/PowerShell/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sandutsar/PowerShell/teams","hooks_url":"https://api.github.com/repos/sandutsar/PowerShell/hooks","issue_events_url":"https://api.github.com/repos/sandutsar/PowerShell/issues/events{/number}","events_url":"https://api.github.com/repos/sandutsar/PowerShell/events","assignees_url":"https://api.github.com/repos/sandutsar/PowerShell/assignees{/user}","branches_url":"https://api.github.com/repos/sandutsar/PowerShell/branches{/branch}","tags_url":"https://api.github.com/repos/sandutsar/PowerShell/tags","blobs_url":"https://api.github.com/repos/sandutsar/PowerShell/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sandutsar/PowerShell/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sandutsar/PowerShell/git/refs{/sha}","trees_url":"https://api.github.com/repos/sandutsar/PowerShell/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sandutsar/PowerShell/statuses/{sha}","languages_url":"https://api.github.com/repos/sandutsar/PowerShell/languages","stargazers_url":"https://api.github.com/repos/sandutsar/PowerShell/stargazers","contributors_url":"https://api.github.com/repos/sandutsar/PowerShell/contributors","subscribers_url":"https://api.github.com/repos/sandutsar/PowerShell/subscribers","subscription_url":"https://api.github.com/repos/sandutsar/PowerShell/subscription","commits_url":"https://api.github.com/repos/sandutsar/PowerShell/commits{/sha}","git_commits_url":"https://api.github.com/repos/sandutsar/PowerShell/git/commits{/sha}","comments_url":"https://api.github.com/repos/sandutsar/PowerShell/comments{/number}","issue_comment_url":"https://api.github.com/repos/sandutsar/PowerShell/issues/comments{/number}","contents_url":"https://api.github.com/repos/sandutsar/PowerShell/contents/{+path}","compare_url":"https://api.github.com/repos/sandutsar/PowerShell/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sandutsar/PowerShell/merges","archive_url":"https://api.github.com/repos/sandutsar/PowerShell/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sandutsar/PowerShell/downloads","issues_url":"https://api.github.com/repos/sandutsar/PowerShell/issues{/number}","pulls_url":"https://api.github.com/repos/sandutsar/PowerShell/pulls{/number}","milestones_url":"https://api.github.com/repos/sandutsar/PowerShell/milestones{/number}","notifications_url":"https://api.github.com/repos/sandutsar/PowerShell/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sandutsar/PowerShell/labels{/name}","releases_url":"https://api.github.com/repos/sandutsar/PowerShell/releases{/id}","deployments_url":"https://api.github.com/repos/sandutsar/PowerShell/deployments","created_at":"2021-07-01T06:51:52Z","updated_at":"2021-07-21T02:00:28Z","pushed_at":"2021-07-21T02:00:19Z","git_url":"git://github.com/sandutsar/PowerShell.git","ssh_url":"git@github.com:sandutsar/PowerShell.git","clone_url":"https://github.com/sandutsar/PowerShell.git","svn_url":"https://github.com/sandutsar/PowerShell","homepage":"https://microsoft.com/PowerShell","size":77641,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sandutsar/PowerShell/pulls/24"},"html":{"href":"https://github.com/sandutsar/PowerShell/pull/24"},"issue":{"href":"https://api.github.com/repos/sandutsar/PowerShell/issues/24"},"comments":{"href":"https://api.github.com/repos/sandutsar/PowerShell/issues/24/comments"},"review_comments":{"href":"https://api.github.com/repos/sandutsar/PowerShell/pulls/24/comments"},"review_comment":{"href":"https://api.github.com/repos/sandutsar/PowerShell/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sandutsar/PowerShell/pulls/24/commits"},"statuses":{"href":"https://api.github.com/repos/sandutsar/PowerShell/statuses/408c4fd9ade57634c2ed1fbc2fd72a1e80b2a04a"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":6,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515411","type":"PushEvent","actor":{"id":81431557,"login":"voyagebagage","display_login":"voyagebagage","gravatar_id":"","url":"https://api.github.com/users/voyagebagage","avatar_url":"https://avatars.githubusercontent.com/u/81431557?"},"repo":{"id":388036768,"name":"voyagebagage/voyagebagage","url":"https://api.github.com/repos/voyagebagage/voyagebagage"},"payload":{"push_id":7562055356,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"4807b964d0c9616f5502be34c20615d5d4bfd6df","before":"a683d05b0446b7d7e2518a31658e09b8c74a94ba","commits":[{"sha":"4807b964d0c9616f5502be34c20615d5d4bfd6df","author":{"name":"Oliv Dev","email":"9aab5662e6c75df679903a6d679cfcc913fcf574@gmail.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/voyagebagage/voyagebagage/commits/4807b964d0c9616f5502be34c20615d5d4bfd6df"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515428","type":"PullRequestEvent","actor":{"id":49736102,"login":"kodiakhq[bot]","display_login":"kodiakhq","gravatar_id":"","url":"https://api.github.com/users/kodiakhq[bot]","avatar_url":"https://avatars.githubusercontent.com/u/49736102?"},"repo":{"id":352747621,"name":"esm-bundle/markdown-it-emoji","url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji"},"payload":{"action":"closed","number":62,"pull_request":{"url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/62","id":694141091,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxMDkx","html_url":"https://github.com/esm-bundle/markdown-it-emoji/pull/62","diff_url":"https://github.com/esm-bundle/markdown-it-emoji/pull/62.diff","patch_url":"https://github.com/esm-bundle/markdown-it-emoji/pull/62.patch","issue_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/62","number":62,"state":"closed","locked":false,"title":"Update dependency rollup to v2.53.3","user":{"login":"renovate[bot]","id":29139614,"node_id":"MDM6Qm90MjkxMzk2MTQ=","avatar_url":"https://avatars.githubusercontent.com/in/2740?v=4","gravatar_id":"","url":"https://api.github.com/users/renovate%5Bbot%5D","html_url":"https://github.com/apps/renovate","followers_url":"https://api.github.com/users/renovate%5Bbot%5D/followers","following_url":"https://api.github.com/users/renovate%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/renovate%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/renovate%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/renovate%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/renovate%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/renovate%5Bbot%5D/repos","events_url":"https://api.github.com/users/renovate%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/renovate%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)\n\nThis PR contains the following updates:\n\n| Package | Change | Age | Adoption | Passing | Confidence |\n|---|---|---|---|---|---|\n| [rollup](https://rollupjs.org/) ([source](https://togithub.com/rollup/rollup)) | [`2.53.2` -> `2.53.3`](https://renovatebot.com/diffs/npm/rollup/2.53.2/2.53.3) | [![age](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/compatibility-slim/2.53.2)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/confidence-slim/2.53.2)](https://docs.renovatebot.com/merge-confidence/) |\n\n---\n\n### Release Notes\n\n
    \nrollup/rollup\n\n### [`v2.53.3`](https://togithub.com/rollup/rollup/blob/master/CHANGELOG.md#​2533)\n\n[Compare Source](https://togithub.com/rollup/rollup/compare/v2.53.2...v2.53.3)\n\n*2021-07-21*\n\n##### Bug Fixes\n\n- Solve an issue that could lead to severe memory issues and crashes when there are a lot of hoisted variables ([#​4183](https://togithub.com/rollup/rollup/issues/4183))\n\n##### Pull Requests\n\n- [#​4183](https://togithub.com/rollup/rollup/pull/4183): Avoid memory issues with hoisted variables ([@​lukastaegert](https://togithub.com/lukastaegert))\n\n
    \n\n---\n\n### Configuration\n\n📅 **Schedule**: At any time (no schedule defined).\n\n🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.\n\n♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.\n\n🔕 **Ignore**: Close this PR and you won't be reminded about this update again.\n\n---\n\n - [ ] If you want to rebase/retry this PR, check this box.\n\n---\n\nThis PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/esm-bundle/markdown-it-emoji).","created_at":"2021-07-21T07:59:07Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:03Z","merged_at":"2021-07-21T08:00:03Z","merge_commit_sha":"179af100fa383064b40c6358632f2531cbc8f4b7","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/62/commits","review_comments_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/62/comments","review_comment_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/comments{/number}","comments_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/62/comments","statuses_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/statuses/65a4c4fef87320c962844ce366dd66be66040f79","head":{"label":"esm-bundle:renovate/rollup-2.x","ref":"renovate/rollup-2.x","sha":"65a4c4fef87320c962844ce366dd66be66040f79","user":{"login":"esm-bundle","id":60286277,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYwMjg2Mjc3","avatar_url":"https://avatars.githubusercontent.com/u/60286277?v=4","gravatar_id":"","url":"https://api.github.com/users/esm-bundle","html_url":"https://github.com/esm-bundle","followers_url":"https://api.github.com/users/esm-bundle/followers","following_url":"https://api.github.com/users/esm-bundle/following{/other_user}","gists_url":"https://api.github.com/users/esm-bundle/gists{/gist_id}","starred_url":"https://api.github.com/users/esm-bundle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/esm-bundle/subscriptions","organizations_url":"https://api.github.com/users/esm-bundle/orgs","repos_url":"https://api.github.com/users/esm-bundle/repos","events_url":"https://api.github.com/users/esm-bundle/events{/privacy}","received_events_url":"https://api.github.com/users/esm-bundle/received_events","type":"Organization","site_admin":false},"repo":{"id":352747621,"node_id":"MDEwOlJlcG9zaXRvcnkzNTI3NDc2MjE=","name":"markdown-it-emoji","full_name":"esm-bundle/markdown-it-emoji","private":false,"owner":{"login":"esm-bundle","id":60286277,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYwMjg2Mjc3","avatar_url":"https://avatars.githubusercontent.com/u/60286277?v=4","gravatar_id":"","url":"https://api.github.com/users/esm-bundle","html_url":"https://github.com/esm-bundle","followers_url":"https://api.github.com/users/esm-bundle/followers","following_url":"https://api.github.com/users/esm-bundle/following{/other_user}","gists_url":"https://api.github.com/users/esm-bundle/gists{/gist_id}","starred_url":"https://api.github.com/users/esm-bundle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/esm-bundle/subscriptions","organizations_url":"https://api.github.com/users/esm-bundle/orgs","repos_url":"https://api.github.com/users/esm-bundle/repos","events_url":"https://api.github.com/users/esm-bundle/events{/privacy}","received_events_url":"https://api.github.com/users/esm-bundle/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/esm-bundle/markdown-it-emoji","description":null,"fork":false,"url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji","forks_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/forks","keys_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/keys{/key_id}","collaborators_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/teams","hooks_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/hooks","issue_events_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/events{/number}","events_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/events","assignees_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/assignees{/user}","branches_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/branches{/branch}","tags_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/tags","blobs_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/refs{/sha}","trees_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/trees{/sha}","statuses_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/statuses/{sha}","languages_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/languages","stargazers_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/stargazers","contributors_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/contributors","subscribers_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/subscribers","subscription_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/subscription","commits_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/commits{/sha}","git_commits_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/commits{/sha}","comments_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/comments{/number}","issue_comment_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/comments{/number}","contents_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/contents/{+path}","compare_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/compare/{base}...{head}","merges_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/merges","archive_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/downloads","issues_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues{/number}","pulls_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls{/number}","milestones_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/milestones{/number}","notifications_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/labels{/name}","releases_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/releases{/id}","deployments_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/deployments","created_at":"2021-03-29T18:36:04Z","updated_at":"2021-07-15T23:47:28Z","pushed_at":"2021-07-21T08:00:03Z","git_url":"git://github.com/esm-bundle/markdown-it-emoji.git","ssh_url":"git@github.com:esm-bundle/markdown-it-emoji.git","clone_url":"https://github.com/esm-bundle/markdown-it-emoji.git","svn_url":"https://github.com/esm-bundle/markdown-it-emoji","homepage":null,"size":483,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":1,"open_issues":0,"watchers":0,"default_branch":"main"}},"base":{"label":"esm-bundle:main","ref":"main","sha":"68fdce2a9921331aaf8d208e93064aba36b20144","user":{"login":"esm-bundle","id":60286277,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYwMjg2Mjc3","avatar_url":"https://avatars.githubusercontent.com/u/60286277?v=4","gravatar_id":"","url":"https://api.github.com/users/esm-bundle","html_url":"https://github.com/esm-bundle","followers_url":"https://api.github.com/users/esm-bundle/followers","following_url":"https://api.github.com/users/esm-bundle/following{/other_user}","gists_url":"https://api.github.com/users/esm-bundle/gists{/gist_id}","starred_url":"https://api.github.com/users/esm-bundle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/esm-bundle/subscriptions","organizations_url":"https://api.github.com/users/esm-bundle/orgs","repos_url":"https://api.github.com/users/esm-bundle/repos","events_url":"https://api.github.com/users/esm-bundle/events{/privacy}","received_events_url":"https://api.github.com/users/esm-bundle/received_events","type":"Organization","site_admin":false},"repo":{"id":352747621,"node_id":"MDEwOlJlcG9zaXRvcnkzNTI3NDc2MjE=","name":"markdown-it-emoji","full_name":"esm-bundle/markdown-it-emoji","private":false,"owner":{"login":"esm-bundle","id":60286277,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYwMjg2Mjc3","avatar_url":"https://avatars.githubusercontent.com/u/60286277?v=4","gravatar_id":"","url":"https://api.github.com/users/esm-bundle","html_url":"https://github.com/esm-bundle","followers_url":"https://api.github.com/users/esm-bundle/followers","following_url":"https://api.github.com/users/esm-bundle/following{/other_user}","gists_url":"https://api.github.com/users/esm-bundle/gists{/gist_id}","starred_url":"https://api.github.com/users/esm-bundle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/esm-bundle/subscriptions","organizations_url":"https://api.github.com/users/esm-bundle/orgs","repos_url":"https://api.github.com/users/esm-bundle/repos","events_url":"https://api.github.com/users/esm-bundle/events{/privacy}","received_events_url":"https://api.github.com/users/esm-bundle/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/esm-bundle/markdown-it-emoji","description":null,"fork":false,"url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji","forks_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/forks","keys_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/keys{/key_id}","collaborators_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/teams","hooks_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/hooks","issue_events_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/events{/number}","events_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/events","assignees_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/assignees{/user}","branches_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/branches{/branch}","tags_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/tags","blobs_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/refs{/sha}","trees_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/trees{/sha}","statuses_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/statuses/{sha}","languages_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/languages","stargazers_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/stargazers","contributors_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/contributors","subscribers_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/subscribers","subscription_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/subscription","commits_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/commits{/sha}","git_commits_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/git/commits{/sha}","comments_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/comments{/number}","issue_comment_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/comments{/number}","contents_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/contents/{+path}","compare_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/compare/{base}...{head}","merges_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/merges","archive_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/downloads","issues_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues{/number}","pulls_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls{/number}","milestones_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/milestones{/number}","notifications_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/labels{/name}","releases_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/releases{/id}","deployments_url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/deployments","created_at":"2021-03-29T18:36:04Z","updated_at":"2021-07-15T23:47:28Z","pushed_at":"2021-07-21T08:00:03Z","git_url":"git://github.com/esm-bundle/markdown-it-emoji.git","ssh_url":"git@github.com:esm-bundle/markdown-it-emoji.git","clone_url":"https://github.com/esm-bundle/markdown-it-emoji.git","svn_url":"https://github.com/esm-bundle/markdown-it-emoji","homepage":null,"size":483,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":1,"open_issues":0,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/62"},"html":{"href":"https://github.com/esm-bundle/markdown-it-emoji/pull/62"},"issue":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/62"},"comments":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/issues/62/comments"},"review_comments":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/62/comments"},"review_comment":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/pulls/62/commits"},"statuses":{"href":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/statuses/65a4c4fef87320c962844ce366dd66be66040f79"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"kodiakhq[bot]","id":49736102,"node_id":"MDM6Qm90NDk3MzYxMDI=","avatar_url":"https://avatars.githubusercontent.com/in/29196?v=4","gravatar_id":"","url":"https://api.github.com/users/kodiakhq%5Bbot%5D","html_url":"https://github.com/apps/kodiakhq","followers_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/followers","following_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/repos","events_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/kodiakhq%5Bbot%5D/received_events","type":"Bot","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":22,"deletions":22,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":60286277,"login":"esm-bundle","gravatar_id":"","url":"https://api.github.com/orgs/esm-bundle","avatar_url":"https://avatars.githubusercontent.com/u/60286277?"}} +{"id":"17245515433","type":"CreateEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":337718447,"name":"Alberto-S-P/Boilerplate_Nextjs","url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs"},"payload":{"ref":"dependabot/npm_and_yarn/types/node-16.4.0","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515438","type":"DeleteEvent","actor":{"id":7659712,"login":"zenkins","display_login":"zenkins","gravatar_id":"","url":"https://api.github.com/users/zenkins","avatar_url":"https://avatars.githubusercontent.com/u/7659712?"},"repo":{"id":53574465,"name":"wireapp/wire-ios-cryptobox","url":"https://api.github.com/repos/wireapp/wire-ios-cryptobox"},"payload":{"ref":"chore/bump_2021-07-21-075451","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":16047324,"login":"wireapp","gravatar_id":"","url":"https://api.github.com/orgs/wireapp","avatar_url":"https://avatars.githubusercontent.com/u/16047324?"}} +{"id":"17245515445","type":"PushEvent","actor":{"id":9197716,"login":"ChrisRG","display_login":"ChrisRG","gravatar_id":"","url":"https://api.github.com/users/ChrisRG","avatar_url":"https://avatars.githubusercontent.com/u/9197716?"},"repo":{"id":387739023,"name":"ChrisRG/lewagon","url":"https://api.github.com/repos/ChrisRG/lewagon"},"payload":{"push_id":7562055401,"size":2,"distinct_size":1,"ref":"refs/heads/main","head":"802fb53c6b970df7d94dee03b772d8d0f1766f4f","before":"bdd0f038633581b1395b49cc1c2dd18ba666ea19","commits":[{"sha":"345771f53451684968e72336b285dc8e37869eb1","author":{"name":"Marcel Fonseca","email":"71eb495dfc5d0c27967d69c49874ccba6cedb3f7@gmail.com"},"message":"finished signup logic","distinct":false,"url":"https://api.github.com/repos/ChrisRG/lewagon/commits/345771f53451684968e72336b285dc8e37869eb1"},{"sha":"802fb53c6b970df7d94dee03b772d8d0f1766f4f","author":{"name":"Chris Geekie","email":"7b73d1a37a0d4016d238674803908da2429cfa5d@users.noreply.github.com"},"message":"Merge pull request #1 from ChrisRG/sign-up-logic\n\nfinished signup logic","distinct":true,"url":"https://api.github.com/repos/ChrisRG/lewagon/commits/802fb53c6b970df7d94dee03b772d8d0f1766f4f"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515461","type":"CreateEvent","actor":{"id":66831205,"login":"charlesmmarshall","display_login":"charlesmmarshall","gravatar_id":"","url":"https://api.github.com/users/charlesmmarshall","avatar_url":"https://avatars.githubusercontent.com/u/66831205?"},"repo":{"id":388040092,"name":"ministryofjustice/opg-infra-costs-action","url":"https://api.github.com/repos/ministryofjustice/opg-infra-costs-action"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":"Github action to generate AWS cost data: Managed by opg-org-infra & Terraform","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":2203574,"login":"ministryofjustice","gravatar_id":"","url":"https://api.github.com/orgs/ministryofjustice","avatar_url":"https://avatars.githubusercontent.com/u/2203574?"}} +{"id":"17245515477","type":"PushEvent","actor":{"id":3379460,"login":"beanslee2012","display_login":"beanslee2012","gravatar_id":"","url":"https://api.github.com/users/beanslee2012","avatar_url":"https://avatars.githubusercontent.com/u/3379460?"},"repo":{"id":215003385,"name":"beanslee2012/games","url":"https://api.github.com/repos/beanslee2012/games"},"payload":{"push_id":7562055416,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"828a7e3fde4d5b3dd564e7064ea09361523439d2","before":"2c1ffaff60d52f1094427848ae9c6f326d821ca4","commits":[{"sha":"828a7e3fde4d5b3dd564e7064ea09361523439d2","author":{"name":"beanslee2012","email":"9aadcc12fe6ab8c01e6245204a102e9e34317220@gmail.com"},"message":"daily update","distinct":true,"url":"https://api.github.com/repos/beanslee2012/games/commits/828a7e3fde4d5b3dd564e7064ea09361523439d2"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515482","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7562055408,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"406a8945fddc6662b6816d96e7e9d21a8f088687","before":"1f46d3439c36fbf8ce98c15df365be16fcd33ef4","commits":[{"sha":"406a8945fddc6662b6816d96e7e9d21a8f088687","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/406a8945fddc6662b6816d96e7e9d21a8f088687"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515486","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":249783941,"name":"miwebst/ssRunnerAngular","url":"https://api.github.com/repos/miwebst/ssRunnerAngular"},"payload":{"push_id":7562055417,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"3746f2fb01535a590f2d708471ca3f7ac849856b","before":"cc57825ea136c09cb0920dfbb4abbc473d44a7f7","commits":[{"sha":"3746f2fb01535a590f2d708471ca3f7ac849856b","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 8:00:03 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunnerAngular/commits/3746f2fb01535a590f2d708471ca3f7ac849856b"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515490","type":"PushEvent","actor":{"id":40586421,"login":"himobi","display_login":"himobi","gravatar_id":"","url":"https://api.github.com/users/himobi","avatar_url":"https://avatars.githubusercontent.com/u/40586421?"},"repo":{"id":138676186,"name":"himobi/hotspot","url":"https://api.github.com/repos/himobi/hotspot"},"payload":{"push_id":7562055414,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"cd0cdb510c3582ac98540b6700fa98d2a52855eb","before":"2eb2a9639e315e9f2d53cc98a5d1ff686833a145","commits":[{"sha":"cd0cdb510c3582ac98540b6700fa98d2a52855eb","author":{"name":"himobi","email":"25be52bce459ebff50c126f5ac512648f87cbe75@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/himobi/hotspot/commits/cd0cdb510c3582ac98540b6700fa98d2a52855eb"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515491","type":"PushEvent","actor":{"id":82876799,"login":"hvaish01","display_login":"hvaish01","gravatar_id":"","url":"https://api.github.com/users/hvaish01","avatar_url":"https://avatars.githubusercontent.com/u/82876799?"},"repo":{"id":359772248,"name":"hvaish01/StixBundles","url":"https://api.github.com/repos/hvaish01/StixBundles"},"payload":{"push_id":7562055415,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"73506f5776e1449063f1ff0220446408fb6865aa","before":"b6f5403b6ccf526d65c6e01b67845dc685135437","commits":[{"sha":"73506f5776e1449063f1ff0220446408fb6865aa","author":{"name":"hvaish01","email":"2afd248fb67ac19cdeb0148e32ca7ee71c30c76d@users.noreply.github.com"},"message":"Updated at 01:00:02 21-07-2021","distinct":true,"url":"https://api.github.com/repos/hvaish01/StixBundles/commits/73506f5776e1449063f1ff0220446408fb6865aa"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515509","type":"PullRequestEvent","actor":{"id":43602284,"login":"urbiz-nr","display_login":"urbiz-nr","gravatar_id":"","url":"https://api.github.com/users/urbiz-nr","avatar_url":"https://avatars.githubusercontent.com/u/43602284?"},"repo":{"id":287113304,"name":"newrelic/docs-website","url":"https://api.github.com/repos/newrelic/docs-website"},"payload":{"action":"opened","number":3204,"pull_request":{"url":"https://api.github.com/repos/newrelic/docs-website/pulls/3204","id":694141693,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNjkz","html_url":"https://github.com/newrelic/docs-website/pull/3204","diff_url":"https://github.com/newrelic/docs-website/pull/3204.diff","patch_url":"https://github.com/newrelic/docs-website/pull/3204.patch","issue_url":"https://api.github.com/repos/newrelic/docs-website/issues/3204","number":3204,"state":"open","locked":false,"title":"Deleting campfire orphan doc","user":{"login":"urbiz-nr","id":43602284,"node_id":"MDQ6VXNlcjQzNjAyMjg0","avatar_url":"https://avatars.githubusercontent.com/u/43602284?v=4","gravatar_id":"","url":"https://api.github.com/users/urbiz-nr","html_url":"https://github.com/urbiz-nr","followers_url":"https://api.github.com/users/urbiz-nr/followers","following_url":"https://api.github.com/users/urbiz-nr/following{/other_user}","gists_url":"https://api.github.com/users/urbiz-nr/gists{/gist_id}","starred_url":"https://api.github.com/users/urbiz-nr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/urbiz-nr/subscriptions","organizations_url":"https://api.github.com/users/urbiz-nr/orgs","repos_url":"https://api.github.com/users/urbiz-nr/repos","events_url":"https://api.github.com/users/urbiz-nr/events{/privacy}","received_events_url":"https://api.github.com/users/urbiz-nr/received_events","type":"User","site_admin":false},"body":"Leftover from the Orphan doc review project DOC-6956.","created_at":"2021-07-21T08:00:03Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/newrelic/docs-website/pulls/3204/commits","review_comments_url":"https://api.github.com/repos/newrelic/docs-website/pulls/3204/comments","review_comment_url":"https://api.github.com/repos/newrelic/docs-website/pulls/comments{/number}","comments_url":"https://api.github.com/repos/newrelic/docs-website/issues/3204/comments","statuses_url":"https://api.github.com/repos/newrelic/docs-website/statuses/242cb5d53668e891248903dfb97c2e98c6e17727","head":{"label":"newrelic:Removing-alerts-orphan-doc","ref":"Removing-alerts-orphan-doc","sha":"242cb5d53668e891248903dfb97c2e98c6e17727","user":{"login":"newrelic","id":31739,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzM5","avatar_url":"https://avatars.githubusercontent.com/u/31739?v=4","gravatar_id":"","url":"https://api.github.com/users/newrelic","html_url":"https://github.com/newrelic","followers_url":"https://api.github.com/users/newrelic/followers","following_url":"https://api.github.com/users/newrelic/following{/other_user}","gists_url":"https://api.github.com/users/newrelic/gists{/gist_id}","starred_url":"https://api.github.com/users/newrelic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/newrelic/subscriptions","organizations_url":"https://api.github.com/users/newrelic/orgs","repos_url":"https://api.github.com/users/newrelic/repos","events_url":"https://api.github.com/users/newrelic/events{/privacy}","received_events_url":"https://api.github.com/users/newrelic/received_events","type":"Organization","site_admin":false},"repo":{"id":287113304,"node_id":"MDEwOlJlcG9zaXRvcnkyODcxMTMzMDQ=","name":"docs-website","full_name":"newrelic/docs-website","private":false,"owner":{"login":"newrelic","id":31739,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzM5","avatar_url":"https://avatars.githubusercontent.com/u/31739?v=4","gravatar_id":"","url":"https://api.github.com/users/newrelic","html_url":"https://github.com/newrelic","followers_url":"https://api.github.com/users/newrelic/followers","following_url":"https://api.github.com/users/newrelic/following{/other_user}","gists_url":"https://api.github.com/users/newrelic/gists{/gist_id}","starred_url":"https://api.github.com/users/newrelic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/newrelic/subscriptions","organizations_url":"https://api.github.com/users/newrelic/orgs","repos_url":"https://api.github.com/users/newrelic/repos","events_url":"https://api.github.com/users/newrelic/events{/privacy}","received_events_url":"https://api.github.com/users/newrelic/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/newrelic/docs-website","description":"Source code and public issue backlog for @newrelic docs.","fork":false,"url":"https://api.github.com/repos/newrelic/docs-website","forks_url":"https://api.github.com/repos/newrelic/docs-website/forks","keys_url":"https://api.github.com/repos/newrelic/docs-website/keys{/key_id}","collaborators_url":"https://api.github.com/repos/newrelic/docs-website/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/newrelic/docs-website/teams","hooks_url":"https://api.github.com/repos/newrelic/docs-website/hooks","issue_events_url":"https://api.github.com/repos/newrelic/docs-website/issues/events{/number}","events_url":"https://api.github.com/repos/newrelic/docs-website/events","assignees_url":"https://api.github.com/repos/newrelic/docs-website/assignees{/user}","branches_url":"https://api.github.com/repos/newrelic/docs-website/branches{/branch}","tags_url":"https://api.github.com/repos/newrelic/docs-website/tags","blobs_url":"https://api.github.com/repos/newrelic/docs-website/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/newrelic/docs-website/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/newrelic/docs-website/git/refs{/sha}","trees_url":"https://api.github.com/repos/newrelic/docs-website/git/trees{/sha}","statuses_url":"https://api.github.com/repos/newrelic/docs-website/statuses/{sha}","languages_url":"https://api.github.com/repos/newrelic/docs-website/languages","stargazers_url":"https://api.github.com/repos/newrelic/docs-website/stargazers","contributors_url":"https://api.github.com/repos/newrelic/docs-website/contributors","subscribers_url":"https://api.github.com/repos/newrelic/docs-website/subscribers","subscription_url":"https://api.github.com/repos/newrelic/docs-website/subscription","commits_url":"https://api.github.com/repos/newrelic/docs-website/commits{/sha}","git_commits_url":"https://api.github.com/repos/newrelic/docs-website/git/commits{/sha}","comments_url":"https://api.github.com/repos/newrelic/docs-website/comments{/number}","issue_comment_url":"https://api.github.com/repos/newrelic/docs-website/issues/comments{/number}","contents_url":"https://api.github.com/repos/newrelic/docs-website/contents/{+path}","compare_url":"https://api.github.com/repos/newrelic/docs-website/compare/{base}...{head}","merges_url":"https://api.github.com/repos/newrelic/docs-website/merges","archive_url":"https://api.github.com/repos/newrelic/docs-website/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/newrelic/docs-website/downloads","issues_url":"https://api.github.com/repos/newrelic/docs-website/issues{/number}","pulls_url":"https://api.github.com/repos/newrelic/docs-website/pulls{/number}","milestones_url":"https://api.github.com/repos/newrelic/docs-website/milestones{/number}","notifications_url":"https://api.github.com/repos/newrelic/docs-website/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/newrelic/docs-website/labels{/name}","releases_url":"https://api.github.com/repos/newrelic/docs-website/releases{/id}","deployments_url":"https://api.github.com/repos/newrelic/docs-website/deployments","created_at":"2020-08-12T20:52:25Z","updated_at":"2021-07-20T23:54:27Z","pushed_at":"2021-07-21T08:00:04Z","git_url":"git://github.com/newrelic/docs-website.git","ssh_url":"git@github.com:newrelic/docs-website.git","clone_url":"https://github.com/newrelic/docs-website.git","svn_url":"https://github.com/newrelic/docs-website","homepage":"https://docs.newrelic.com","size":302903,"stargazers_count":33,"watchers_count":33,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":269,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":161,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":269,"open_issues":161,"watchers":33,"default_branch":"develop"}},"base":{"label":"newrelic:develop","ref":"develop","sha":"b2f0b84bb8f1cfd05dc88f27647f77b3b4efd41f","user":{"login":"newrelic","id":31739,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzM5","avatar_url":"https://avatars.githubusercontent.com/u/31739?v=4","gravatar_id":"","url":"https://api.github.com/users/newrelic","html_url":"https://github.com/newrelic","followers_url":"https://api.github.com/users/newrelic/followers","following_url":"https://api.github.com/users/newrelic/following{/other_user}","gists_url":"https://api.github.com/users/newrelic/gists{/gist_id}","starred_url":"https://api.github.com/users/newrelic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/newrelic/subscriptions","organizations_url":"https://api.github.com/users/newrelic/orgs","repos_url":"https://api.github.com/users/newrelic/repos","events_url":"https://api.github.com/users/newrelic/events{/privacy}","received_events_url":"https://api.github.com/users/newrelic/received_events","type":"Organization","site_admin":false},"repo":{"id":287113304,"node_id":"MDEwOlJlcG9zaXRvcnkyODcxMTMzMDQ=","name":"docs-website","full_name":"newrelic/docs-website","private":false,"owner":{"login":"newrelic","id":31739,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzM5","avatar_url":"https://avatars.githubusercontent.com/u/31739?v=4","gravatar_id":"","url":"https://api.github.com/users/newrelic","html_url":"https://github.com/newrelic","followers_url":"https://api.github.com/users/newrelic/followers","following_url":"https://api.github.com/users/newrelic/following{/other_user}","gists_url":"https://api.github.com/users/newrelic/gists{/gist_id}","starred_url":"https://api.github.com/users/newrelic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/newrelic/subscriptions","organizations_url":"https://api.github.com/users/newrelic/orgs","repos_url":"https://api.github.com/users/newrelic/repos","events_url":"https://api.github.com/users/newrelic/events{/privacy}","received_events_url":"https://api.github.com/users/newrelic/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/newrelic/docs-website","description":"Source code and public issue backlog for @newrelic docs.","fork":false,"url":"https://api.github.com/repos/newrelic/docs-website","forks_url":"https://api.github.com/repos/newrelic/docs-website/forks","keys_url":"https://api.github.com/repos/newrelic/docs-website/keys{/key_id}","collaborators_url":"https://api.github.com/repos/newrelic/docs-website/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/newrelic/docs-website/teams","hooks_url":"https://api.github.com/repos/newrelic/docs-website/hooks","issue_events_url":"https://api.github.com/repos/newrelic/docs-website/issues/events{/number}","events_url":"https://api.github.com/repos/newrelic/docs-website/events","assignees_url":"https://api.github.com/repos/newrelic/docs-website/assignees{/user}","branches_url":"https://api.github.com/repos/newrelic/docs-website/branches{/branch}","tags_url":"https://api.github.com/repos/newrelic/docs-website/tags","blobs_url":"https://api.github.com/repos/newrelic/docs-website/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/newrelic/docs-website/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/newrelic/docs-website/git/refs{/sha}","trees_url":"https://api.github.com/repos/newrelic/docs-website/git/trees{/sha}","statuses_url":"https://api.github.com/repos/newrelic/docs-website/statuses/{sha}","languages_url":"https://api.github.com/repos/newrelic/docs-website/languages","stargazers_url":"https://api.github.com/repos/newrelic/docs-website/stargazers","contributors_url":"https://api.github.com/repos/newrelic/docs-website/contributors","subscribers_url":"https://api.github.com/repos/newrelic/docs-website/subscribers","subscription_url":"https://api.github.com/repos/newrelic/docs-website/subscription","commits_url":"https://api.github.com/repos/newrelic/docs-website/commits{/sha}","git_commits_url":"https://api.github.com/repos/newrelic/docs-website/git/commits{/sha}","comments_url":"https://api.github.com/repos/newrelic/docs-website/comments{/number}","issue_comment_url":"https://api.github.com/repos/newrelic/docs-website/issues/comments{/number}","contents_url":"https://api.github.com/repos/newrelic/docs-website/contents/{+path}","compare_url":"https://api.github.com/repos/newrelic/docs-website/compare/{base}...{head}","merges_url":"https://api.github.com/repos/newrelic/docs-website/merges","archive_url":"https://api.github.com/repos/newrelic/docs-website/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/newrelic/docs-website/downloads","issues_url":"https://api.github.com/repos/newrelic/docs-website/issues{/number}","pulls_url":"https://api.github.com/repos/newrelic/docs-website/pulls{/number}","milestones_url":"https://api.github.com/repos/newrelic/docs-website/milestones{/number}","notifications_url":"https://api.github.com/repos/newrelic/docs-website/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/newrelic/docs-website/labels{/name}","releases_url":"https://api.github.com/repos/newrelic/docs-website/releases{/id}","deployments_url":"https://api.github.com/repos/newrelic/docs-website/deployments","created_at":"2020-08-12T20:52:25Z","updated_at":"2021-07-20T23:54:27Z","pushed_at":"2021-07-21T08:00:04Z","git_url":"git://github.com/newrelic/docs-website.git","ssh_url":"git@github.com:newrelic/docs-website.git","clone_url":"https://github.com/newrelic/docs-website.git","svn_url":"https://github.com/newrelic/docs-website","homepage":"https://docs.newrelic.com","size":302903,"stargazers_count":33,"watchers_count":33,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":269,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":161,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":269,"open_issues":161,"watchers":33,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/newrelic/docs-website/pulls/3204"},"html":{"href":"https://github.com/newrelic/docs-website/pull/3204"},"issue":{"href":"https://api.github.com/repos/newrelic/docs-website/issues/3204"},"comments":{"href":"https://api.github.com/repos/newrelic/docs-website/issues/3204/comments"},"review_comments":{"href":"https://api.github.com/repos/newrelic/docs-website/pulls/3204/comments"},"review_comment":{"href":"https://api.github.com/repos/newrelic/docs-website/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/newrelic/docs-website/pulls/3204/commits"},"statuses":{"href":"https://api.github.com/repos/newrelic/docs-website/statuses/242cb5d53668e891248903dfb97c2e98c6e17727"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":0,"deletions":34,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":31739,"login":"newrelic","gravatar_id":"","url":"https://api.github.com/orgs/newrelic","avatar_url":"https://avatars.githubusercontent.com/u/31739?"}} +{"id":"17245515511","type":"PushEvent","actor":{"id":878058,"login":"ParkMinKyu","display_login":"ParkMinKyu","gravatar_id":"","url":"https://api.github.com/users/ParkMinKyu","avatar_url":"https://avatars.githubusercontent.com/u/878058?"},"repo":{"id":362471221,"name":"ApartMoney/Apartmoney.github.io","url":"https://api.github.com/repos/ApartMoney/Apartmoney.github.io"},"payload":{"push_id":7562055423,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"26905cbb104f4d6021935c878009df18632786d3","before":"6d674aad9aa3ab4961d8530422886323115fc825","commits":[{"sha":"26905cbb104f4d6021935c878009df18632786d3","author":{"name":"ParkMinkyu","email":"f1d6fc1b4c5023402fddfd826b843346eec78543@naver.com"},"message":"update news data","distinct":true,"url":"https://api.github.com/repos/ApartMoney/Apartmoney.github.io/commits/26905cbb104f4d6021935c878009df18632786d3"}]},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":60907646,"login":"ApartMoney","gravatar_id":"","url":"https://api.github.com/orgs/ApartMoney","avatar_url":"https://avatars.githubusercontent.com/u/60907646?"}} +{"id":"17245515520","type":"PullRequestEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":337718447,"name":"Alberto-S-P/Boilerplate_Nextjs","url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs"},"payload":{"action":"opened","number":86,"pull_request":{"url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/86","id":694141690,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNjkw","html_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/86","diff_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/86.diff","patch_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/86.patch","issue_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/86","number":86,"state":"open","locked":false,"title":"Bump @types/node from 14.14.25 to 16.4.0","user":{"login":"dependabot-preview[bot]","id":27856297,"node_id":"MDM6Qm90Mjc4NTYyOTc=","avatar_url":"https://avatars.githubusercontent.com/in/2141?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview%5Bbot%5D","html_url":"https://github.com/apps/dependabot-preview","followers_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.25 to 16.4.0.\n
    \nCommits\n\n
    \n
    \n\n\n[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=14.14.25&new-version=16.4.0)](https://dependabot.com/compatibility-score/?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=14.14.25&new-version=16.4.0)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language\n- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language\n- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language\n- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language\n- `@dependabot badge me` will comment on this PR with code to add a \"Dependabot enabled\" badge to your readme\n\nAdditionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):\n- Update frequency (including time of day and day of week)\n- Pull request limits (per update run and/or open at any time)\n- Out-of-range updates (receive only lockfile updates, if desired)\n- Security updates (receive only security updates, if desired)\n\n\n\n
    ","created_at":"2021-07-21T08:00:03Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/86/commits","review_comments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/86/comments","review_comment_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/86/comments","statuses_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/statuses/8243f97eaaa9a4844f460c21c144c571638db940","head":{"label":"Alberto-S-P:dependabot/npm_and_yarn/types/node-16.4.0","ref":"dependabot/npm_and_yarn/types/node-16.4.0","sha":"8243f97eaaa9a4844f460c21c144c571638db940","user":{"login":"Alberto-S-P","id":77673768,"node_id":"MDQ6VXNlcjc3NjczNzY4","avatar_url":"https://avatars.githubusercontent.com/u/77673768?v=4","gravatar_id":"","url":"https://api.github.com/users/Alberto-S-P","html_url":"https://github.com/Alberto-S-P","followers_url":"https://api.github.com/users/Alberto-S-P/followers","following_url":"https://api.github.com/users/Alberto-S-P/following{/other_user}","gists_url":"https://api.github.com/users/Alberto-S-P/gists{/gist_id}","starred_url":"https://api.github.com/users/Alberto-S-P/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Alberto-S-P/subscriptions","organizations_url":"https://api.github.com/users/Alberto-S-P/orgs","repos_url":"https://api.github.com/users/Alberto-S-P/repos","events_url":"https://api.github.com/users/Alberto-S-P/events{/privacy}","received_events_url":"https://api.github.com/users/Alberto-S-P/received_events","type":"User","site_admin":false},"repo":{"id":337718447,"node_id":"MDEwOlJlcG9zaXRvcnkzMzc3MTg0NDc=","name":"Boilerplate_Nextjs","full_name":"Alberto-S-P/Boilerplate_Nextjs","private":false,"owner":{"login":"Alberto-S-P","id":77673768,"node_id":"MDQ6VXNlcjc3NjczNzY4","avatar_url":"https://avatars.githubusercontent.com/u/77673768?v=4","gravatar_id":"","url":"https://api.github.com/users/Alberto-S-P","html_url":"https://github.com/Alberto-S-P","followers_url":"https://api.github.com/users/Alberto-S-P/followers","following_url":"https://api.github.com/users/Alberto-S-P/following{/other_user}","gists_url":"https://api.github.com/users/Alberto-S-P/gists{/gist_id}","starred_url":"https://api.github.com/users/Alberto-S-P/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Alberto-S-P/subscriptions","organizations_url":"https://api.github.com/users/Alberto-S-P/orgs","repos_url":"https://api.github.com/users/Alberto-S-P/repos","events_url":"https://api.github.com/users/Alberto-S-P/events{/privacy}","received_events_url":"https://api.github.com/users/Alberto-S-P/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs","description":null,"fork":false,"url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs","forks_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/forks","keys_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/teams","hooks_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/hooks","issue_events_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/events{/number}","events_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/events","assignees_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/assignees{/user}","branches_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/branches{/branch}","tags_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/tags","blobs_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/refs{/sha}","trees_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/statuses/{sha}","languages_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/languages","stargazers_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/stargazers","contributors_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/contributors","subscribers_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/subscribers","subscription_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/subscription","commits_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/commits{/sha}","git_commits_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/commits{/sha}","comments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/comments{/number}","issue_comment_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/comments{/number}","contents_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/contents/{+path}","compare_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/merges","archive_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/downloads","issues_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues{/number}","pulls_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls{/number}","milestones_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/milestones{/number}","notifications_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/labels{/name}","releases_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/releases{/id}","deployments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/deployments","created_at":"2021-02-10T12:33:10Z","updated_at":"2021-04-09T01:45:06Z","pushed_at":"2021-07-21T08:00:04Z","git_url":"git://github.com/Alberto-S-P/Boilerplate_Nextjs.git","ssh_url":"git@github.com:Alberto-S-P/Boilerplate_Nextjs.git","clone_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs.git","svn_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs","homepage":null,"size":1471,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":12,"license":null,"forks":0,"open_issues":12,"watchers":0,"default_branch":"main"}},"base":{"label":"Alberto-S-P:main","ref":"main","sha":"e029c42d45fb29e13b99bbf3f7b9bfbec24d672f","user":{"login":"Alberto-S-P","id":77673768,"node_id":"MDQ6VXNlcjc3NjczNzY4","avatar_url":"https://avatars.githubusercontent.com/u/77673768?v=4","gravatar_id":"","url":"https://api.github.com/users/Alberto-S-P","html_url":"https://github.com/Alberto-S-P","followers_url":"https://api.github.com/users/Alberto-S-P/followers","following_url":"https://api.github.com/users/Alberto-S-P/following{/other_user}","gists_url":"https://api.github.com/users/Alberto-S-P/gists{/gist_id}","starred_url":"https://api.github.com/users/Alberto-S-P/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Alberto-S-P/subscriptions","organizations_url":"https://api.github.com/users/Alberto-S-P/orgs","repos_url":"https://api.github.com/users/Alberto-S-P/repos","events_url":"https://api.github.com/users/Alberto-S-P/events{/privacy}","received_events_url":"https://api.github.com/users/Alberto-S-P/received_events","type":"User","site_admin":false},"repo":{"id":337718447,"node_id":"MDEwOlJlcG9zaXRvcnkzMzc3MTg0NDc=","name":"Boilerplate_Nextjs","full_name":"Alberto-S-P/Boilerplate_Nextjs","private":false,"owner":{"login":"Alberto-S-P","id":77673768,"node_id":"MDQ6VXNlcjc3NjczNzY4","avatar_url":"https://avatars.githubusercontent.com/u/77673768?v=4","gravatar_id":"","url":"https://api.github.com/users/Alberto-S-P","html_url":"https://github.com/Alberto-S-P","followers_url":"https://api.github.com/users/Alberto-S-P/followers","following_url":"https://api.github.com/users/Alberto-S-P/following{/other_user}","gists_url":"https://api.github.com/users/Alberto-S-P/gists{/gist_id}","starred_url":"https://api.github.com/users/Alberto-S-P/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Alberto-S-P/subscriptions","organizations_url":"https://api.github.com/users/Alberto-S-P/orgs","repos_url":"https://api.github.com/users/Alberto-S-P/repos","events_url":"https://api.github.com/users/Alberto-S-P/events{/privacy}","received_events_url":"https://api.github.com/users/Alberto-S-P/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs","description":null,"fork":false,"url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs","forks_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/forks","keys_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/teams","hooks_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/hooks","issue_events_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/events{/number}","events_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/events","assignees_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/assignees{/user}","branches_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/branches{/branch}","tags_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/tags","blobs_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/refs{/sha}","trees_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/statuses/{sha}","languages_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/languages","stargazers_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/stargazers","contributors_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/contributors","subscribers_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/subscribers","subscription_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/subscription","commits_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/commits{/sha}","git_commits_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/git/commits{/sha}","comments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/comments{/number}","issue_comment_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/comments{/number}","contents_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/contents/{+path}","compare_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/merges","archive_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/downloads","issues_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues{/number}","pulls_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls{/number}","milestones_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/milestones{/number}","notifications_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/labels{/name}","releases_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/releases{/id}","deployments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/deployments","created_at":"2021-02-10T12:33:10Z","updated_at":"2021-04-09T01:45:06Z","pushed_at":"2021-07-21T08:00:04Z","git_url":"git://github.com/Alberto-S-P/Boilerplate_Nextjs.git","ssh_url":"git@github.com:Alberto-S-P/Boilerplate_Nextjs.git","clone_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs.git","svn_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs","homepage":null,"size":1471,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":12,"license":null,"forks":0,"open_issues":12,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/86"},"html":{"href":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/86"},"issue":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/86"},"comments":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/86/comments"},"review_comments":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/86/comments"},"review_comment":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/86/commits"},"statuses":{"href":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/statuses/8243f97eaaa9a4844f460c21c144c571638db940"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":5,"deletions":5,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515521","type":"CreateEvent","actor":{"id":79855143,"login":"meva-ranarison","display_login":"meva-ranarison","gravatar_id":"","url":"https://api.github.com/users/meva-ranarison","avatar_url":"https://avatars.githubusercontent.com/u/79855143?"},"repo":{"id":381090157,"name":"meva-ranarison/karuta-frontend","url":"https://api.github.com/repos/meva-ranarison/karuta-frontend"},"payload":{"ref":"amu","ref_type":"branch","master_branch":"3.0","description":"End user interface for Karuta","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515522","type":"CreateEvent","actor":{"id":60234658,"login":"guillaumew16","display_login":"guillaumew16","gravatar_id":"","url":"https://api.github.com/users/guillaumew16","avatar_url":"https://avatars.githubusercontent.com/u/60234658?"},"repo":{"id":388040578,"name":"guillaumew16/PI_INF442_distrJoin","url":"https://api.github.com/repos/guillaumew16/PI_INF442_distrJoin"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515523","type":"PushEvent","actor":{"id":541490,"login":"morrah","display_login":"morrah","gravatar_id":"","url":"https://api.github.com/users/morrah","avatar_url":"https://avatars.githubusercontent.com/u/541490?"},"repo":{"id":224001345,"name":"morrah/oro-market","url":"https://api.github.com/repos/morrah/oro-market"},"payload":{"push_id":7562055380,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"023c52f39be33694610a845a505eed5d2b568727","before":"8e4a3a256ead7ae2b4fee55f9d567358a7c139a3","commits":[{"sha":"023c52f39be33694610a845a505eed5d2b568727","author":{"name":"morrah","email":"5b2d4826958c41aa3d7920ac238926453de1302d@users.noreply.github.com"},"message":"data update","distinct":true,"url":"https://api.github.com/repos/morrah/oro-market/commits/023c52f39be33694610a845a505eed5d2b568727"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515525","type":"PushEvent","actor":{"id":6749661,"login":"test987987","display_login":"test987987","gravatar_id":"","url":"https://api.github.com/users/test987987","avatar_url":"https://avatars.githubusercontent.com/u/6749661?"},"repo":{"id":375970514,"name":"test987987/test-europe-central2","url":"https://api.github.com/repos/test987987/test-europe-central2"},"payload":{"push_id":7562055424,"size":1,"distinct_size":1,"ref":"refs/heads/csr-mirror","head":"258c12cf5ff0eec7e4c2fa447e0ed0eaa4bd64d9","before":"51f4033e63828d58ff0fbc912384a52ab3aa153b","commits":[{"sha":"258c12cf5ff0eec7e4c2fa447e0ed0eaa4bd64d9","author":{"name":"gerritcodereview-eu-prober@csr-europe-central2-prober.iam.gserviceaccount.com","email":"ddffdf4d0caafd830ea5425d6445998518f10cf0@csr-europe-central2-prober.iam.gserviceaccount.com"},"message":"adding timestamp file","distinct":true,"url":"https://api.github.com/repos/test987987/test-europe-central2/commits/258c12cf5ff0eec7e4c2fa447e0ed0eaa4bd64d9"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515527","type":"PushEvent","actor":{"id":86300656,"login":"nidhi-meraki","display_login":"nidhi-meraki","gravatar_id":"","url":"https://api.github.com/users/nidhi-meraki","avatar_url":"https://avatars.githubusercontent.com/u/86300656?"},"repo":{"id":106544223,"name":"navgurukul/newton","url":"https://api.github.com/repos/navgurukul/newton"},"payload":{"push_id":7562055422,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"80445712ef33a6294a7c80669c1eb2e087cd4cd4","before":"3d95c84379ac50ac4317bc9a62892a9e522c16e7","commits":[{"sha":"80445712ef33a6294a7c80669c1eb2e087cd4cd4","author":{"name":"nidhi-meraki","email":"873a93799817a673ac461f1d2903c664e42aaa95@users.noreply.github.com"},"message":"Create Volunteer-tracker.md","distinct":true,"url":"https://api.github.com/repos/navgurukul/newton/commits/80445712ef33a6294a7c80669c1eb2e087cd4cd4"}]},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":25605372,"login":"navgurukul","gravatar_id":"","url":"https://api.github.com/orgs/navgurukul","avatar_url":"https://avatars.githubusercontent.com/u/25605372?"}} +{"id":"17245515530","type":"PushEvent","actor":{"id":1902623,"login":"trutx","display_login":"trutx","gravatar_id":"","url":"https://api.github.com/users/trutx","avatar_url":"https://avatars.githubusercontent.com/u/1902623?"},"repo":{"id":173382036,"name":"newrelic-forks/cluster-api-provider-aws","url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws"},"payload":{"push_id":7562055429,"size":10,"distinct_size":10,"ref":"refs/heads/master","head":"33c9ef97f852bf9b0866977911184e4e0f651ba5","before":"01f433dab26ab51c469060eff129e7bd9cdd9c61","commits":[{"sha":"51164fb320e65058b6cc74371c0fb622167c5577","author":{"name":"Yuvaraj Kakaraparthi","email":"ceddd27fc4dfbbe99eb2ec753a56aeee5fb631e5@vmware.com"},"message":"add AWSClusterTemplate type","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/51164fb320e65058b6cc74371c0fb622167c5577"},{"sha":"1956af1cb5fbfaf8469c5c215181e6acf5551f69","author":{"name":"shivi28","email":"2af264758635ce604a5fad615778f3665635f3fd@vmware.com"},"message":"Enabled GPU optimised AMIs for EKS\nRemoved unused field like ARN and Filters from AMIReference","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/1956af1cb5fbfaf8469c5c215181e6acf5551f69"},{"sha":"1ceb80e48a24ca2923039f2e29492507bd439c28","author":{"name":"dependabot[bot]","email":"1c358da00a777d4e9898c1280ab801e2df165188@users.noreply.github.com"},"message":"Bump golang from 1.16.5 to 1.16.6\n\nBumps golang from 1.16.5 to 1.16.6.\n\n---\nupdated-dependencies:\n- dependency-name: golang\n dependency-type: direct:production\n update-type: version-update:semver-patch\n...\n\nSigned-off-by: dependabot[bot] ","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/1ceb80e48a24ca2923039f2e29492507bd439c28"},{"sha":"daf59aba4f8918696c9d9913e9440c147ded14b3","author":{"name":"dependabot[bot]","email":"1c358da00a777d4e9898c1280ab801e2df165188@users.noreply.github.com"},"message":"Bump k8s.io/apimachinery from 0.21.2 to 0.21.3 in /hack/tools\n\nBumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.21.2 to 0.21.3.\n- [Release notes](https://github.com/kubernetes/apimachinery/releases)\n- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.21.2...v0.21.3)\n\n---\nupdated-dependencies:\n- dependency-name: k8s.io/apimachinery\n dependency-type: direct:production\n update-type: version-update:semver-patch\n...\n\nSigned-off-by: dependabot[bot] ","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/daf59aba4f8918696c9d9913e9440c147ded14b3"},{"sha":"9d2cc047d14567ec4a59f253100236227f996228","author":{"name":"Kubernetes Prow Robot","email":"5c2029016e1607554af814ace319699a11cecd88@users.noreply.github.com"},"message":"Merge pull request #2591 from kubernetes-sigs/dependabot/docker/golang-1.16.6\n\nBump golang from 1.16.5 to 1.16.6","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/9d2cc047d14567ec4a59f253100236227f996228"},{"sha":"d8ac4dab380a8eacb7bc51cac6fa9bdf088380d5","author":{"name":"Kubernetes Prow Robot","email":"5c2029016e1607554af814ace319699a11cecd88@users.noreply.github.com"},"message":"Merge pull request #2592 from kubernetes-sigs/dependabot/go_modules/hack/tools/k8s.io/apimachinery-0.21.3\n\nBump k8s.io/apimachinery from 0.21.2 to 0.21.3 in /hack/tools","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/d8ac4dab380a8eacb7bc51cac6fa9bdf088380d5"},{"sha":"68592bb4f5013f3689a09c8cffec0158ee1889d5","author":{"name":"dependabot[bot]","email":"1c358da00a777d4e9898c1280ab801e2df165188@users.noreply.github.com"},"message":"Bump k8s.io/code-generator from 0.21.2 to 0.21.3 in /hack/tools\n\nBumps [k8s.io/code-generator](https://github.com/kubernetes/code-generator) from 0.21.2 to 0.21.3.\n- [Release notes](https://github.com/kubernetes/code-generator/releases)\n- [Commits](https://github.com/kubernetes/code-generator/compare/v0.21.2...v0.21.3)\n\n---\nupdated-dependencies:\n- dependency-name: k8s.io/code-generator\n dependency-type: direct:production\n update-type: version-update:semver-patch\n...\n\nSigned-off-by: dependabot[bot] ","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/68592bb4f5013f3689a09c8cffec0158ee1889d5"},{"sha":"8c0d921e8596861a5a774098b2eb0e9b79dbfe0d","author":{"name":"Kubernetes Prow Robot","email":"5c2029016e1607554af814ace319699a11cecd88@users.noreply.github.com"},"message":"Merge pull request #2593 from kubernetes-sigs/dependabot/go_modules/hack/tools/k8s.io/code-generator-0.21.3\n\nBump k8s.io/code-generator from 0.21.2 to 0.21.3 in /hack/tools","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/8c0d921e8596861a5a774098b2eb0e9b79dbfe0d"},{"sha":"11e824649230b31f68e0442e5e520ada0ac7e4e0","author":{"name":"Kubernetes Prow Robot","email":"5c2029016e1607554af814ace319699a11cecd88@users.noreply.github.com"},"message":"Merge pull request #2549 from shivi28/aws_2275\n\nEnabled GPU optimised AMIs for EKS","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/11e824649230b31f68e0442e5e520ada0ac7e4e0"},{"sha":"33c9ef97f852bf9b0866977911184e4e0f651ba5","author":{"name":"Kubernetes Prow Robot","email":"5c2029016e1607554af814ace319699a11cecd88@users.noreply.github.com"},"message":"Merge pull request #2585 from ykakarap/awsclustertemplate\n\nadd AWSClusterTemplate type","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/cluster-api-provider-aws/commits/33c9ef97f852bf9b0866977911184e4e0f651ba5"}]},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":8526625,"login":"newrelic-forks","gravatar_id":"","url":"https://api.github.com/orgs/newrelic-forks","avatar_url":"https://avatars.githubusercontent.com/u/8526625?"}} +{"id":"17245515533","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":249503742,"name":"miwebst/ssRunnerReact","url":"https://api.github.com/repos/miwebst/ssRunnerReact"},"payload":{"push_id":7562055432,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"35b982ad24848bf21d15258b541034f632d87dac","before":"ed8bb708fb26166431d4f210770ec0b9e75f2f68","commits":[{"sha":"35b982ad24848bf21d15258b541034f632d87dac","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 8:00:03 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunnerReact/commits/35b982ad24848bf21d15258b541034f632d87dac"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515542","type":"CreateEvent","actor":{"id":84962076,"login":"NavneetO","display_login":"NavneetO","gravatar_id":"","url":"https://api.github.com/users/NavneetO","avatar_url":"https://avatars.githubusercontent.com/u/84962076?"},"repo":{"id":388040328,"name":"NavneetO/SLide","url":"https://api.github.com/repos/NavneetO/SLide"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515543","type":"WatchEvent","actor":{"id":23494020,"login":"NEWADS","display_login":"NEWADS","gravatar_id":"","url":"https://api.github.com/users/NEWADS","avatar_url":"https://avatars.githubusercontent.com/u/23494020?"},"repo":{"id":372813788,"name":"BiOmicsLab/AVPIden","url":"https://api.github.com/repos/BiOmicsLab/AVPIden"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":82027496,"login":"BiOmicsLab","gravatar_id":"","url":"https://api.github.com/orgs/BiOmicsLab","avatar_url":"https://avatars.githubusercontent.com/u/82027496?"}} +{"id":"17245515550","type":"PushEvent","actor":{"id":84560927,"login":"neevhikeqa","display_login":"neevhikeqa","gravatar_id":"","url":"https://api.github.com/users/neevhikeqa","avatar_url":"https://avatars.githubusercontent.com/u/84560927?"},"repo":{"id":370386589,"name":"neevhikeqa/wordpress","url":"https://api.github.com/repos/neevhikeqa/wordpress"},"payload":{"push_id":7562055435,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"f4d14cfb007b64d80f6dfc5be61da1f737b20cb4","before":"71fde2aef9a88e2b6fa9d21fabd8b46b75db69c9","commits":[{"sha":"f4d14cfb007b64d80f6dfc5be61da1f737b20cb4","author":{"name":"neevhikeqa","email":"18cc8c9397aba9d661630654d760473e8d6d67a4@users.noreply.github.com"},"message":"Update appz.yml","distinct":true,"url":"https://api.github.com/repos/neevhikeqa/wordpress/commits/f4d14cfb007b64d80f6dfc5be61da1f737b20cb4"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515551","type":"IssueCommentEvent","actor":{"id":7548711,"login":"cardigliano","display_login":"cardigliano","gravatar_id":"","url":"https://api.github.com/users/cardigliano","avatar_url":"https://avatars.githubusercontent.com/u/7548711?"},"repo":{"id":69042496,"name":"ntop/n2disk","url":"https://api.github.com/repos/ntop/n2disk"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/ntop/n2disk/issues/41","repository_url":"https://api.github.com/repos/ntop/n2disk","labels_url":"https://api.github.com/repos/ntop/n2disk/issues/41/labels{/name}","comments_url":"https://api.github.com/repos/ntop/n2disk/issues/41/comments","events_url":"https://api.github.com/repos/ntop/n2disk/issues/41/events","html_url":"https://github.com/ntop/n2disk/issues/41","id":949381680,"node_id":"MDU6SXNzdWU5NDkzODE2ODA=","number":41,"title":"(code=killed, signal=KILL)","user":{"login":"MaoPann","id":44804599,"node_id":"MDQ6VXNlcjQ0ODA0NTk5","avatar_url":"https://avatars.githubusercontent.com/u/44804599?v=4","gravatar_id":"","url":"https://api.github.com/users/MaoPann","html_url":"https://github.com/MaoPann","followers_url":"https://api.github.com/users/MaoPann/followers","following_url":"https://api.github.com/users/MaoPann/following{/other_user}","gists_url":"https://api.github.com/users/MaoPann/gists{/gist_id}","starred_url":"https://api.github.com/users/MaoPann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MaoPann/subscriptions","organizations_url":"https://api.github.com/users/MaoPann/orgs","repos_url":"https://api.github.com/users/MaoPann/repos","events_url":"https://api.github.com/users/MaoPann/events{/privacy}","received_events_url":"https://api.github.com/users/MaoPann/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-07-21T06:43:10Z","updated_at":"2021-07-21T08:00:04Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"Now I want to use n2disk to export the stream to ntopng for monitoring, but starting n2disk fails and reports the following error:\r\nn2disk@test.service - n2disk ultra-high-speed traffic recorder with realtime indexing on test\r\n Loaded: loaded (/etc/systemd/system/n2disk@.service; disabled; vendor preset: enabled)\r\n Active: activating (auto-restart) (Result: signal) since Wed 2021-07-21 14:35:36 +08; 1s ago\r\n Process: 3234 ExecStopPost=/bin/sh -c /bin/echo \"$(/bin/date) n2disk test StopPost\" >> /var/log/ntop-systemd.log (code=exited, status=0/SUCCESS)\r\n Process: 3233 ExecStopPost=/bin/rm -rf /run/n2disk-test.conf (code=exited, status=0/SUCCESS)\r\n Process: 3232 ExecStopPost=/bin/rm -rf /run/n2disk-test.env (code=exited, status=0/SUCCESS)\r\n Process: 3215 ExecStartPost=/bin/sh -c /bin/echo \"$(/bin/date) n2disk test StartPost\" >> /var/log/ntop-systemd.log (code=exited, status=0/SUCCESS)\r\n Process: 3214 ExecStart=/usr/bin/stdbuf -oL /usr/bin/${N2DISK_BINARY} /run/n2disk-test.conf (code=killed, signal=KILL)\r\n Process: 3212 ExecStartPre=/bin/sh -c /bin/sed \"/-P.*$\\|--daemon.*\\|--pid.*/s/^/#/\" /etc/n2disk/n2disk-test.conf > /run/n2disk-test.conf (code=exited, st\r\n Process: 3178 ExecStartPre=/bin/sh -c /usr/bin/n2disk --check-license | /bin/grep \"Ok\\|Time-Limited\" && /bin/echo \"N2DISK_BINARY=n2disk\" > /run/n2disk-te\r\n Process: 3120 ExecStartPre=/bin/sh -c /usr/bin/n2disk5g --check-license | /bin/grep \"Ok\\|Time-Limited\" && /bin/echo \"N2DISK_BINARY=n2disk5g\" > /run/n2dis\r\n Process: 3111 ExecStartPre=/bin/sh -c /usr/bin/n2disk1g --check-license | /bin/grep \"Ok\\|Time-Limited\" && /bin/echo \"N2DISK_BINARY=n2disk1g\" > /run/n2dis\r\n Process: 3109 ExecStartPre=/bin/sh -c /bin/echo \"N2DISK_BINARY=n2disk\" > /run/n2disk-test.env (code=exited, status=0/SUCCESS)\r\n Process: 3106 ExecStartPre=/bin/sh -c /bin/echo \"$(/bin/date) n2disk test StartPre\" >> /var/log/ntop-systemd.log (code=exited, status=0/SUCCESS)\r\n Main PID: 3214 (code=killed, signal=KILL)\r\n\r\nn2disk.conf \r\n\r\n# \r\n--interface=ens33\r\n--dump-directory=/storage/n2disk/pcap\r\n--timeline-dir=/storage/n2disk/timeline\r\n--disk-limit=512\r\n#\r\n--max-file-len=1000\r\n--buffer-len=4000\r\n--max-file-duration=60\r\n--index\r\n--snaplen=1536\r\n#\r\n--writer-cpu-affinity=0\r\n--reader-cpu-affinity=1\r\n--compressor-cpu-affinity=2,3\r\n--index-on-compressor-threads\r\n#\r\n-u=ntopng\r\n--zmq=tcp://127.0.0.1:5556\r\n--zmq-probe-mode\r\n--zmq-export-flows\r\n\r\nntopng.conf\r\n\r\n-i tcp://127.0.0.2:5556\r\n-w=3001\r\n-F=nindex\r\n-m=\"192.168.0.0/24,192.168.1.0/24\"\r\n-G=/var/run/ntopng.pid\r\n\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/ntop/n2disk/issues/comments/883977132","html_url":"https://github.com/ntop/n2disk/issues/41#issuecomment-883977132","issue_url":"https://api.github.com/repos/ntop/n2disk/issues/41","id":883977132,"node_id":"IC_kwDOBB2BQM40sGus","user":{"login":"cardigliano","id":7548711,"node_id":"MDQ6VXNlcjc1NDg3MTE=","avatar_url":"https://avatars.githubusercontent.com/u/7548711?v=4","gravatar_id":"","url":"https://api.github.com/users/cardigliano","html_url":"https://github.com/cardigliano","followers_url":"https://api.github.com/users/cardigliano/followers","following_url":"https://api.github.com/users/cardigliano/following{/other_user}","gists_url":"https://api.github.com/users/cardigliano/gists{/gist_id}","starred_url":"https://api.github.com/users/cardigliano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cardigliano/subscriptions","organizations_url":"https://api.github.com/users/cardigliano/orgs","repos_url":"https://api.github.com/users/cardigliano/repos","events_url":"https://api.github.com/users/cardigliano/events{/privacy}","received_events_url":"https://api.github.com/users/cardigliano/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T08:00:04Z","author_association":"MEMBER","body":"Are you running out of memory perhaps? Could you provide cat /proc/meminfo ?","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":12014789,"login":"ntop","gravatar_id":"","url":"https://api.github.com/orgs/ntop","avatar_url":"https://avatars.githubusercontent.com/u/12014789?"}} +{"id":"17245515552","type":"PushEvent","actor":{"id":69245708,"login":"yugam2001","display_login":"yugam2001","gravatar_id":"","url":"https://api.github.com/users/yugam2001","avatar_url":"https://avatars.githubusercontent.com/u/69245708?"},"repo":{"id":388040037,"name":"yugam2001/assignment-1","url":"https://api.github.com/repos/yugam2001/assignment-1"},"payload":{"push_id":7562055439,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"b434205915fa1d80a3c1e21fbcc109c0384a8f21","before":"a4f707b299b8ff4cdf955782a54ebc5a64205387","commits":[{"sha":"b434205915fa1d80a3c1e21fbcc109c0384a8f21","author":{"name":"yugam2001","email":"9b5d239560540b602ce8eff3aee8eb2502d7273b@users.noreply.github.com"},"message":"assignment-1(HTML)","distinct":true,"url":"https://api.github.com/repos/yugam2001/assignment-1/commits/b434205915fa1d80a3c1e21fbcc109c0384a8f21"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515554","type":"PushEvent","actor":{"id":77524128,"login":"vsanga04","display_login":"vsanga04","gravatar_id":"","url":"https://api.github.com/users/vsanga04","avatar_url":"https://avatars.githubusercontent.com/u/77524128?"},"repo":{"id":385436680,"name":"vsanga04/Project2-OaklandCrimeData","url":"https://api.github.com/repos/vsanga04/Project2-OaklandCrimeData"},"payload":{"push_id":7562055440,"size":2,"distinct_size":2,"ref":"refs/heads/main","head":"0fcb4c00e593b0e9807bd1551d4dfa7018acb8fa","before":"4f01d75a0839d95e214c2a374cc1c1be36580cad","commits":[{"sha":"8a191f35fe60fe341d57e43b068990e11d86f892","author":{"name":"vsanga","email":"21d39ef4c70caeedd736cc56b65f44a8811ab52e@gmail.com"},"message":"geojson","distinct":true,"url":"https://api.github.com/repos/vsanga04/Project2-OaklandCrimeData/commits/8a191f35fe60fe341d57e43b068990e11d86f892"},{"sha":"0fcb4c00e593b0e9807bd1551d4dfa7018acb8fa","author":{"name":"vsanga","email":"21d39ef4c70caeedd736cc56b65f44a8811ab52e@gmail.com"},"message":"Merge branch 'main' of github.com:vsanga04/Project2-OaklandCrimeData","distinct":true,"url":"https://api.github.com/repos/vsanga04/Project2-OaklandCrimeData/commits/0fcb4c00e593b0e9807bd1551d4dfa7018acb8fa"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515558","type":"ForkEvent","actor":{"id":69636355,"login":"Cyrix66","display_login":"Cyrix66","gravatar_id":"","url":"https://api.github.com/users/Cyrix66","avatar_url":"https://avatars.githubusercontent.com/u/69636355?"},"repo":{"id":86289901,"name":"baidu/uid-generator","url":"https://api.github.com/repos/baidu/uid-generator"},"payload":{"forkee":{"id":388040579,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1Nzk=","name":"uid-generator","full_name":"Cyrix66/uid-generator","private":false,"owner":{"login":"Cyrix66","id":69636355,"node_id":"MDQ6VXNlcjY5NjM2MzU1","avatar_url":"https://avatars.githubusercontent.com/u/69636355?v=4","gravatar_id":"","url":"https://api.github.com/users/Cyrix66","html_url":"https://github.com/Cyrix66","followers_url":"https://api.github.com/users/Cyrix66/followers","following_url":"https://api.github.com/users/Cyrix66/following{/other_user}","gists_url":"https://api.github.com/users/Cyrix66/gists{/gist_id}","starred_url":"https://api.github.com/users/Cyrix66/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Cyrix66/subscriptions","organizations_url":"https://api.github.com/users/Cyrix66/orgs","repos_url":"https://api.github.com/users/Cyrix66/repos","events_url":"https://api.github.com/users/Cyrix66/events{/privacy}","received_events_url":"https://api.github.com/users/Cyrix66/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Cyrix66/uid-generator","description":"UniqueID generator","fork":true,"url":"https://api.github.com/repos/Cyrix66/uid-generator","forks_url":"https://api.github.com/repos/Cyrix66/uid-generator/forks","keys_url":"https://api.github.com/repos/Cyrix66/uid-generator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Cyrix66/uid-generator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Cyrix66/uid-generator/teams","hooks_url":"https://api.github.com/repos/Cyrix66/uid-generator/hooks","issue_events_url":"https://api.github.com/repos/Cyrix66/uid-generator/issues/events{/number}","events_url":"https://api.github.com/repos/Cyrix66/uid-generator/events","assignees_url":"https://api.github.com/repos/Cyrix66/uid-generator/assignees{/user}","branches_url":"https://api.github.com/repos/Cyrix66/uid-generator/branches{/branch}","tags_url":"https://api.github.com/repos/Cyrix66/uid-generator/tags","blobs_url":"https://api.github.com/repos/Cyrix66/uid-generator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Cyrix66/uid-generator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Cyrix66/uid-generator/git/refs{/sha}","trees_url":"https://api.github.com/repos/Cyrix66/uid-generator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Cyrix66/uid-generator/statuses/{sha}","languages_url":"https://api.github.com/repos/Cyrix66/uid-generator/languages","stargazers_url":"https://api.github.com/repos/Cyrix66/uid-generator/stargazers","contributors_url":"https://api.github.com/repos/Cyrix66/uid-generator/contributors","subscribers_url":"https://api.github.com/repos/Cyrix66/uid-generator/subscribers","subscription_url":"https://api.github.com/repos/Cyrix66/uid-generator/subscription","commits_url":"https://api.github.com/repos/Cyrix66/uid-generator/commits{/sha}","git_commits_url":"https://api.github.com/repos/Cyrix66/uid-generator/git/commits{/sha}","comments_url":"https://api.github.com/repos/Cyrix66/uid-generator/comments{/number}","issue_comment_url":"https://api.github.com/repos/Cyrix66/uid-generator/issues/comments{/number}","contents_url":"https://api.github.com/repos/Cyrix66/uid-generator/contents/{+path}","compare_url":"https://api.github.com/repos/Cyrix66/uid-generator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Cyrix66/uid-generator/merges","archive_url":"https://api.github.com/repos/Cyrix66/uid-generator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Cyrix66/uid-generator/downloads","issues_url":"https://api.github.com/repos/Cyrix66/uid-generator/issues{/number}","pulls_url":"https://api.github.com/repos/Cyrix66/uid-generator/pulls{/number}","milestones_url":"https://api.github.com/repos/Cyrix66/uid-generator/milestones{/number}","notifications_url":"https://api.github.com/repos/Cyrix66/uid-generator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Cyrix66/uid-generator/labels{/name}","releases_url":"https://api.github.com/repos/Cyrix66/uid-generator/releases{/id}","deployments_url":"https://api.github.com/repos/Cyrix66/uid-generator/deployments","created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T01:51:29Z","pushed_at":"2021-06-07T17:25:16Z","git_url":"git://github.com/Cyrix66/uid-generator.git","ssh_url":"git@github.com:Cyrix66/uid-generator.git","clone_url":"https://github.com/Cyrix66/uid-generator.git","svn_url":"https://github.com/Cyrix66/uid-generator","homepage":"","size":369,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":13245940,"login":"baidu","gravatar_id":"","url":"https://api.github.com/orgs/baidu","avatar_url":"https://avatars.githubusercontent.com/u/13245940?"}} +{"id":"17245515571","type":"MemberEvent","actor":{"id":86040839,"login":"Solstron","display_login":"Solstron","gravatar_id":"","url":"https://api.github.com/users/Solstron","avatar_url":"https://avatars.githubusercontent.com/u/86040839?"},"repo":{"id":378172154,"name":"Solstron/Second-West-Russian-War---Mod-Files","url":"https://api.github.com/repos/Solstron/Second-West-Russian-War---Mod-Files"},"payload":{"member":{"login":"Majchin","id":87670984,"node_id":"MDQ6VXNlcjg3NjcwOTg0","avatar_url":"https://avatars.githubusercontent.com/u/87670984?v=4","gravatar_id":"","url":"https://api.github.com/users/Majchin","html_url":"https://github.com/Majchin","followers_url":"https://api.github.com/users/Majchin/followers","following_url":"https://api.github.com/users/Majchin/following{/other_user}","gists_url":"https://api.github.com/users/Majchin/gists{/gist_id}","starred_url":"https://api.github.com/users/Majchin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Majchin/subscriptions","organizations_url":"https://api.github.com/users/Majchin/orgs","repos_url":"https://api.github.com/users/Majchin/repos","events_url":"https://api.github.com/users/Majchin/events{/privacy}","received_events_url":"https://api.github.com/users/Majchin/received_events","type":"User","site_admin":false},"action":"added"},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515573","type":"PushEvent","actor":{"id":75016136,"login":"CastformMadrid","display_login":"CastformMadrid","gravatar_id":"","url":"https://api.github.com/users/CastformMadrid","avatar_url":"https://avatars.githubusercontent.com/u/75016136?"},"repo":{"id":315383656,"name":"alexelgt/Castform-Madrid","url":"https://api.github.com/repos/alexelgt/Castform-Madrid"},"payload":{"push_id":7562055452,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"0a69f3e9a2644084e923725e8bed1eff1e10c7bd","before":"c18ce1429a65e78d4052a2f525048fc3d71c1012","commits":[{"sha":"0a69f3e9a2644084e923725e8bed1eff1e10c7bd","author":{"name":"CastformMadrid","email":"4bf3034b7f6dbaf30fedab8adaf278c90d929f80@gmail.com"},"message":"21/7/2021 10:00","distinct":true,"url":"https://api.github.com/repos/alexelgt/Castform-Madrid/commits/0a69f3e9a2644084e923725e8bed1eff1e10c7bd"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515574","type":"PushEvent","actor":{"id":79092341,"login":"Bhanuprasad45","display_login":"Bhanuprasad45","gravatar_id":"","url":"https://api.github.com/users/Bhanuprasad45","avatar_url":"https://avatars.githubusercontent.com/u/79092341?"},"repo":{"id":388039033,"name":"Bhanuprasad45/Counterfeit_Medicine_Sales","url":"https://api.github.com/repos/Bhanuprasad45/Counterfeit_Medicine_Sales"},"payload":{"push_id":7562055454,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"59e79c9f1855c081b6e43ade86a005edf1d39d13","before":"9ce00f09e30cb9aed9244ce59a30300de126ead9","commits":[{"sha":"59e79c9f1855c081b6e43ade86a005edf1d39d13","author":{"name":"Bhanuprasad45","email":"fe2b0d9ad05ef5c114ea306f138ef2330ac47615@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/Bhanuprasad45/Counterfeit_Medicine_Sales/commits/59e79c9f1855c081b6e43ade86a005edf1d39d13"}]},"public":true,"created_at":"2021-07-21T08:00:04Z"} +{"id":"17245515586","type":"PushEvent","actor":{"id":49736102,"login":"kodiakhq[bot]","display_login":"kodiakhq","gravatar_id":"","url":"https://api.github.com/users/kodiakhq[bot]","avatar_url":"https://avatars.githubusercontent.com/u/49736102?"},"repo":{"id":352747621,"name":"esm-bundle/markdown-it-emoji","url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji"},"payload":{"push_id":7562055451,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"179af100fa383064b40c6358632f2531cbc8f4b7","before":"68fdce2a9921331aaf8d208e93064aba36b20144","commits":[{"sha":"179af100fa383064b40c6358632f2531cbc8f4b7","author":{"name":"renovate[bot]","email":"1d2c67ad0e3429d16690809152e1083ff6af8a05@users.noreply.github.com"},"message":"Update dependency rollup to v2.53.3 (#62)\n\nCo-authored-by: Renovate Bot ","distinct":true,"url":"https://api.github.com/repos/esm-bundle/markdown-it-emoji/commits/179af100fa383064b40c6358632f2531cbc8f4b7"}]},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":60286277,"login":"esm-bundle","gravatar_id":"","url":"https://api.github.com/orgs/esm-bundle","avatar_url":"https://avatars.githubusercontent.com/u/60286277?"}} +{"id":"17245515651","type":"PullRequestReviewCommentEvent","actor":{"id":6770950,"login":"rebkwok","display_login":"rebkwok","gravatar_id":"","url":"https://api.github.com/users/rebkwok","avatar_url":"https://avatars.githubusercontent.com/u/6770950?"},"repo":{"id":379919397,"name":"opensafely-core/cohort-extractor-v2","url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/comments/673748220","pull_request_review_id":711364936,"id":673748220,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3Mzc0ODIyMA==","diff_hunk":"@@ -44,6 +51,14 @@ def __or__(self, other):\n def __len__(self):\n return len(self.children)\n \n+ def __invert__(self):\n+ self.negate()\n+ return self\n+\n+ def negate(self):\n+ \"\"\"Negate the sense of the root comparator.\"\"\"\n+ self.negated = not self.negated\n+","path":"cohortextractor/query_language.py","position":null,"original_position":34,"commit_id":"db8811b0c38db5f90a9358cc74e3f7393fe0ce3f","original_commit_id":"0379b2b97e05ce7e868b56ccc701a721a12e42f2","user":{"login":"rebkwok","id":6770950,"node_id":"MDQ6VXNlcjY3NzA5NTA=","avatar_url":"https://avatars.githubusercontent.com/u/6770950?v=4","gravatar_id":"","url":"https://api.github.com/users/rebkwok","html_url":"https://github.com/rebkwok","followers_url":"https://api.github.com/users/rebkwok/followers","following_url":"https://api.github.com/users/rebkwok/following{/other_user}","gists_url":"https://api.github.com/users/rebkwok/gists{/gist_id}","starred_url":"https://api.github.com/users/rebkwok/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rebkwok/subscriptions","organizations_url":"https://api.github.com/users/rebkwok/orgs","repos_url":"https://api.github.com/users/rebkwok/repos","events_url":"https://api.github.com/users/rebkwok/events{/privacy}","received_events_url":"https://api.github.com/users/rebkwok/received_events","type":"User","site_admin":false},"body":"Actually, I think we could have an `__invert__` method on a Value too. In fact, we should, so we can do `~foo` and don't have to do `~c(foo)`. ","created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T08:00:04Z","html_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76#discussion_r673748220","pull_request_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/comments/673748220"},"html":{"href":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76#discussion_r673748220"},"pull_request":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76"}},"start_line":null,"original_start_line":54,"start_side":"RIGHT","line":null,"original_line":65,"side":"RIGHT","in_reply_to_id":672925923},"pull_request":{"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76","id":692523207,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyNTIzMjA3","html_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76","diff_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76.diff","patch_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76.patch","issue_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76","number":76,"state":"open","locked":false,"title":"Categorising by truthiness (non-nullness) of a value","user":{"login":"rebkwok","id":6770950,"node_id":"MDQ6VXNlcjY3NzA5NTA=","avatar_url":"https://avatars.githubusercontent.com/u/6770950?v=4","gravatar_id":"","url":"https://api.github.com/users/rebkwok","html_url":"https://github.com/rebkwok","followers_url":"https://api.github.com/users/rebkwok/followers","following_url":"https://api.github.com/users/rebkwok/following{/other_user}","gists_url":"https://api.github.com/users/rebkwok/gists{/gist_id}","starred_url":"https://api.github.com/users/rebkwok/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rebkwok/subscriptions","organizations_url":"https://api.github.com/users/rebkwok/orgs","repos_url":"https://api.github.com/users/rebkwok/repos","events_url":"https://api.github.com/users/rebkwok/events{/privacy}","received_events_url":"https://api.github.com/users/rebkwok/received_events","type":"User","site_admin":false},"body":"Allows categorisation on whether a Value (of any sort) is truthy, by using a `c` function that returns a comparator for != None. Since every patient will have a row in any value table created from a filter or aggregation, we can use the None values to check for existence.","created_at":"2021-07-19T11:29:40Z","updated_at":"2021-07-21T08:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"af958dfb92c525be886993e5219c5b12374ea86a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/commits","review_comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/comments","review_comment_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/comments{/number}","comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76/comments","statuses_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/db8811b0c38db5f90a9358cc74e3f7393fe0ce3f","head":{"label":"opensafely-core:categorise_existence","ref":"categorise_existence","sha":"db8811b0c38db5f90a9358cc74e3f7393fe0ce3f","user":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"repo":{"id":379919397,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk5MTkzOTc=","name":"cohort-extractor-v2","full_name":"opensafely-core/cohort-extractor-v2","private":false,"owner":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opensafely-core/cohort-extractor-v2","description":null,"fork":false,"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2","forks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/forks","keys_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/teams","hooks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/hooks","issue_events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/events{/number}","events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/events","assignees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/assignees{/user}","branches_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/branches{/branch}","tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/tags","blobs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/refs{/sha}","trees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/{sha}","languages_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/languages","stargazers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/stargazers","contributors_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contributors","subscribers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscribers","subscription_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscription","commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/commits{/sha}","git_commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/commits{/sha}","comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/comments{/number}","issue_comment_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/comments{/number}","contents_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contents/{+path}","compare_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/merges","archive_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/downloads","issues_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues{/number}","pulls_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls{/number}","milestones_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/milestones{/number}","notifications_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/labels{/name}","releases_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/releases{/id}","deployments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/deployments","created_at":"2021-06-24T12:36:00Z","updated_at":"2021-07-20T09:16:30Z","pushed_at":"2021-07-20T17:10:34Z","git_url":"git://github.com/opensafely-core/cohort-extractor-v2.git","ssh_url":"git@github.com:opensafely-core/cohort-extractor-v2.git","clone_url":"https://github.com/opensafely-core/cohort-extractor-v2.git","svn_url":"https://github.com/opensafely-core/cohort-extractor-v2","homepage":null,"size":488,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":19,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":19,"watchers":0,"default_branch":"main"}},"base":{"label":"opensafely-core:main","ref":"main","sha":"b72f356cbe1883b99426e46be9a67926269dc62a","user":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"repo":{"id":379919397,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk5MTkzOTc=","name":"cohort-extractor-v2","full_name":"opensafely-core/cohort-extractor-v2","private":false,"owner":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opensafely-core/cohort-extractor-v2","description":null,"fork":false,"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2","forks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/forks","keys_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/teams","hooks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/hooks","issue_events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/events{/number}","events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/events","assignees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/assignees{/user}","branches_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/branches{/branch}","tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/tags","blobs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/refs{/sha}","trees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/{sha}","languages_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/languages","stargazers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/stargazers","contributors_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contributors","subscribers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscribers","subscription_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscription","commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/commits{/sha}","git_commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/commits{/sha}","comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/comments{/number}","issue_comment_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/comments{/number}","contents_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contents/{+path}","compare_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/merges","archive_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/downloads","issues_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues{/number}","pulls_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls{/number}","milestones_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/milestones{/number}","notifications_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/labels{/name}","releases_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/releases{/id}","deployments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/deployments","created_at":"2021-06-24T12:36:00Z","updated_at":"2021-07-20T09:16:30Z","pushed_at":"2021-07-20T17:10:34Z","git_url":"git://github.com/opensafely-core/cohort-extractor-v2.git","ssh_url":"git@github.com:opensafely-core/cohort-extractor-v2.git","clone_url":"https://github.com/opensafely-core/cohort-extractor-v2.git","svn_url":"https://github.com/opensafely-core/cohort-extractor-v2","homepage":null,"size":488,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":19,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":19,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76"},"html":{"href":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76"},"issue":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76"},"comments":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76/comments"},"review_comments":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/comments"},"review_comment":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/commits"},"statuses":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/db8811b0c38db5f90a9358cc74e3f7393fe0ce3f"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:04Z","org":{"id":77844139,"login":"opensafely-core","gravatar_id":"","url":"https://api.github.com/orgs/opensafely-core","avatar_url":"https://avatars.githubusercontent.com/u/77844139?"}} +{"id":"17245515606","type":"PushEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388040568,"name":"thatjohn01/753021085","url":"https://api.github.com/repos/thatjohn01/753021085"},"payload":{"push_id":7562055464,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f736cdfa2587c4ea6ba2accdbba51b6c72767b79","before":"68f32c06defbf15c9ad528133292d1646ff4478a","commits":[{"sha":"f736cdfa2587c4ea6ba2accdbba51b6c72767b79","author":{"name":"thatjohn01","email":"72eb81e66410b3da65da4cca287ce0578825ce64@users.noreply.github.com"},"message":"change README.md","distinct":true,"url":"https://api.github.com/repos/thatjohn01/753021085/commits/f736cdfa2587c4ea6ba2accdbba51b6c72767b79"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515609","type":"PushEvent","actor":{"id":40587912,"login":"supermobiteam2","display_login":"supermobiteam2","gravatar_id":"","url":"https://api.github.com/users/supermobiteam2","avatar_url":"https://avatars.githubusercontent.com/u/40587912?"},"repo":{"id":138681984,"name":"supermobiteam2/Tizi","url":"https://api.github.com/repos/supermobiteam2/Tizi"},"payload":{"push_id":7562055468,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"2618c8af037d293811fb372212fbd7fbbe187ca9","before":"8174bd0c89c1bb7d5ce452843d1eda0a05c858e8","commits":[{"sha":"2618c8af037d293811fb372212fbd7fbbe187ca9","author":{"name":"supermobiteam2","email":"f34688687956b708ea5937840a164da02e7b6797@users.noreply.github.com"},"message":"tizi ios","distinct":true,"url":"https://api.github.com/repos/supermobiteam2/Tizi/commits/2618c8af037d293811fb372212fbd7fbbe187ca9"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515610","type":"PushEvent","actor":{"id":37944667,"login":"xymox1987","display_login":"xymox1987","gravatar_id":"","url":"https://api.github.com/users/xymox1987","avatar_url":"https://avatars.githubusercontent.com/u/37944667?"},"repo":{"id":388034681,"name":"xymox1987/AgroCampo","url":"https://api.github.com/repos/xymox1987/AgroCampo"},"payload":{"push_id":7562055469,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"4b4b48feb7113d42f9db0dbd0088c9d2a28ab560","before":"fceb4b92a7a9c1f9af9d44ba7157a4b3f7ce4a14","commits":[{"sha":"4b4b48feb7113d42f9db0dbd0088c9d2a28ab560","author":{"name":"xymox1987","email":"1b511e670af0641721791560a65bcffb53e3d7e1@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/xymox1987/AgroCampo/commits/4b4b48feb7113d42f9db0dbd0088c9d2a28ab560"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515612","type":"IssueCommentEvent","actor":{"id":48406637,"login":"shy-tan","display_login":"shy-tan","gravatar_id":"","url":"https://api.github.com/users/shy-tan","avatar_url":"https://avatars.githubusercontent.com/u/48406637?"},"repo":{"id":35810174,"name":"electron-react-boilerplate/electron-react-boilerplate","url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate/issues/2395","repository_url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate","labels_url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate/issues/2395/labels{/name}","comments_url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate/issues/2395/comments","events_url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate/issues/2395/events","html_url":"https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/2395","id":573609402,"node_id":"MDU6SXNzdWU1NzM2MDk0MDI=","number":2395,"title":"How to properly set up material-ui with electron-react-boilerplate","user":{"login":"Vasniktel","id":23561899,"node_id":"MDQ6VXNlcjIzNTYxODk5","avatar_url":"https://avatars.githubusercontent.com/u/23561899?v=4","gravatar_id":"","url":"https://api.github.com/users/Vasniktel","html_url":"https://github.com/Vasniktel","followers_url":"https://api.github.com/users/Vasniktel/followers","following_url":"https://api.github.com/users/Vasniktel/following{/other_user}","gists_url":"https://api.github.com/users/Vasniktel/gists{/gist_id}","starred_url":"https://api.github.com/users/Vasniktel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Vasniktel/subscriptions","organizations_url":"https://api.github.com/users/Vasniktel/orgs","repos_url":"https://api.github.com/users/Vasniktel/repos","events_url":"https://api.github.com/users/Vasniktel/events{/privacy}","received_events_url":"https://api.github.com/users/Vasniktel/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":31,"created_at":"2020-03-01T20:27:07Z","updated_at":"2021-07-21T08:00:04Z","closed_at":"2020-07-24T18:08:54Z","author_association":"NONE","active_lock_reason":null,"body":"See this question: https://stackoverflow.com/questions/60473495/how-to-properly-set-up-material-ui-with-electron-react-boilerplate\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate/issues/comments/883977137","html_url":"https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/2395#issuecomment-883977137","issue_url":"https://api.github.com/repos/electron-react-boilerplate/electron-react-boilerplate/issues/2395","id":883977137,"node_id":"IC_kwDOAiJrfs40sGux","user":{"login":"shy-tan","id":48406637,"node_id":"MDQ6VXNlcjQ4NDA2NjM3","avatar_url":"https://avatars.githubusercontent.com/u/48406637?v=4","gravatar_id":"","url":"https://api.github.com/users/shy-tan","html_url":"https://github.com/shy-tan","followers_url":"https://api.github.com/users/shy-tan/followers","following_url":"https://api.github.com/users/shy-tan/following{/other_user}","gists_url":"https://api.github.com/users/shy-tan/gists{/gist_id}","starred_url":"https://api.github.com/users/shy-tan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shy-tan/subscriptions","organizations_url":"https://api.github.com/users/shy-tan/orgs","repos_url":"https://api.github.com/users/shy-tan/repos","events_url":"https://api.github.com/users/shy-tan/events{/privacy}","received_events_url":"https://api.github.com/users/shy-tan/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T08:00:04Z","author_association":"NONE","body":"Deleting the cache worked for me.\r\nhttps://stackoverflow.com/a/65780651/10934636","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":28049053,"login":"electron-react-boilerplate","gravatar_id":"","url":"https://api.github.com/orgs/electron-react-boilerplate","avatar_url":"https://avatars.githubusercontent.com/u/28049053?"}} +{"id":"17245515623","type":"IssueCommentEvent","actor":{"id":5003891,"login":"trasherdk","display_login":"trasherdk","gravatar_id":"","url":"https://api.github.com/users/trasherdk","avatar_url":"https://avatars.githubusercontent.com/u/5003891?"},"repo":{"id":217603326,"name":"hagopj13/node-express-boilerplate","url":"https://api.github.com/repos/hagopj13/node-express-boilerplate"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/hagopj13/node-express-boilerplate/issues/136","repository_url":"https://api.github.com/repos/hagopj13/node-express-boilerplate","labels_url":"https://api.github.com/repos/hagopj13/node-express-boilerplate/issues/136/labels{/name}","comments_url":"https://api.github.com/repos/hagopj13/node-express-boilerplate/issues/136/comments","events_url":"https://api.github.com/repos/hagopj13/node-express-boilerplate/issues/136/events","html_url":"https://github.com/hagopj13/node-express-boilerplate/issues/136","id":948991976,"node_id":"MDU6SXNzdWU5NDg5OTE5NzY=","number":136,"title":"PR - TypeScript Conversion","user":{"login":"dominick-more","id":62982489,"node_id":"MDQ6VXNlcjYyOTgyNDg5","avatar_url":"https://avatars.githubusercontent.com/u/62982489?v=4","gravatar_id":"","url":"https://api.github.com/users/dominick-more","html_url":"https://github.com/dominick-more","followers_url":"https://api.github.com/users/dominick-more/followers","following_url":"https://api.github.com/users/dominick-more/following{/other_user}","gists_url":"https://api.github.com/users/dominick-more/gists{/gist_id}","starred_url":"https://api.github.com/users/dominick-more/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dominick-more/subscriptions","organizations_url":"https://api.github.com/users/dominick-more/orgs","repos_url":"https://api.github.com/users/dominick-more/repos","events_url":"https://api.github.com/users/dominick-more/events{/privacy}","received_events_url":"https://api.github.com/users/dominick-more/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-20T19:23:06Z","updated_at":"2021-07-21T08:00:05Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"I set for myself the goal to make a \"mini\" app project, in order to showcase my skills and thought about what the basic functionality of backend server required, so that I can execute async calls from my client code, and add records in a db, so I saw the work that you did and think it's really quite well thought through, but I also want to demonstrate my typescript abilities.\r\nI wanted to use your work for a basis and did a complete typescript overhaul of all src and tests js files. All existing package.json scripts are running as before with noted changes (I will point these out in a pr).\r\n\r\nScope of changes:\r\n\r\nCode:\r\nAll function parameters and return types defined (where practical)\r\nBest effort to keep the original function signtatures unchanged.\r\n\r\nBuild-Configuration:\r\nDue to inclusion of TypeScript, have modified executable dev dependencies 'ts-node' for 'dev' script, babel-jest for 'test' script and 'babel-ci' for the 'server' script transpilation to 'dist' out directory.\r\n\r\nEslint:\r\nHave replaced extends \"airbnb-base\" with \"airbnb-typescript\" (and other typescript contraints).\r\n\r\nDocker:\r\nReordered DockerFile 'USER node' to first execution position and creating the checkout working directory /home/user/nope-app.\r\n\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/hagopj13/node-express-boilerplate/issues/comments/883977139","html_url":"https://github.com/hagopj13/node-express-boilerplate/issues/136#issuecomment-883977139","issue_url":"https://api.github.com/repos/hagopj13/node-express-boilerplate/issues/136","id":883977139,"node_id":"IC_kwDODPhc_s40sGuz","user":{"login":"trasherdk","id":5003891,"node_id":"MDQ6VXNlcjUwMDM4OTE=","avatar_url":"https://avatars.githubusercontent.com/u/5003891?v=4","gravatar_id":"","url":"https://api.github.com/users/trasherdk","html_url":"https://github.com/trasherdk","followers_url":"https://api.github.com/users/trasherdk/followers","following_url":"https://api.github.com/users/trasherdk/following{/other_user}","gists_url":"https://api.github.com/users/trasherdk/gists{/gist_id}","starred_url":"https://api.github.com/users/trasherdk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/trasherdk/subscriptions","organizations_url":"https://api.github.com/users/trasherdk/orgs","repos_url":"https://api.github.com/users/trasherdk/repos","events_url":"https://api.github.com/users/trasherdk/events{/privacy}","received_events_url":"https://api.github.com/users/trasherdk/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T08:00:04Z","author_association":"NONE","body":"Sounds interesting.\r\nCan you put it up in a repository? I would like to take a look.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515636","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7562055455,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"0b9c99f1728d7f200085293478e6e8549b40a455","before":"406a8945fddc6662b6816d96e7e9d21a8f088687","commits":[{"sha":"0b9c99f1728d7f200085293478e6e8549b40a455","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/0b9c99f1728d7f200085293478e6e8549b40a455"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515641","type":"PushEvent","actor":{"id":3469524,"login":"ahmetoz","display_login":"ahmetoz","gravatar_id":"","url":"https://api.github.com/users/ahmetoz","avatar_url":"https://avatars.githubusercontent.com/u/3469524?"},"repo":{"id":84962876,"name":"commercetools/commercetools-sync-java","url":"https://api.github.com/repos/commercetools/commercetools-sync-java"},"payload":{"push_id":7562055474,"size":3,"distinct_size":3,"ref":"refs/heads/update-readme","head":"818bf4d19712e0b4980113d5a2a02155e2a8192e","before":"b6022c2d068988f2feafdf97070466196a5a9067","commits":[{"sha":"04b09c819d158285c8037fc6fd8571f79ec4c045","author":{"name":"aoz","email":"13357b2be637093e650389a6f89d89c8296a9202@gmail.com"},"message":"remove roadmap.","distinct":true,"url":"https://api.github.com/repos/commercetools/commercetools-sync-java/commits/04b09c819d158285c8037fc6fd8571f79ec4c045"},{"sha":"75fd8d383130f161df0efe12d81183c4ae82c34a","author":{"name":"aoz","email":"13357b2be637093e650389a6f89d89c8296a9202@gmail.com"},"message":"edit main sentence.","distinct":true,"url":"https://api.github.com/repos/commercetools/commercetools-sync-java/commits/75fd8d383130f161df0efe12d81183c4ae82c34a"},{"sha":"818bf4d19712e0b4980113d5a2a02155e2a8192e","author":{"name":"aoz","email":"13357b2be637093e650389a6f89d89c8296a9202@gmail.com"},"message":"Update usage section.","distinct":true,"url":"https://api.github.com/repos/commercetools/commercetools-sync-java/commits/818bf4d19712e0b4980113d5a2a02155e2a8192e"}]},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":1084585,"login":"commercetools","gravatar_id":"","url":"https://api.github.com/orgs/commercetools","avatar_url":"https://avatars.githubusercontent.com/u/1084585?"}} +{"id":"17245515647","type":"IssueCommentEvent","actor":{"id":15917439,"login":"xhcao","display_login":"xhcao","gravatar_id":"","url":"https://api.github.com/users/xhcao","avatar_url":"https://avatars.githubusercontent.com/u/15917439?"},"repo":{"id":123870315,"name":"tensorflow/tfjs","url":"https://api.github.com/repos/tensorflow/tfjs"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/tensorflow/tfjs/issues/5342","repository_url":"https://api.github.com/repos/tensorflow/tfjs","labels_url":"https://api.github.com/repos/tensorflow/tfjs/issues/5342/labels{/name}","comments_url":"https://api.github.com/repos/tensorflow/tfjs/issues/5342/comments","events_url":"https://api.github.com/repos/tensorflow/tfjs/issues/5342/events","html_url":"https://github.com/tensorflow/tfjs/pull/5342","id":947199230,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyMjE1MTAz","number":5342,"title":"webgpu: optimize matmul with small output size","user":{"login":"xhcao","id":15917439,"node_id":"MDQ6VXNlcjE1OTE3NDM5","avatar_url":"https://avatars.githubusercontent.com/u/15917439?v=4","gravatar_id":"","url":"https://api.github.com/users/xhcao","html_url":"https://github.com/xhcao","followers_url":"https://api.github.com/users/xhcao/followers","following_url":"https://api.github.com/users/xhcao/following{/other_user}","gists_url":"https://api.github.com/users/xhcao/gists{/gist_id}","starred_url":"https://api.github.com/users/xhcao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xhcao/subscriptions","organizations_url":"https://api.github.com/users/xhcao/orgs","repos_url":"https://api.github.com/users/xhcao/repos","events_url":"https://api.github.com/users/xhcao/events{/privacy}","received_events_url":"https://api.github.com/users/xhcao/received_events","type":"User","site_admin":false},"labels":[{"id":1290978557,"node_id":"MDU6TGFiZWwxMjkwOTc4NTU3","url":"https://api.github.com/repos/tensorflow/tfjs/labels/cla:%20yes","name":"cla: yes","color":"ededed","default":false,"description":null}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-07-19T02:22:48Z","updated_at":"2021-07-21T08:00:05Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/tensorflow/tfjs/pulls/5342","html_url":"https://github.com/tensorflow/tfjs/pull/5342","diff_url":"https://github.com/tensorflow/tfjs/pull/5342.diff","patch_url":"https://github.com/tensorflow/tfjs/pull/5342.patch"},"body":"Current matmul algorithm dispatches logical threads based on\r\noutput size, if output size is small and inner dimension size\r\nis large, there are few threads and every thread should handle\r\nlarge loads, so the performance is poor.\r\nThis patch dispatches logical threads based on output size and\r\ninner dimension size, increases the logical threads, makes load\r\nbalancing.\r\nThis patches also introduces two shared memory buffers, some\r\nlogical threads could handle arithmetic operations and others\r\nhandle IO operations between barrier api, makes ALUs and load/store\r\nunits work simultaneously, could improves the performance.\r\n\r\nTo see the logs from the Cloud Build CI, please join either our [discussion](https://groups.google.com/a/tensorflow.org/forum/#!forum/tfjs) or [announcement](https://groups.google.com/a/tensorflow.org/forum/#!forum/tfjs-announce) mailing list.\n\n\n---\nThis change is [\"Reviewable\"/](https://reviewable.io/reviews/tensorflow/tfjs/5342)\n\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/tensorflow/tfjs/issues/comments/883977138","html_url":"https://github.com/tensorflow/tfjs/pull/5342#issuecomment-883977138","issue_url":"https://api.github.com/repos/tensorflow/tfjs/issues/5342","id":883977138,"node_id":"IC_kwDOB2Ica840sGuy","user":{"login":"xhcao","id":15917439,"node_id":"MDQ6VXNlcjE1OTE3NDM5","avatar_url":"https://avatars.githubusercontent.com/u/15917439?v=4","gravatar_id":"","url":"https://api.github.com/users/xhcao","html_url":"https://github.com/xhcao","followers_url":"https://api.github.com/users/xhcao/followers","following_url":"https://api.github.com/users/xhcao/following{/other_user}","gists_url":"https://api.github.com/users/xhcao/gists{/gist_id}","starred_url":"https://api.github.com/users/xhcao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xhcao/subscriptions","organizations_url":"https://api.github.com/users/xhcao/orgs","repos_url":"https://api.github.com/users/xhcao/repos","events_url":"https://api.github.com/users/xhcao/events{/privacy}","received_events_url":"https://api.github.com/users/xhcao/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T08:00:04Z","author_association":"CONTRIBUTOR","body":"@qjia7 @haoyunfeix @axinging @gyagp, please take a look, thank you.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":15658638,"login":"tensorflow","gravatar_id":"","url":"https://api.github.com/orgs/tensorflow","avatar_url":"https://avatars.githubusercontent.com/u/15658638?"}} +{"id":"17245515653","type":"PullRequestEvent","actor":{"id":63818299,"login":"jgsheppa","display_login":"jgsheppa","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","avatar_url":"https://avatars.githubusercontent.com/u/63818299?"},"repo":{"id":299556960,"name":"jgsheppa/dictionary-app","url":"https://api.github.com/repos/jgsheppa/dictionary-app"},"payload":{"action":"closed","number":3,"pull_request":{"url":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/3","id":693963488,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzOTYzNDg4","html_url":"https://github.com/jgsheppa/dictionary-app/pull/3","diff_url":"https://github.com/jgsheppa/dictionary-app/pull/3.diff","patch_url":"https://github.com/jgsheppa/dictionary-app/pull/3.patch","issue_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/3","number":3,"state":"closed","locked":false,"title":"[Snyk] Upgrade @types/node from 14.14.7 to 14.17.4","user":{"login":"snyk-bot","id":19733683,"node_id":"MDQ6VXNlcjE5NzMzNjgz","avatar_url":"https://avatars.githubusercontent.com/u/19733683?v=4","gravatar_id":"","url":"https://api.github.com/users/snyk-bot","html_url":"https://github.com/snyk-bot","followers_url":"https://api.github.com/users/snyk-bot/followers","following_url":"https://api.github.com/users/snyk-bot/following{/other_user}","gists_url":"https://api.github.com/users/snyk-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/snyk-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snyk-bot/subscriptions","organizations_url":"https://api.github.com/users/snyk-bot/orgs","repos_url":"https://api.github.com/users/snyk-bot/repos","events_url":"https://api.github.com/users/snyk-bot/events{/privacy}","received_events_url":"https://api.github.com/users/snyk-bot/received_events","type":"User","site_admin":false},"body":"

    Snyk has created this PR to upgrade @types/node from 14.14.7 to 14.17.4.

    \n\n:information_source: Keep your dependencies up-to-date. This makes it easier to fix existing vulnerabilities and to more quickly identify and fix newly disclosed vulnerabilities when they affect your project.\n
    \n\n- The recommended version is **43 versions** ahead of your current version.\n- The recommended version was released **a month ago**, on 2021-06-23.\n\n\n
    \n\n**Note:** *You are seeing this because you or someone else with access to this repository has authorized Snyk to open upgrade PRs.*\n\nFor more information: \n\n🧐 [View latest project report](https://app.snyk.io/org/jgsheppa/project/890b4ae1-03b6-448d-9daa-875c5f5add2a?utm_source=github&utm_medium=upgrade-pr)\n\n🛠 [Adjust upgrade PR settings](https://app.snyk.io/org/jgsheppa/project/890b4ae1-03b6-448d-9daa-875c5f5add2a/settings/integration?utm_source=github&utm_medium=upgrade-pr)\n\n🔕 [Ignore this dependency or unsubscribe from future upgrade PRs](https://app.snyk.io/org/jgsheppa/project/890b4ae1-03b6-448d-9daa-875c5f5add2a/settings/integration?pkg=@types/node&utm_source=github&utm_medium=upgrade-pr#auto-dep-upgrades)\n\n\n","created_at":"2021-07-21T00:55:53Z","updated_at":"2021-07-21T08:00:04Z","closed_at":"2021-07-21T08:00:04Z","merged_at":"2021-07-21T08:00:04Z","merge_commit_sha":"53234f0c8eecf2ab187a148d02feb857c21f9fb9","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/3/commits","review_comments_url":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/3/comments","review_comment_url":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/3/comments","statuses_url":"https://api.github.com/repos/jgsheppa/dictionary-app/statuses/fb82ec3f16b56286c28e2a263321341e44890dda","head":{"label":"jgsheppa:snyk-upgrade-4e77843a0a56b2a9fdd8525e47b1bbef","ref":"snyk-upgrade-4e77843a0a56b2a9fdd8525e47b1bbef","sha":"fb82ec3f16b56286c28e2a263321341e44890dda","user":{"login":"jgsheppa","id":63818299,"node_id":"MDQ6VXNlcjYzODE4Mjk5","avatar_url":"https://avatars.githubusercontent.com/u/63818299?v=4","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","html_url":"https://github.com/jgsheppa","followers_url":"https://api.github.com/users/jgsheppa/followers","following_url":"https://api.github.com/users/jgsheppa/following{/other_user}","gists_url":"https://api.github.com/users/jgsheppa/gists{/gist_id}","starred_url":"https://api.github.com/users/jgsheppa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgsheppa/subscriptions","organizations_url":"https://api.github.com/users/jgsheppa/orgs","repos_url":"https://api.github.com/users/jgsheppa/repos","events_url":"https://api.github.com/users/jgsheppa/events{/privacy}","received_events_url":"https://api.github.com/users/jgsheppa/received_events","type":"User","site_admin":false},"repo":{"id":299556960,"node_id":"MDEwOlJlcG9zaXRvcnkyOTk1NTY5NjA=","name":"dictionary-app","full_name":"jgsheppa/dictionary-app","private":false,"owner":{"login":"jgsheppa","id":63818299,"node_id":"MDQ6VXNlcjYzODE4Mjk5","avatar_url":"https://avatars.githubusercontent.com/u/63818299?v=4","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","html_url":"https://github.com/jgsheppa","followers_url":"https://api.github.com/users/jgsheppa/followers","following_url":"https://api.github.com/users/jgsheppa/following{/other_user}","gists_url":"https://api.github.com/users/jgsheppa/gists{/gist_id}","starred_url":"https://api.github.com/users/jgsheppa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgsheppa/subscriptions","organizations_url":"https://api.github.com/users/jgsheppa/orgs","repos_url":"https://api.github.com/users/jgsheppa/repos","events_url":"https://api.github.com/users/jgsheppa/events{/privacy}","received_events_url":"https://api.github.com/users/jgsheppa/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jgsheppa/dictionary-app","description":null,"fork":false,"url":"https://api.github.com/repos/jgsheppa/dictionary-app","forks_url":"https://api.github.com/repos/jgsheppa/dictionary-app/forks","keys_url":"https://api.github.com/repos/jgsheppa/dictionary-app/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jgsheppa/dictionary-app/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jgsheppa/dictionary-app/teams","hooks_url":"https://api.github.com/repos/jgsheppa/dictionary-app/hooks","issue_events_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/events{/number}","events_url":"https://api.github.com/repos/jgsheppa/dictionary-app/events","assignees_url":"https://api.github.com/repos/jgsheppa/dictionary-app/assignees{/user}","branches_url":"https://api.github.com/repos/jgsheppa/dictionary-app/branches{/branch}","tags_url":"https://api.github.com/repos/jgsheppa/dictionary-app/tags","blobs_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/refs{/sha}","trees_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jgsheppa/dictionary-app/statuses/{sha}","languages_url":"https://api.github.com/repos/jgsheppa/dictionary-app/languages","stargazers_url":"https://api.github.com/repos/jgsheppa/dictionary-app/stargazers","contributors_url":"https://api.github.com/repos/jgsheppa/dictionary-app/contributors","subscribers_url":"https://api.github.com/repos/jgsheppa/dictionary-app/subscribers","subscription_url":"https://api.github.com/repos/jgsheppa/dictionary-app/subscription","commits_url":"https://api.github.com/repos/jgsheppa/dictionary-app/commits{/sha}","git_commits_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/commits{/sha}","comments_url":"https://api.github.com/repos/jgsheppa/dictionary-app/comments{/number}","issue_comment_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/comments{/number}","contents_url":"https://api.github.com/repos/jgsheppa/dictionary-app/contents/{+path}","compare_url":"https://api.github.com/repos/jgsheppa/dictionary-app/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jgsheppa/dictionary-app/merges","archive_url":"https://api.github.com/repos/jgsheppa/dictionary-app/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jgsheppa/dictionary-app/downloads","issues_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues{/number}","pulls_url":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls{/number}","milestones_url":"https://api.github.com/repos/jgsheppa/dictionary-app/milestones{/number}","notifications_url":"https://api.github.com/repos/jgsheppa/dictionary-app/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jgsheppa/dictionary-app/labels{/name}","releases_url":"https://api.github.com/repos/jgsheppa/dictionary-app/releases{/id}","deployments_url":"https://api.github.com/repos/jgsheppa/dictionary-app/deployments","created_at":"2020-09-29T08:43:49Z","updated_at":"2021-07-21T07:59:50Z","pushed_at":"2021-07-21T08:00:04Z","git_url":"git://github.com/jgsheppa/dictionary-app.git","ssh_url":"git@github.com:jgsheppa/dictionary-app.git","clone_url":"https://github.com/jgsheppa/dictionary-app.git","svn_url":"https://github.com/jgsheppa/dictionary-app","homepage":"https://word-divan.herokuapp.com/","size":5583,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"jgsheppa:master","ref":"master","sha":"6ea43a173456a2066c48366a163dbe11e7bcb649","user":{"login":"jgsheppa","id":63818299,"node_id":"MDQ6VXNlcjYzODE4Mjk5","avatar_url":"https://avatars.githubusercontent.com/u/63818299?v=4","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","html_url":"https://github.com/jgsheppa","followers_url":"https://api.github.com/users/jgsheppa/followers","following_url":"https://api.github.com/users/jgsheppa/following{/other_user}","gists_url":"https://api.github.com/users/jgsheppa/gists{/gist_id}","starred_url":"https://api.github.com/users/jgsheppa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgsheppa/subscriptions","organizations_url":"https://api.github.com/users/jgsheppa/orgs","repos_url":"https://api.github.com/users/jgsheppa/repos","events_url":"https://api.github.com/users/jgsheppa/events{/privacy}","received_events_url":"https://api.github.com/users/jgsheppa/received_events","type":"User","site_admin":false},"repo":{"id":299556960,"node_id":"MDEwOlJlcG9zaXRvcnkyOTk1NTY5NjA=","name":"dictionary-app","full_name":"jgsheppa/dictionary-app","private":false,"owner":{"login":"jgsheppa","id":63818299,"node_id":"MDQ6VXNlcjYzODE4Mjk5","avatar_url":"https://avatars.githubusercontent.com/u/63818299?v=4","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","html_url":"https://github.com/jgsheppa","followers_url":"https://api.github.com/users/jgsheppa/followers","following_url":"https://api.github.com/users/jgsheppa/following{/other_user}","gists_url":"https://api.github.com/users/jgsheppa/gists{/gist_id}","starred_url":"https://api.github.com/users/jgsheppa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgsheppa/subscriptions","organizations_url":"https://api.github.com/users/jgsheppa/orgs","repos_url":"https://api.github.com/users/jgsheppa/repos","events_url":"https://api.github.com/users/jgsheppa/events{/privacy}","received_events_url":"https://api.github.com/users/jgsheppa/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jgsheppa/dictionary-app","description":null,"fork":false,"url":"https://api.github.com/repos/jgsheppa/dictionary-app","forks_url":"https://api.github.com/repos/jgsheppa/dictionary-app/forks","keys_url":"https://api.github.com/repos/jgsheppa/dictionary-app/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jgsheppa/dictionary-app/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jgsheppa/dictionary-app/teams","hooks_url":"https://api.github.com/repos/jgsheppa/dictionary-app/hooks","issue_events_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/events{/number}","events_url":"https://api.github.com/repos/jgsheppa/dictionary-app/events","assignees_url":"https://api.github.com/repos/jgsheppa/dictionary-app/assignees{/user}","branches_url":"https://api.github.com/repos/jgsheppa/dictionary-app/branches{/branch}","tags_url":"https://api.github.com/repos/jgsheppa/dictionary-app/tags","blobs_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/refs{/sha}","trees_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jgsheppa/dictionary-app/statuses/{sha}","languages_url":"https://api.github.com/repos/jgsheppa/dictionary-app/languages","stargazers_url":"https://api.github.com/repos/jgsheppa/dictionary-app/stargazers","contributors_url":"https://api.github.com/repos/jgsheppa/dictionary-app/contributors","subscribers_url":"https://api.github.com/repos/jgsheppa/dictionary-app/subscribers","subscription_url":"https://api.github.com/repos/jgsheppa/dictionary-app/subscription","commits_url":"https://api.github.com/repos/jgsheppa/dictionary-app/commits{/sha}","git_commits_url":"https://api.github.com/repos/jgsheppa/dictionary-app/git/commits{/sha}","comments_url":"https://api.github.com/repos/jgsheppa/dictionary-app/comments{/number}","issue_comment_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/comments{/number}","contents_url":"https://api.github.com/repos/jgsheppa/dictionary-app/contents/{+path}","compare_url":"https://api.github.com/repos/jgsheppa/dictionary-app/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jgsheppa/dictionary-app/merges","archive_url":"https://api.github.com/repos/jgsheppa/dictionary-app/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jgsheppa/dictionary-app/downloads","issues_url":"https://api.github.com/repos/jgsheppa/dictionary-app/issues{/number}","pulls_url":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls{/number}","milestones_url":"https://api.github.com/repos/jgsheppa/dictionary-app/milestones{/number}","notifications_url":"https://api.github.com/repos/jgsheppa/dictionary-app/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jgsheppa/dictionary-app/labels{/name}","releases_url":"https://api.github.com/repos/jgsheppa/dictionary-app/releases{/id}","deployments_url":"https://api.github.com/repos/jgsheppa/dictionary-app/deployments","created_at":"2020-09-29T08:43:49Z","updated_at":"2021-07-21T07:59:50Z","pushed_at":"2021-07-21T08:00:04Z","git_url":"git://github.com/jgsheppa/dictionary-app.git","ssh_url":"git@github.com:jgsheppa/dictionary-app.git","clone_url":"https://github.com/jgsheppa/dictionary-app.git","svn_url":"https://github.com/jgsheppa/dictionary-app","homepage":"https://word-divan.herokuapp.com/","size":5583,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/3"},"html":{"href":"https://github.com/jgsheppa/dictionary-app/pull/3"},"issue":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/3"},"comments":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/3/comments"},"review_comment":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/pulls/3/commits"},"statuses":{"href":"https://api.github.com/repos/jgsheppa/dictionary-app/statuses/fb82ec3f16b56286c28e2a263321341e44890dda"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"jgsheppa","id":63818299,"node_id":"MDQ6VXNlcjYzODE4Mjk5","avatar_url":"https://avatars.githubusercontent.com/u/63818299?v=4","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","html_url":"https://github.com/jgsheppa","followers_url":"https://api.github.com/users/jgsheppa/followers","following_url":"https://api.github.com/users/jgsheppa/following{/other_user}","gists_url":"https://api.github.com/users/jgsheppa/gists{/gist_id}","starred_url":"https://api.github.com/users/jgsheppa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgsheppa/subscriptions","organizations_url":"https://api.github.com/users/jgsheppa/orgs","repos_url":"https://api.github.com/users/jgsheppa/repos","events_url":"https://api.github.com/users/jgsheppa/events{/privacy}","received_events_url":"https://api.github.com/users/jgsheppa/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":6,"deletions":6,"changed_files":2}},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515654","type":"PullRequestReviewEvent","actor":{"id":6770950,"login":"rebkwok","display_login":"rebkwok","gravatar_id":"","url":"https://api.github.com/users/rebkwok","avatar_url":"https://avatars.githubusercontent.com/u/6770950?"},"repo":{"id":379919397,"name":"opensafely-core/cohort-extractor-v2","url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2"},"payload":{"action":"created","review":{"id":711364936,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzY0OTM2","user":{"login":"rebkwok","id":6770950,"node_id":"MDQ6VXNlcjY3NzA5NTA=","avatar_url":"https://avatars.githubusercontent.com/u/6770950?v=4","gravatar_id":"","url":"https://api.github.com/users/rebkwok","html_url":"https://github.com/rebkwok","followers_url":"https://api.github.com/users/rebkwok/followers","following_url":"https://api.github.com/users/rebkwok/following{/other_user}","gists_url":"https://api.github.com/users/rebkwok/gists{/gist_id}","starred_url":"https://api.github.com/users/rebkwok/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rebkwok/subscriptions","organizations_url":"https://api.github.com/users/rebkwok/orgs","repos_url":"https://api.github.com/users/rebkwok/repos","events_url":"https://api.github.com/users/rebkwok/events{/privacy}","received_events_url":"https://api.github.com/users/rebkwok/received_events","type":"User","site_admin":false},"body":null,"commit_id":"db8811b0c38db5f90a9358cc74e3f7393fe0ce3f","submitted_at":"2021-07-21T08:00:04Z","state":"commented","html_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76#pullrequestreview-711364936","pull_request_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76","author_association":"COLLABORATOR","_links":{"html":{"href":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76#pullrequestreview-711364936"},"pull_request":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76"}}},"pull_request":{"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76","id":692523207,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyNTIzMjA3","html_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76","diff_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76.diff","patch_url":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76.patch","issue_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76","number":76,"state":"open","locked":false,"title":"Categorising by truthiness (non-nullness) of a value","user":{"login":"rebkwok","id":6770950,"node_id":"MDQ6VXNlcjY3NzA5NTA=","avatar_url":"https://avatars.githubusercontent.com/u/6770950?v=4","gravatar_id":"","url":"https://api.github.com/users/rebkwok","html_url":"https://github.com/rebkwok","followers_url":"https://api.github.com/users/rebkwok/followers","following_url":"https://api.github.com/users/rebkwok/following{/other_user}","gists_url":"https://api.github.com/users/rebkwok/gists{/gist_id}","starred_url":"https://api.github.com/users/rebkwok/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rebkwok/subscriptions","organizations_url":"https://api.github.com/users/rebkwok/orgs","repos_url":"https://api.github.com/users/rebkwok/repos","events_url":"https://api.github.com/users/rebkwok/events{/privacy}","received_events_url":"https://api.github.com/users/rebkwok/received_events","type":"User","site_admin":false},"body":"Allows categorisation on whether a Value (of any sort) is truthy, by using a `c` function that returns a comparator for != None. Since every patient will have a row in any value table created from a filter or aggregation, we can use the None values to check for existence.","created_at":"2021-07-19T11:29:40Z","updated_at":"2021-07-21T08:00:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"af958dfb92c525be886993e5219c5b12374ea86a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/commits","review_comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/comments","review_comment_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/comments{/number}","comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76/comments","statuses_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/db8811b0c38db5f90a9358cc74e3f7393fe0ce3f","head":{"label":"opensafely-core:categorise_existence","ref":"categorise_existence","sha":"db8811b0c38db5f90a9358cc74e3f7393fe0ce3f","user":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"repo":{"id":379919397,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk5MTkzOTc=","name":"cohort-extractor-v2","full_name":"opensafely-core/cohort-extractor-v2","private":false,"owner":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opensafely-core/cohort-extractor-v2","description":null,"fork":false,"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2","forks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/forks","keys_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/teams","hooks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/hooks","issue_events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/events{/number}","events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/events","assignees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/assignees{/user}","branches_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/branches{/branch}","tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/tags","blobs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/refs{/sha}","trees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/{sha}","languages_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/languages","stargazers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/stargazers","contributors_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contributors","subscribers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscribers","subscription_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscription","commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/commits{/sha}","git_commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/commits{/sha}","comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/comments{/number}","issue_comment_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/comments{/number}","contents_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contents/{+path}","compare_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/merges","archive_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/downloads","issues_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues{/number}","pulls_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls{/number}","milestones_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/milestones{/number}","notifications_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/labels{/name}","releases_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/releases{/id}","deployments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/deployments","created_at":"2021-06-24T12:36:00Z","updated_at":"2021-07-20T09:16:30Z","pushed_at":"2021-07-20T17:10:34Z","git_url":"git://github.com/opensafely-core/cohort-extractor-v2.git","ssh_url":"git@github.com:opensafely-core/cohort-extractor-v2.git","clone_url":"https://github.com/opensafely-core/cohort-extractor-v2.git","svn_url":"https://github.com/opensafely-core/cohort-extractor-v2","homepage":null,"size":488,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":19,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":19,"watchers":0,"default_branch":"main"}},"base":{"label":"opensafely-core:main","ref":"main","sha":"b72f356cbe1883b99426e46be9a67926269dc62a","user":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"repo":{"id":379919397,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk5MTkzOTc=","name":"cohort-extractor-v2","full_name":"opensafely-core/cohort-extractor-v2","private":false,"owner":{"login":"opensafely-core","id":77844139,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc3ODQ0MTM5","avatar_url":"https://avatars.githubusercontent.com/u/77844139?v=4","gravatar_id":"","url":"https://api.github.com/users/opensafely-core","html_url":"https://github.com/opensafely-core","followers_url":"https://api.github.com/users/opensafely-core/followers","following_url":"https://api.github.com/users/opensafely-core/following{/other_user}","gists_url":"https://api.github.com/users/opensafely-core/gists{/gist_id}","starred_url":"https://api.github.com/users/opensafely-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensafely-core/subscriptions","organizations_url":"https://api.github.com/users/opensafely-core/orgs","repos_url":"https://api.github.com/users/opensafely-core/repos","events_url":"https://api.github.com/users/opensafely-core/events{/privacy}","received_events_url":"https://api.github.com/users/opensafely-core/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opensafely-core/cohort-extractor-v2","description":null,"fork":false,"url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2","forks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/forks","keys_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/teams","hooks_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/hooks","issue_events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/events{/number}","events_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/events","assignees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/assignees{/user}","branches_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/branches{/branch}","tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/tags","blobs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/refs{/sha}","trees_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/{sha}","languages_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/languages","stargazers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/stargazers","contributors_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contributors","subscribers_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscribers","subscription_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/subscription","commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/commits{/sha}","git_commits_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/git/commits{/sha}","comments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/comments{/number}","issue_comment_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/comments{/number}","contents_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/contents/{+path}","compare_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/merges","archive_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/downloads","issues_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues{/number}","pulls_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls{/number}","milestones_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/milestones{/number}","notifications_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/labels{/name}","releases_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/releases{/id}","deployments_url":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/deployments","created_at":"2021-06-24T12:36:00Z","updated_at":"2021-07-20T09:16:30Z","pushed_at":"2021-07-20T17:10:34Z","git_url":"git://github.com/opensafely-core/cohort-extractor-v2.git","ssh_url":"git@github.com:opensafely-core/cohort-extractor-v2.git","clone_url":"https://github.com/opensafely-core/cohort-extractor-v2.git","svn_url":"https://github.com/opensafely-core/cohort-extractor-v2","homepage":null,"size":488,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":19,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":19,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76"},"html":{"href":"https://github.com/opensafely-core/cohort-extractor-v2/pull/76"},"issue":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76"},"comments":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/issues/76/comments"},"review_comments":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/comments"},"review_comment":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/pulls/76/commits"},"statuses":{"href":"https://api.github.com/repos/opensafely-core/cohort-extractor-v2/statuses/db8811b0c38db5f90a9358cc74e3f7393fe0ce3f"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":77844139,"login":"opensafely-core","gravatar_id":"","url":"https://api.github.com/orgs/opensafely-core","avatar_url":"https://avatars.githubusercontent.com/u/77844139?"}} +{"id":"17245515665","type":"PushEvent","actor":{"id":3379460,"login":"beanslee2012","display_login":"beanslee2012","gravatar_id":"","url":"https://api.github.com/users/beanslee2012","avatar_url":"https://avatars.githubusercontent.com/u/3379460?"},"repo":{"id":215003385,"name":"beanslee2012/games","url":"https://api.github.com/repos/beanslee2012/games"},"payload":{"push_id":7562055527,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"382532ab0018e3a3b1e534b8c8fc55d33a9b2e79","before":"828a7e3fde4d5b3dd564e7064ea09361523439d2","commits":[{"sha":"382532ab0018e3a3b1e534b8c8fc55d33a9b2e79","author":{"name":"beanslee2012","email":"9aadcc12fe6ab8c01e6245204a102e9e34317220@gmail.com"},"message":"daily update","distinct":true,"url":"https://api.github.com/repos/beanslee2012/games/commits/382532ab0018e3a3b1e534b8c8fc55d33a9b2e79"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515672","type":"CreateEvent","actor":{"id":83330540,"login":"kamky","display_login":"kamky","gravatar_id":"","url":"https://api.github.com/users/kamky","avatar_url":"https://avatars.githubusercontent.com/u/83330540?"},"repo":{"id":378913953,"name":"kamky/react-app-six-cities","url":"https://api.github.com/repos/kamky/react-app-six-cities"},"payload":{"ref":"static-pages","ref_type":"branch","master_branch":"main","description":"React app \"6 Cities\"","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515680","type":"PushEvent","actor":{"id":82876799,"login":"hvaish01","display_login":"hvaish01","gravatar_id":"","url":"https://api.github.com/users/hvaish01","avatar_url":"https://avatars.githubusercontent.com/u/82876799?"},"repo":{"id":359772248,"name":"hvaish01/StixBundles","url":"https://api.github.com/repos/hvaish01/StixBundles"},"payload":{"push_id":7562055531,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"fac3f618997cee47d057c96a627ac4fa9fc09549","before":"73506f5776e1449063f1ff0220446408fb6865aa","commits":[{"sha":"fac3f618997cee47d057c96a627ac4fa9fc09549","author":{"name":"hvaish01","email":"2afd248fb67ac19cdeb0148e32ca7ee71c30c76d@users.noreply.github.com"},"message":"Updated at 01:00:04 21-07-2021","distinct":true,"url":"https://api.github.com/repos/hvaish01/StixBundles/commits/fac3f618997cee47d057c96a627ac4fa9fc09549"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515729","type":"WatchEvent","actor":{"id":87158991,"login":"heihei030","display_login":"heihei030","gravatar_id":"","url":"https://api.github.com/users/heihei030","avatar_url":"https://avatars.githubusercontent.com/u/87158991?"},"repo":{"id":21289110,"name":"vinta/awesome-python","url":"https://api.github.com/repos/vinta/awesome-python"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515735","type":"ForkEvent","actor":{"id":69457338,"login":"Tikhonov-Chen","display_login":"Tikhonov-Chen","gravatar_id":"","url":"https://api.github.com/users/Tikhonov-Chen","avatar_url":"https://avatars.githubusercontent.com/u/69457338?"},"repo":{"id":272731386,"name":"l0o0/jasminum","url":"https://api.github.com/repos/l0o0/jasminum"},"payload":{"forkee":{"id":388040581,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1ODE=","name":"jasminum","full_name":"Tikhonov-Chen/jasminum","private":false,"owner":{"login":"Tikhonov-Chen","id":69457338,"node_id":"MDQ6VXNlcjY5NDU3MzM4","avatar_url":"https://avatars.githubusercontent.com/u/69457338?v=4","gravatar_id":"","url":"https://api.github.com/users/Tikhonov-Chen","html_url":"https://github.com/Tikhonov-Chen","followers_url":"https://api.github.com/users/Tikhonov-Chen/followers","following_url":"https://api.github.com/users/Tikhonov-Chen/following{/other_user}","gists_url":"https://api.github.com/users/Tikhonov-Chen/gists{/gist_id}","starred_url":"https://api.github.com/users/Tikhonov-Chen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tikhonov-Chen/subscriptions","organizations_url":"https://api.github.com/users/Tikhonov-Chen/orgs","repos_url":"https://api.github.com/users/Tikhonov-Chen/repos","events_url":"https://api.github.com/users/Tikhonov-Chen/events{/privacy}","received_events_url":"https://api.github.com/users/Tikhonov-Chen/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Tikhonov-Chen/jasminum","description":"A Zotero add-on to retrive CNKI meta data. 一个简单的Zotero 插件,用于识别中文元数据","fork":true,"url":"https://api.github.com/repos/Tikhonov-Chen/jasminum","forks_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/forks","keys_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/teams","hooks_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/hooks","issue_events_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/issues/events{/number}","events_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/events","assignees_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/assignees{/user}","branches_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/branches{/branch}","tags_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/tags","blobs_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/git/refs{/sha}","trees_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/statuses/{sha}","languages_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/languages","stargazers_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/stargazers","contributors_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/contributors","subscribers_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/subscribers","subscription_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/subscription","commits_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/commits{/sha}","git_commits_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/git/commits{/sha}","comments_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/comments{/number}","issue_comment_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/issues/comments{/number}","contents_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/contents/{+path}","compare_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/merges","archive_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/downloads","issues_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/issues{/number}","pulls_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/pulls{/number}","milestones_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/milestones{/number}","notifications_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/labels{/name}","releases_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/releases{/id}","deployments_url":"https://api.github.com/repos/Tikhonov-Chen/jasminum/deployments","created_at":"2021-07-21T08:00:04Z","updated_at":"2021-07-21T05:09:08Z","pushed_at":"2021-07-13T15:03:36Z","git_url":"git://github.com/Tikhonov-Chen/jasminum.git","ssh_url":"git@github.com:Tikhonov-Chen/jasminum.git","clone_url":"https://github.com/Tikhonov-Chen/jasminum.git","svn_url":"https://github.com/Tikhonov-Chen/jasminum","homepage":"","size":229,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515744","type":"IssuesEvent","actor":{"id":34935391,"login":"kondratyev-a","display_login":"kondratyev-a","gravatar_id":"","url":"https://api.github.com/users/kondratyev-a","avatar_url":"https://avatars.githubusercontent.com/u/34935391?"},"repo":{"id":378857135,"name":"kondratyev-a/pet-clinic","url":"https://api.github.com/repos/kondratyev-a/pet-clinic"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/kondratyev-a/pet-clinic/issues/56","repository_url":"https://api.github.com/repos/kondratyev-a/pet-clinic","labels_url":"https://api.github.com/repos/kondratyev-a/pet-clinic/issues/56/labels{/name}","comments_url":"https://api.github.com/repos/kondratyev-a/pet-clinic/issues/56/comments","events_url":"https://api.github.com/repos/kondratyev-a/pet-clinic/issues/56/events","html_url":"https://github.com/kondratyev-a/pet-clinic/issues/56","id":949435875,"node_id":"MDU6SXNzdWU5NDk0MzU4NzU=","number":56,"title":"Add ability to add or update a Pet","user":{"login":"kondratyev-a","id":34935391,"node_id":"MDQ6VXNlcjM0OTM1Mzkx","avatar_url":"https://avatars.githubusercontent.com/u/34935391?v=4","gravatar_id":"","url":"https://api.github.com/users/kondratyev-a","html_url":"https://github.com/kondratyev-a","followers_url":"https://api.github.com/users/kondratyev-a/followers","following_url":"https://api.github.com/users/kondratyev-a/following{/other_user}","gists_url":"https://api.github.com/users/kondratyev-a/gists{/gist_id}","starred_url":"https://api.github.com/users/kondratyev-a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kondratyev-a/subscriptions","organizations_url":"https://api.github.com/users/kondratyev-a/orgs","repos_url":"https://api.github.com/users/kondratyev-a/repos","events_url":"https://api.github.com/users/kondratyev-a/events{/privacy}","received_events_url":"https://api.github.com/users/kondratyev-a/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T08:00:05Z","updated_at":"2021-07-21T08:00:05Z","closed_at":null,"author_association":"OWNER","active_lock_reason":null,"body":"","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515746","type":"DeleteEvent","actor":{"id":526307,"login":"endorama","display_login":"endorama","gravatar_id":"","url":"https://api.github.com/users/endorama","avatar_url":"https://avatars.githubusercontent.com/u/526307?"},"repo":{"id":16554739,"name":"elastic/beats","url":"https://api.github.com/repos/elastic/beats"},"payload":{"ref":"mergify/bp/7.x/pr-26870","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":6764390,"login":"elastic","gravatar_id":"","url":"https://api.github.com/orgs/elastic","avatar_url":"https://avatars.githubusercontent.com/u/6764390?"}} +{"id":"17245515749","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7562055542,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"a9dfcf1ddd1ce9d3a35d090a2e4c00abf3d7d1d3","before":"0b9c99f1728d7f200085293478e6e8549b40a455","commits":[{"sha":"a9dfcf1ddd1ce9d3a35d090a2e4c00abf3d7d1d3","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/a9dfcf1ddd1ce9d3a35d090a2e4c00abf3d7d1d3"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515750","type":"PushEvent","actor":{"id":22955941,"login":"Volodichev","display_login":"Volodichev","gravatar_id":"","url":"https://api.github.com/users/Volodichev","avatar_url":"https://avatars.githubusercontent.com/u/22955941?"},"repo":{"id":374556291,"name":"Volodichev/proxy-list","url":"https://api.github.com/repos/Volodichev/proxy-list"},"payload":{"push_id":7562055535,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"33f74e7da997df14a562e393c486a58384492190","before":"181c3bc89352583c52404e8de5b283597ee29295","commits":[{"sha":"33f74e7da997df14a562e393c486a58384492190","author":{"name":"Alexander","email":"db02dbb7d5376400a8069895ae273c92e0d1f31d@gmail.com"},"message":"hmn keys 21.07.21 11:00:01","distinct":true,"url":"https://api.github.com/repos/Volodichev/proxy-list/commits/33f74e7da997df14a562e393c486a58384492190"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515751","type":"PushEvent","actor":{"id":39004010,"login":"Tsanfer","display_login":"Tsanfer","gravatar_id":"","url":"https://api.github.com/users/Tsanfer","avatar_url":"https://avatars.githubusercontent.com/u/39004010?"},"repo":{"id":360417924,"name":"Tsanfer/web_object_detection","url":"https://api.github.com/repos/Tsanfer/web_object_detection"},"payload":{"push_id":7562055544,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"e41afd31a9bcf482b4314c394a88722fb43fa00b","before":"590b90d21a8c9f2709744e43c7f8038f75bf163d","commits":[{"sha":"e41afd31a9bcf482b4314c394a88722fb43fa00b","author":{"name":"Tsanfer","email":"ac64262defbfe8875884ab8cc7327a83f5152011@gmail.com"},"message":"Create test.file","distinct":true,"url":"https://api.github.com/repos/Tsanfer/web_object_detection/commits/e41afd31a9bcf482b4314c394a88722fb43fa00b"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515757","type":"IssuesEvent","actor":{"id":6112685,"login":"sebastiendeleze","display_login":"sebastiendeleze","gravatar_id":"","url":"https://api.github.com/users/sebastiendeleze","avatar_url":"https://avatars.githubusercontent.com/u/6112685?"},"repo":{"id":149142372,"name":"rero/sonar","url":"https://api.github.com/repos/rero/sonar"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/rero/sonar/issues/570","repository_url":"https://api.github.com/repos/rero/sonar","labels_url":"https://api.github.com/repos/rero/sonar/issues/570/labels{/name}","comments_url":"https://api.github.com/repos/rero/sonar/issues/570/comments","events_url":"https://api.github.com/repos/rero/sonar/issues/570/events","html_url":"https://github.com/rero/sonar/issues/570","id":903781328,"node_id":"MDU6SXNzdWU5MDM3ODEzMjg=","number":570,"title":"Mask a document from the public view","user":{"login":"pronguen","id":8154915,"node_id":"MDQ6VXNlcjgxNTQ5MTU=","avatar_url":"https://avatars.githubusercontent.com/u/8154915?v=4","gravatar_id":"","url":"https://api.github.com/users/pronguen","html_url":"https://github.com/pronguen","followers_url":"https://api.github.com/users/pronguen/followers","following_url":"https://api.github.com/users/pronguen/following{/other_user}","gists_url":"https://api.github.com/users/pronguen/gists{/gist_id}","starred_url":"https://api.github.com/users/pronguen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pronguen/subscriptions","organizations_url":"https://api.github.com/users/pronguen/orgs","repos_url":"https://api.github.com/users/pronguen/repos","events_url":"https://api.github.com/users/pronguen/events{/privacy}","received_events_url":"https://api.github.com/users/pronguen/received_events","type":"User","site_admin":false},"labels":[{"id":2544776289,"node_id":"MDU6TGFiZWwyNTQ0Nzc2Mjg5","url":"https://api.github.com/repos/rero/sonar/labels/new","name":"new","color":"A3F7F7","default":false,"description":"New feature"}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-05-27T13:46:51Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:03Z","author_association":"MEMBER","active_lock_reason":null,"body":"* A new field in the document editor (not in the deposit editor) enables to mask the document\r\n * It appears in the very bottom of the editor.\r\n * It appears by default when opening the form, disabled.\r\n * Same spec than for RERO ILS, see [JSON schema](https://github.com/rero/rero-ils/blob/dev/rero_ils/modules/documents/jsonschemas/documents/document-v0.0.1.json#L297)\r\n* An icon on the professional document detailed view indicates that the document is masked\r\n * Tooltip: \"Masked in the public view\"\r\n\r\n![image](https://user-images.githubusercontent.com/8154915/119862071-6b955080-bf18-11eb-8186-5410411f1bcf.png)\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":105555,"login":"rero","gravatar_id":"","url":"https://api.github.com/orgs/rero","avatar_url":"https://avatars.githubusercontent.com/u/105555?"}} +{"id":"17245515772","type":"WatchEvent","actor":{"id":69457338,"login":"Tikhonov-Chen","display_login":"Tikhonov-Chen","gravatar_id":"","url":"https://api.github.com/users/Tikhonov-Chen","avatar_url":"https://avatars.githubusercontent.com/u/69457338?"},"repo":{"id":272731386,"name":"l0o0/jasminum","url":"https://api.github.com/repos/l0o0/jasminum"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515784","type":"PushEvent","actor":{"id":2486411,"login":"DmitriiP","display_login":"DmitriiP","gravatar_id":"","url":"https://api.github.com/users/DmitriiP","avatar_url":"https://avatars.githubusercontent.com/u/2486411?"},"repo":{"id":135042737,"name":"DmitriiP/hello-github","url":"https://api.github.com/repos/DmitriiP/hello-github"},"payload":{"push_id":7562055553,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"f033dff665ffcb726b11cb101b666c98e97885b8","before":"9b5c6fbc0b1dfd590e253d6663020a84c7545cc2","commits":[{"sha":"f033dff665ffcb726b11cb101b666c98e97885b8","author":{"name":"Dmitrii Prihodco","email":"b597b9214547d49006fffec85ed8baebc73b6971@gmail.com"},"message":"Hello Github!","distinct":true,"url":"https://api.github.com/repos/DmitriiP/hello-github/commits/f033dff665ffcb726b11cb101b666c98e97885b8"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515785","type":"PushEvent","actor":{"id":55799457,"login":"wiisportsresort","display_login":"wiisportsresort","gravatar_id":"","url":"https://api.github.com/users/wiisportsresort","avatar_url":"https://avatars.githubusercontent.com/u/55799457?"},"repo":{"id":289093185,"name":"gamer-gang/gamerbot","url":"https://api.github.com/repos/gamer-gang/gamerbot"},"payload":{"push_id":7562055496,"size":1,"distinct_size":1,"ref":"refs/heads/djs-v13-packages","head":"ba08b2a5e86b51adc4b69cb640974170d42eca86","before":"e981f3513d685aa0872b5e48a2dbabe33c3b5eac","commits":[{"sha":"ba08b2a5e86b51adc4b69cb640974170d42eca86","author":{"name":"wiisportsresort","email":"4916bd11f433b46f57f656a801de1d7bae16e734@gmail.com"},"message":"update packages, rework color class, add $color","distinct":true,"url":"https://api.github.com/repos/gamer-gang/gamerbot/commits/ba08b2a5e86b51adc4b69cb640974170d42eca86"}]},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":58985866,"login":"gamer-gang","gravatar_id":"","url":"https://api.github.com/orgs/gamer-gang","avatar_url":"https://avatars.githubusercontent.com/u/58985866?"}} +{"id":"17245515889","type":"PullRequestReviewCommentEvent","actor":{"id":6417047,"login":"AlexeySachkov","display_login":"AlexeySachkov","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","avatar_url":"https://avatars.githubusercontent.com/u/6417047?"},"repo":{"id":127136978,"name":"KhronosGroup/SPIRV-LLVM-Translator","url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/comments/673748196","pull_request_review_id":711364920,"id":673748196,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3Mzc0ODE5Ng==","diff_hunk":"@@ -54,54 +54,51 @@ namespace SPIRV {\n \n class SPIRVLowerMemmoveBase {\n public:\n- SPIRVLowerMemmoveBase() : Context(nullptr), Mod(nullptr) {}\n+ SPIRVLowerMemmoveBase() : Context(nullptr) {}\n+\n void LowerMemMoveInst(MemMoveInst &I) {\n+ // There is no direct equivalent of @llvm.memmove in SPIR-V and the closest\n+ // instructions are 'OpMemoryCopy' and 'OpMemoryCopySized'.","path":"lib/SPIRV/SPIRVLowerMemmove.cpp","position":null,"original_position":9,"commit_id":"dbc54dd752c04bb65e8d2e2aedaaac22c247266e","original_commit_id":"1551e65e73bb6f1461c31cca3c6e75a0d57d5ce8","user":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"body":"Done in dbc54dd752c04bb65e8d2e2aedaaac22c247266e","created_at":"2021-07-21T08:00:03Z","updated_at":"2021-07-21T08:00:03Z","html_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119#discussion_r673748196","pull_request_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/comments/673748196"},"html":{"href":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119#discussion_r673748196"},"pull_request":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119"}},"start_line":null,"original_start_line":null,"start_side":null,"line":null,"original_line":61,"side":"RIGHT","in_reply_to_id":673403624},"pull_request":{"url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119","id":693465500,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDY1NTAw","html_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119","diff_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119.diff","patch_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119.patch","issue_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119","number":1119,"state":"open","locked":false,"title":"Improve llvm.memmove translation","user":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"body":"The main change comparing to previous approach: we do not look for the underlying type which is being copied anymore. Instead, we directly allocate required amount of bytes and copy them.","created_at":"2021-07-20T13:26:14Z","updated_at":"2021-07-21T08:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1df177eccdd4f5230bed2491a8e681f5963ea9d7","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/commits","review_comments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/comments","review_comment_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/comments{/number}","comments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119/comments","statuses_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/statuses/dbc54dd752c04bb65e8d2e2aedaaac22c247266e","head":{"label":"AlexeySachkov:private/asachkov/memmove-translation-fixes","ref":"private/asachkov/memmove-translation-fixes","sha":"dbc54dd752c04bb65e8d2e2aedaaac22c247266e","user":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"repo":{"id":160663481,"node_id":"MDEwOlJlcG9zaXRvcnkxNjA2NjM0ODE=","name":"SPIRV-LLVM-Translator","full_name":"AlexeySachkov/SPIRV-LLVM-Translator","private":false,"owner":{"login":"AlexeySachkov","id":6417047,"node_id":"MDQ6VXNlcjY0MTcwNDc=","avatar_url":"https://avatars.githubusercontent.com/u/6417047?v=4","gravatar_id":"","url":"https://api.github.com/users/AlexeySachkov","html_url":"https://github.com/AlexeySachkov","followers_url":"https://api.github.com/users/AlexeySachkov/followers","following_url":"https://api.github.com/users/AlexeySachkov/following{/other_user}","gists_url":"https://api.github.com/users/AlexeySachkov/gists{/gist_id}","starred_url":"https://api.github.com/users/AlexeySachkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlexeySachkov/subscriptions","organizations_url":"https://api.github.com/users/AlexeySachkov/orgs","repos_url":"https://api.github.com/users/AlexeySachkov/repos","events_url":"https://api.github.com/users/AlexeySachkov/events{/privacy}","received_events_url":"https://api.github.com/users/AlexeySachkov/received_events","type":"User","site_admin":false},"html_url":"https://github.com/AlexeySachkov/SPIRV-LLVM-Translator","description":"A tool and a library for bi-directional translation between SPIR-V and LLVM IR","fork":true,"url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator","forks_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/forks","keys_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/teams","hooks_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/hooks","issue_events_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/issues/events{/number}","events_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/events","assignees_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/assignees{/user}","branches_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/branches{/branch}","tags_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/tags","blobs_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/refs{/sha}","trees_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/statuses/{sha}","languages_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/languages","stargazers_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/stargazers","contributors_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/contributors","subscribers_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/subscribers","subscription_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/subscription","commits_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/commits{/sha}","git_commits_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/git/commits{/sha}","comments_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/comments{/number}","issue_comment_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/issues/comments{/number}","contents_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/contents/{+path}","compare_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/merges","archive_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/downloads","issues_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/issues{/number}","pulls_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/pulls{/number}","milestones_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/milestones{/number}","notifications_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/labels{/name}","releases_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/releases{/id}","deployments_url":"https://api.github.com/repos/AlexeySachkov/SPIRV-LLVM-Translator/deployments","created_at":"2018-12-06T11:12:57Z","updated_at":"2021-02-17T09:49:57Z","pushed_at":"2021-07-21T07:59:11Z","git_url":"git://github.com/AlexeySachkov/SPIRV-LLVM-Translator.git","ssh_url":"git@github.com:AlexeySachkov/SPIRV-LLVM-Translator.git","clone_url":"https://github.com/AlexeySachkov/SPIRV-LLVM-Translator.git","svn_url":"https://github.com/AlexeySachkov/SPIRV-LLVM-Translator","homepage":null,"size":8654,"stargazers_count":0,"watchers_count":0,"language":"LLVM","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"KhronosGroup:master","ref":"master","sha":"6795cab62f3ed36e9a5cec235c62a8d92a40cc8f","user":{"login":"KhronosGroup","id":1608701,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDg3MDE=","avatar_url":"https://avatars.githubusercontent.com/u/1608701?v=4","gravatar_id":"","url":"https://api.github.com/users/KhronosGroup","html_url":"https://github.com/KhronosGroup","followers_url":"https://api.github.com/users/KhronosGroup/followers","following_url":"https://api.github.com/users/KhronosGroup/following{/other_user}","gists_url":"https://api.github.com/users/KhronosGroup/gists{/gist_id}","starred_url":"https://api.github.com/users/KhronosGroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KhronosGroup/subscriptions","organizations_url":"https://api.github.com/users/KhronosGroup/orgs","repos_url":"https://api.github.com/users/KhronosGroup/repos","events_url":"https://api.github.com/users/KhronosGroup/events{/privacy}","received_events_url":"https://api.github.com/users/KhronosGroup/received_events","type":"Organization","site_admin":false},"repo":{"id":127136978,"node_id":"MDEwOlJlcG9zaXRvcnkxMjcxMzY5Nzg=","name":"SPIRV-LLVM-Translator","full_name":"KhronosGroup/SPIRV-LLVM-Translator","private":false,"owner":{"login":"KhronosGroup","id":1608701,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDg3MDE=","avatar_url":"https://avatars.githubusercontent.com/u/1608701?v=4","gravatar_id":"","url":"https://api.github.com/users/KhronosGroup","html_url":"https://github.com/KhronosGroup","followers_url":"https://api.github.com/users/KhronosGroup/followers","following_url":"https://api.github.com/users/KhronosGroup/following{/other_user}","gists_url":"https://api.github.com/users/KhronosGroup/gists{/gist_id}","starred_url":"https://api.github.com/users/KhronosGroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KhronosGroup/subscriptions","organizations_url":"https://api.github.com/users/KhronosGroup/orgs","repos_url":"https://api.github.com/users/KhronosGroup/repos","events_url":"https://api.github.com/users/KhronosGroup/events{/privacy}","received_events_url":"https://api.github.com/users/KhronosGroup/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator","description":"A tool and a library for bi-directional translation between SPIR-V and LLVM IR","fork":false,"url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator","forks_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/forks","keys_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/teams","hooks_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/hooks","issue_events_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/events{/number}","events_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/events","assignees_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/assignees{/user}","branches_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/branches{/branch}","tags_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/tags","blobs_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/refs{/sha}","trees_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/statuses/{sha}","languages_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/languages","stargazers_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/stargazers","contributors_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/contributors","subscribers_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/subscribers","subscription_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/subscription","commits_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/commits{/sha}","git_commits_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/git/commits{/sha}","comments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/comments{/number}","issue_comment_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/comments{/number}","contents_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/contents/{+path}","compare_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/merges","archive_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/downloads","issues_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues{/number}","pulls_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls{/number}","milestones_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/milestones{/number}","notifications_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/labels{/name}","releases_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/releases{/id}","deployments_url":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/deployments","created_at":"2018-03-28T12:29:24Z","updated_at":"2021-07-19T17:01:10Z","pushed_at":"2021-07-21T07:59:13Z","git_url":"git://github.com/KhronosGroup/SPIRV-LLVM-Translator.git","ssh_url":"git@github.com:KhronosGroup/SPIRV-LLVM-Translator.git","clone_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator.git","svn_url":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator","homepage":null,"size":9402,"stargazers_count":248,"watchers_count":248,"language":"LLVM","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":113,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":104,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":113,"open_issues":104,"watchers":248,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119"},"html":{"href":"https://github.com/KhronosGroup/SPIRV-LLVM-Translator/pull/1119"},"issue":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119"},"comments":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/issues/1119/comments"},"review_comments":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/comments"},"review_comment":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/pulls/1119/commits"},"statuses":{"href":"https://api.github.com/repos/KhronosGroup/SPIRV-LLVM-Translator/statuses/dbc54dd752c04bb65e8d2e2aedaaac22c247266e"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T08:00:03Z","org":{"id":1608701,"login":"KhronosGroup","gravatar_id":"","url":"https://api.github.com/orgs/KhronosGroup","avatar_url":"https://avatars.githubusercontent.com/u/1608701?"}} +{"id":"17245515782","type":"PushEvent","actor":{"id":40586421,"login":"himobi","display_login":"himobi","gravatar_id":"","url":"https://api.github.com/users/himobi","avatar_url":"https://avatars.githubusercontent.com/u/40586421?"},"repo":{"id":138676186,"name":"himobi/hotspot","url":"https://api.github.com/repos/himobi/hotspot"},"payload":{"push_id":7562055557,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"705a507d5d295d2589334ae412391fd7afe88ce7","before":"cd0cdb510c3582ac98540b6700fa98d2a52855eb","commits":[{"sha":"705a507d5d295d2589334ae412391fd7afe88ce7","author":{"name":"himobi","email":"25be52bce459ebff50c126f5ac512648f87cbe75@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/himobi/hotspot/commits/705a507d5d295d2589334ae412391fd7afe88ce7"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515788","type":"PushEvent","actor":{"id":22955941,"login":"Volodichev","display_login":"Volodichev","gravatar_id":"","url":"https://api.github.com/users/Volodichev","avatar_url":"https://avatars.githubusercontent.com/u/22955941?"},"repo":{"id":374556291,"name":"Volodichev/proxy-list","url":"https://api.github.com/repos/Volodichev/proxy-list"},"payload":{"push_id":7562055550,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"816d2973250cad2d38d0c9556183a477f06ab72d","before":"33f74e7da997df14a562e393c486a58384492190","commits":[{"sha":"816d2973250cad2d38d0c9556183a477f06ab72d","author":{"name":"Alexander","email":"db02dbb7d5376400a8069895ae273c92e0d1f31d@gmail.com"},"message":"awm proxy 21.07.21 11:00:01","distinct":true,"url":"https://api.github.com/repos/Volodichev/proxy-list/commits/816d2973250cad2d38d0c9556183a477f06ab72d"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515791","type":"PushEvent","actor":{"id":60849980,"login":"jordans-bot","display_login":"jordans-bot","gravatar_id":"","url":"https://api.github.com/users/jordans-bot","avatar_url":"https://avatars.githubusercontent.com/u/60849980?"},"repo":{"id":139190305,"name":"JTuwiner/bitcoinclock","url":"https://api.github.com/repos/JTuwiner/bitcoinclock"},"payload":{"push_id":7562055552,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"5a001f4a5f5af68ec3da02c1ca004882a321b783","before":"cac51092053a2547310619861014856104b0e08e","commits":[{"sha":"5a001f4a5f5af68ec3da02c1ca004882a321b783","author":{"name":"jordans-bot","email":"6323b4aabf9938a958e69e4471281fb61992bb8f@users.noreply.github.com"},"message":"Updated block height","distinct":true,"url":"https://api.github.com/repos/JTuwiner/bitcoinclock/commits/5a001f4a5f5af68ec3da02c1ca004882a321b783"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515801","type":"PushEvent","actor":{"id":63818299,"login":"jgsheppa","display_login":"jgsheppa","gravatar_id":"","url":"https://api.github.com/users/jgsheppa","avatar_url":"https://avatars.githubusercontent.com/u/63818299?"},"repo":{"id":299556960,"name":"jgsheppa/dictionary-app","url":"https://api.github.com/repos/jgsheppa/dictionary-app"},"payload":{"push_id":7562055558,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"53234f0c8eecf2ab187a148d02feb857c21f9fb9","before":"5bb3e1434ab4eb09f024818a898526cce88e6180","commits":[{"sha":"53234f0c8eecf2ab187a148d02feb857c21f9fb9","author":{"name":"Snyk bot","email":"6524ae312692b75c8ed05087d0aa3c757ac39348@snyk.io"},"message":"fix: upgrade @types/node from 14.14.7 to 14.17.4 (#3)\n\nSnyk has created this PR to upgrade @types/node from 14.14.7 to 14.17.4.\r\n\r\nSee this package in npm:\r\n\r\n\r\nSee this project in Snyk:\r\nhttps://app.snyk.io/org/jgsheppa/project/890b4ae1-03b6-448d-9daa-875c5f5add2a?utm_source=github&utm_medium=upgrade-pr","distinct":true,"url":"https://api.github.com/repos/jgsheppa/dictionary-app/commits/53234f0c8eecf2ab187a148d02feb857c21f9fb9"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515816","type":"PushEvent","actor":{"id":31001511,"login":"Kimyuhwanpeter","display_login":"Kimyuhwanpeter","gravatar_id":"","url":"https://api.github.com/users/Kimyuhwanpeter","avatar_url":"https://avatars.githubusercontent.com/u/31001511?"},"repo":{"id":388040321,"name":"Kimyuhwanpeter/F2M_model_10","url":"https://api.github.com/repos/Kimyuhwanpeter/F2M_model_10"},"payload":{"push_id":7562055556,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"ba74d50f0bf2754b883bac59b6b513bc14f8c982","before":"ada7324ccce700559a1b85840469700cf18e770c","commits":[{"sha":"ba74d50f0bf2754b883bac59b6b513bc14f8c982","author":{"name":"YYu HHwan KKim","email":"dece1e7f54de1f8c609c68d4650eb27c2dca2de6@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/Kimyuhwanpeter/F2M_model_10/commits/ba74d50f0bf2754b883bac59b6b513bc14f8c982"}]},"public":true,"created_at":"2021-07-21T08:00:05Z"} +{"id":"17245515827","type":"PullRequestEvent","actor":{"id":1858241,"login":"willseeyou","display_login":"willseeyou","gravatar_id":"","url":"https://api.github.com/users/willseeyou","avatar_url":"https://avatars.githubusercontent.com/u/1858241?"},"repo":{"id":18511687,"name":"igniterealtime/Openfire","url":"https://api.github.com/repos/igniterealtime/Openfire"},"payload":{"action":"opened","number":1864,"pull_request":{"url":"https://api.github.com/repos/igniterealtime/Openfire/pulls/1864","id":694141706,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTQxNzA2","html_url":"https://github.com/igniterealtime/Openfire/pull/1864","diff_url":"https://github.com/igniterealtime/Openfire/pull/1864.diff","patch_url":"https://github.com/igniterealtime/Openfire/pull/1864.patch","issue_url":"https://api.github.com/repos/igniterealtime/Openfire/issues/1864","number":1864,"state":"open","locked":false,"title":"fix wrong typo responsbile and immediatelly","user":{"login":"willseeyou","id":1858241,"node_id":"MDQ6VXNlcjE4NTgyNDE=","avatar_url":"https://avatars.githubusercontent.com/u/1858241?v=4","gravatar_id":"","url":"https://api.github.com/users/willseeyou","html_url":"https://github.com/willseeyou","followers_url":"https://api.github.com/users/willseeyou/followers","following_url":"https://api.github.com/users/willseeyou/following{/other_user}","gists_url":"https://api.github.com/users/willseeyou/gists{/gist_id}","starred_url":"https://api.github.com/users/willseeyou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/willseeyou/subscriptions","organizations_url":"https://api.github.com/users/willseeyou/orgs","repos_url":"https://api.github.com/users/willseeyou/repos","events_url":"https://api.github.com/users/willseeyou/events{/privacy}","received_events_url":"https://api.github.com/users/willseeyou/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T08:00:05Z","updated_at":"2021-07-21T08:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/igniterealtime/Openfire/pulls/1864/commits","review_comments_url":"https://api.github.com/repos/igniterealtime/Openfire/pulls/1864/comments","review_comment_url":"https://api.github.com/repos/igniterealtime/Openfire/pulls/comments{/number}","comments_url":"https://api.github.com/repos/igniterealtime/Openfire/issues/1864/comments","statuses_url":"https://api.github.com/repos/igniterealtime/Openfire/statuses/ed7b8cbe51fc30bff8c3e7c41749934d093bb699","head":{"label":"willseeyou:patch-1","ref":"patch-1","sha":"ed7b8cbe51fc30bff8c3e7c41749934d093bb699","user":{"login":"willseeyou","id":1858241,"node_id":"MDQ6VXNlcjE4NTgyNDE=","avatar_url":"https://avatars.githubusercontent.com/u/1858241?v=4","gravatar_id":"","url":"https://api.github.com/users/willseeyou","html_url":"https://github.com/willseeyou","followers_url":"https://api.github.com/users/willseeyou/followers","following_url":"https://api.github.com/users/willseeyou/following{/other_user}","gists_url":"https://api.github.com/users/willseeyou/gists{/gist_id}","starred_url":"https://api.github.com/users/willseeyou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/willseeyou/subscriptions","organizations_url":"https://api.github.com/users/willseeyou/orgs","repos_url":"https://api.github.com/users/willseeyou/repos","events_url":"https://api.github.com/users/willseeyou/events{/privacy}","received_events_url":"https://api.github.com/users/willseeyou/received_events","type":"User","site_admin":false},"repo":{"id":386548440,"node_id":"MDEwOlJlcG9zaXRvcnkzODY1NDg0NDA=","name":"Openfire","full_name":"willseeyou/Openfire","private":false,"owner":{"login":"willseeyou","id":1858241,"node_id":"MDQ6VXNlcjE4NTgyNDE=","avatar_url":"https://avatars.githubusercontent.com/u/1858241?v=4","gravatar_id":"","url":"https://api.github.com/users/willseeyou","html_url":"https://github.com/willseeyou","followers_url":"https://api.github.com/users/willseeyou/followers","following_url":"https://api.github.com/users/willseeyou/following{/other_user}","gists_url":"https://api.github.com/users/willseeyou/gists{/gist_id}","starred_url":"https://api.github.com/users/willseeyou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/willseeyou/subscriptions","organizations_url":"https://api.github.com/users/willseeyou/orgs","repos_url":"https://api.github.com/users/willseeyou/repos","events_url":"https://api.github.com/users/willseeyou/events{/privacy}","received_events_url":"https://api.github.com/users/willseeyou/received_events","type":"User","site_admin":false},"html_url":"https://github.com/willseeyou/Openfire","description":"An XMPP server licensed under the Open Source Apache License.","fork":true,"url":"https://api.github.com/repos/willseeyou/Openfire","forks_url":"https://api.github.com/repos/willseeyou/Openfire/forks","keys_url":"https://api.github.com/repos/willseeyou/Openfire/keys{/key_id}","collaborators_url":"https://api.github.com/repos/willseeyou/Openfire/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/willseeyou/Openfire/teams","hooks_url":"https://api.github.com/repos/willseeyou/Openfire/hooks","issue_events_url":"https://api.github.com/repos/willseeyou/Openfire/issues/events{/number}","events_url":"https://api.github.com/repos/willseeyou/Openfire/events","assignees_url":"https://api.github.com/repos/willseeyou/Openfire/assignees{/user}","branches_url":"https://api.github.com/repos/willseeyou/Openfire/branches{/branch}","tags_url":"https://api.github.com/repos/willseeyou/Openfire/tags","blobs_url":"https://api.github.com/repos/willseeyou/Openfire/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/willseeyou/Openfire/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/willseeyou/Openfire/git/refs{/sha}","trees_url":"https://api.github.com/repos/willseeyou/Openfire/git/trees{/sha}","statuses_url":"https://api.github.com/repos/willseeyou/Openfire/statuses/{sha}","languages_url":"https://api.github.com/repos/willseeyou/Openfire/languages","stargazers_url":"https://api.github.com/repos/willseeyou/Openfire/stargazers","contributors_url":"https://api.github.com/repos/willseeyou/Openfire/contributors","subscribers_url":"https://api.github.com/repos/willseeyou/Openfire/subscribers","subscription_url":"https://api.github.com/repos/willseeyou/Openfire/subscription","commits_url":"https://api.github.com/repos/willseeyou/Openfire/commits{/sha}","git_commits_url":"https://api.github.com/repos/willseeyou/Openfire/git/commits{/sha}","comments_url":"https://api.github.com/repos/willseeyou/Openfire/comments{/number}","issue_comment_url":"https://api.github.com/repos/willseeyou/Openfire/issues/comments{/number}","contents_url":"https://api.github.com/repos/willseeyou/Openfire/contents/{+path}","compare_url":"https://api.github.com/repos/willseeyou/Openfire/compare/{base}...{head}","merges_url":"https://api.github.com/repos/willseeyou/Openfire/merges","archive_url":"https://api.github.com/repos/willseeyou/Openfire/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/willseeyou/Openfire/downloads","issues_url":"https://api.github.com/repos/willseeyou/Openfire/issues{/number}","pulls_url":"https://api.github.com/repos/willseeyou/Openfire/pulls{/number}","milestones_url":"https://api.github.com/repos/willseeyou/Openfire/milestones{/number}","notifications_url":"https://api.github.com/repos/willseeyou/Openfire/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/willseeyou/Openfire/labels{/name}","releases_url":"https://api.github.com/repos/willseeyou/Openfire/releases{/id}","deployments_url":"https://api.github.com/repos/willseeyou/Openfire/deployments","created_at":"2021-07-16T07:25:01Z","updated_at":"2021-07-16T07:25:02Z","pushed_at":"2021-07-21T07:59:54Z","git_url":"git://github.com/willseeyou/Openfire.git","ssh_url":"git@github.com:willseeyou/Openfire.git","clone_url":"https://github.com/willseeyou/Openfire.git","svn_url":"https://github.com/willseeyou/Openfire","homepage":"https://igniterealtime.org/projects/openfire/","size":862051,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"igniterealtime:master","ref":"master","sha":"31b55763fdd5409e17f7b42391a7bee618230726","user":{"login":"igniterealtime","id":5991032,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5OTEwMzI=","avatar_url":"https://avatars.githubusercontent.com/u/5991032?v=4","gravatar_id":"","url":"https://api.github.com/users/igniterealtime","html_url":"https://github.com/igniterealtime","followers_url":"https://api.github.com/users/igniterealtime/followers","following_url":"https://api.github.com/users/igniterealtime/following{/other_user}","gists_url":"https://api.github.com/users/igniterealtime/gists{/gist_id}","starred_url":"https://api.github.com/users/igniterealtime/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/igniterealtime/subscriptions","organizations_url":"https://api.github.com/users/igniterealtime/orgs","repos_url":"https://api.github.com/users/igniterealtime/repos","events_url":"https://api.github.com/users/igniterealtime/events{/privacy}","received_events_url":"https://api.github.com/users/igniterealtime/received_events","type":"Organization","site_admin":false},"repo":{"id":18511687,"node_id":"MDEwOlJlcG9zaXRvcnkxODUxMTY4Nw==","name":"Openfire","full_name":"igniterealtime/Openfire","private":false,"owner":{"login":"igniterealtime","id":5991032,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5OTEwMzI=","avatar_url":"https://avatars.githubusercontent.com/u/5991032?v=4","gravatar_id":"","url":"https://api.github.com/users/igniterealtime","html_url":"https://github.com/igniterealtime","followers_url":"https://api.github.com/users/igniterealtime/followers","following_url":"https://api.github.com/users/igniterealtime/following{/other_user}","gists_url":"https://api.github.com/users/igniterealtime/gists{/gist_id}","starred_url":"https://api.github.com/users/igniterealtime/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/igniterealtime/subscriptions","organizations_url":"https://api.github.com/users/igniterealtime/orgs","repos_url":"https://api.github.com/users/igniterealtime/repos","events_url":"https://api.github.com/users/igniterealtime/events{/privacy}","received_events_url":"https://api.github.com/users/igniterealtime/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/igniterealtime/Openfire","description":"An XMPP server licensed under the Open Source Apache License.","fork":false,"url":"https://api.github.com/repos/igniterealtime/Openfire","forks_url":"https://api.github.com/repos/igniterealtime/Openfire/forks","keys_url":"https://api.github.com/repos/igniterealtime/Openfire/keys{/key_id}","collaborators_url":"https://api.github.com/repos/igniterealtime/Openfire/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/igniterealtime/Openfire/teams","hooks_url":"https://api.github.com/repos/igniterealtime/Openfire/hooks","issue_events_url":"https://api.github.com/repos/igniterealtime/Openfire/issues/events{/number}","events_url":"https://api.github.com/repos/igniterealtime/Openfire/events","assignees_url":"https://api.github.com/repos/igniterealtime/Openfire/assignees{/user}","branches_url":"https://api.github.com/repos/igniterealtime/Openfire/branches{/branch}","tags_url":"https://api.github.com/repos/igniterealtime/Openfire/tags","blobs_url":"https://api.github.com/repos/igniterealtime/Openfire/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/igniterealtime/Openfire/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/igniterealtime/Openfire/git/refs{/sha}","trees_url":"https://api.github.com/repos/igniterealtime/Openfire/git/trees{/sha}","statuses_url":"https://api.github.com/repos/igniterealtime/Openfire/statuses/{sha}","languages_url":"https://api.github.com/repos/igniterealtime/Openfire/languages","stargazers_url":"https://api.github.com/repos/igniterealtime/Openfire/stargazers","contributors_url":"https://api.github.com/repos/igniterealtime/Openfire/contributors","subscribers_url":"https://api.github.com/repos/igniterealtime/Openfire/subscribers","subscription_url":"https://api.github.com/repos/igniterealtime/Openfire/subscription","commits_url":"https://api.github.com/repos/igniterealtime/Openfire/commits{/sha}","git_commits_url":"https://api.github.com/repos/igniterealtime/Openfire/git/commits{/sha}","comments_url":"https://api.github.com/repos/igniterealtime/Openfire/comments{/number}","issue_comment_url":"https://api.github.com/repos/igniterealtime/Openfire/issues/comments{/number}","contents_url":"https://api.github.com/repos/igniterealtime/Openfire/contents/{+path}","compare_url":"https://api.github.com/repos/igniterealtime/Openfire/compare/{base}...{head}","merges_url":"https://api.github.com/repos/igniterealtime/Openfire/merges","archive_url":"https://api.github.com/repos/igniterealtime/Openfire/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/igniterealtime/Openfire/downloads","issues_url":"https://api.github.com/repos/igniterealtime/Openfire/issues{/number}","pulls_url":"https://api.github.com/repos/igniterealtime/Openfire/pulls{/number}","milestones_url":"https://api.github.com/repos/igniterealtime/Openfire/milestones{/number}","notifications_url":"https://api.github.com/repos/igniterealtime/Openfire/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/igniterealtime/Openfire/labels{/name}","releases_url":"https://api.github.com/repos/igniterealtime/Openfire/releases{/id}","deployments_url":"https://api.github.com/repos/igniterealtime/Openfire/deployments","created_at":"2014-04-07T09:12:33Z","updated_at":"2021-07-21T01:32:28Z","pushed_at":"2021-07-17T15:38:52Z","git_url":"git://github.com/igniterealtime/Openfire.git","ssh_url":"git@github.com:igniterealtime/Openfire.git","clone_url":"https://github.com/igniterealtime/Openfire.git","svn_url":"https://github.com/igniterealtime/Openfire","homepage":"https://igniterealtime.org/projects/openfire/","size":862051,"stargazers_count":2356,"watchers_count":2356,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1249,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":28,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":1249,"open_issues":28,"watchers":2356,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/igniterealtime/Openfire/pulls/1864"},"html":{"href":"https://github.com/igniterealtime/Openfire/pull/1864"},"issue":{"href":"https://api.github.com/repos/igniterealtime/Openfire/issues/1864"},"comments":{"href":"https://api.github.com/repos/igniterealtime/Openfire/issues/1864/comments"},"review_comments":{"href":"https://api.github.com/repos/igniterealtime/Openfire/pulls/1864/comments"},"review_comment":{"href":"https://api.github.com/repos/igniterealtime/Openfire/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/igniterealtime/Openfire/pulls/1864/commits"},"statuses":{"href":"https://api.github.com/repos/igniterealtime/Openfire/statuses/ed7b8cbe51fc30bff8c3e7c41749934d093bb699"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":true,"commits":1,"additions":2,"deletions":2,"changed_files":1}},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":5991032,"login":"igniterealtime","gravatar_id":"","url":"https://api.github.com/orgs/igniterealtime","avatar_url":"https://avatars.githubusercontent.com/u/5991032?"}} +{"id":"17245515828","type":"PullRequestEvent","actor":{"id":6112685,"login":"sebastiendeleze","display_login":"sebastiendeleze","gravatar_id":"","url":"https://api.github.com/users/sebastiendeleze","avatar_url":"https://avatars.githubusercontent.com/u/6112685?"},"repo":{"id":149142372,"name":"rero/sonar","url":"https://api.github.com/repos/rero/sonar"},"payload":{"action":"closed","number":601,"pull_request":{"url":"https://api.github.com/repos/rero/sonar/pulls/601","id":688525987,"node_id":"MDExOlB1bGxSZXF1ZXN0Njg4NTI1OTg3","html_url":"https://github.com/rero/sonar/pull/601","diff_url":"https://github.com/rero/sonar/pull/601.diff","patch_url":"https://github.com/rero/sonar/pull/601.patch","issue_url":"https://api.github.com/repos/rero/sonar/issues/601","number":601,"state":"closed","locked":false,"title":"documents: add `masked` property","user":{"login":"sebastiendeleze","id":6112685,"node_id":"MDQ6VXNlcjYxMTI2ODU=","avatar_url":"https://avatars.githubusercontent.com/u/6112685?v=4","gravatar_id":"","url":"https://api.github.com/users/sebastiendeleze","html_url":"https://github.com/sebastiendeleze","followers_url":"https://api.github.com/users/sebastiendeleze/followers","following_url":"https://api.github.com/users/sebastiendeleze/following{/other_user}","gists_url":"https://api.github.com/users/sebastiendeleze/gists{/gist_id}","starred_url":"https://api.github.com/users/sebastiendeleze/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sebastiendeleze/subscriptions","organizations_url":"https://api.github.com/users/sebastiendeleze/orgs","repos_url":"https://api.github.com/users/sebastiendeleze/repos","events_url":"https://api.github.com/users/sebastiendeleze/events{/privacy}","received_events_url":"https://api.github.com/users/sebastiendeleze/received_events","type":"User","site_admin":false},"body":"* Adds `marked` property to documents and hides masked documents on the public interface.\r\n* Closes #570.\r\n\r\nCo-Authored-by: Sébastien Délèze ","created_at":"2021-07-13T06:34:46Z","updated_at":"2021-07-21T08:00:03Z","closed_at":"2021-07-21T08:00:03Z","merged_at":"2021-07-21T08:00:03Z","merge_commit_sha":"4fbfcf41c5f203545ce4a8b8c3f7e9ffb7cc8c0b","assignee":null,"assignees":[],"requested_reviewers":[{"login":"jma","id":127249,"node_id":"MDQ6VXNlcjEyNzI0OQ==","avatar_url":"https://avatars.githubusercontent.com/u/127249?v=4","gravatar_id":"","url":"https://api.github.com/users/jma","html_url":"https://github.com/jma","followers_url":"https://api.github.com/users/jma/followers","following_url":"https://api.github.com/users/jma/following{/other_user}","gists_url":"https://api.github.com/users/jma/gists{/gist_id}","starred_url":"https://api.github.com/users/jma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jma/subscriptions","organizations_url":"https://api.github.com/users/jma/orgs","repos_url":"https://api.github.com/users/jma/repos","events_url":"https://api.github.com/users/jma/events{/privacy}","received_events_url":"https://api.github.com/users/jma/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/rero/sonar/pulls/601/commits","review_comments_url":"https://api.github.com/repos/rero/sonar/pulls/601/comments","review_comment_url":"https://api.github.com/repos/rero/sonar/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rero/sonar/issues/601/comments","statuses_url":"https://api.github.com/repos/rero/sonar/statuses/ee3d9739e8ebbc2d2168448b47a3a8694249d73f","head":{"label":"sebastiendeleze:sed-masked-document","ref":"sed-masked-document","sha":"ee3d9739e8ebbc2d2168448b47a3a8694249d73f","user":{"login":"sebastiendeleze","id":6112685,"node_id":"MDQ6VXNlcjYxMTI2ODU=","avatar_url":"https://avatars.githubusercontent.com/u/6112685?v=4","gravatar_id":"","url":"https://api.github.com/users/sebastiendeleze","html_url":"https://github.com/sebastiendeleze","followers_url":"https://api.github.com/users/sebastiendeleze/followers","following_url":"https://api.github.com/users/sebastiendeleze/following{/other_user}","gists_url":"https://api.github.com/users/sebastiendeleze/gists{/gist_id}","starred_url":"https://api.github.com/users/sebastiendeleze/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sebastiendeleze/subscriptions","organizations_url":"https://api.github.com/users/sebastiendeleze/orgs","repos_url":"https://api.github.com/users/sebastiendeleze/repos","events_url":"https://api.github.com/users/sebastiendeleze/events{/privacy}","received_events_url":"https://api.github.com/users/sebastiendeleze/received_events","type":"User","site_admin":false},"repo":{"id":185577052,"node_id":"MDEwOlJlcG9zaXRvcnkxODU1NzcwNTI=","name":"sonar","full_name":"sebastiendeleze/sonar","private":false,"owner":{"login":"sebastiendeleze","id":6112685,"node_id":"MDQ6VXNlcjYxMTI2ODU=","avatar_url":"https://avatars.githubusercontent.com/u/6112685?v=4","gravatar_id":"","url":"https://api.github.com/users/sebastiendeleze","html_url":"https://github.com/sebastiendeleze","followers_url":"https://api.github.com/users/sebastiendeleze/followers","following_url":"https://api.github.com/users/sebastiendeleze/following{/other_user}","gists_url":"https://api.github.com/users/sebastiendeleze/gists{/gist_id}","starred_url":"https://api.github.com/users/sebastiendeleze/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sebastiendeleze/subscriptions","organizations_url":"https://api.github.com/users/sebastiendeleze/orgs","repos_url":"https://api.github.com/users/sebastiendeleze/repos","events_url":"https://api.github.com/users/sebastiendeleze/events{/privacy}","received_events_url":"https://api.github.com/users/sebastiendeleze/received_events","type":"User","site_admin":false},"html_url":"https://github.com/sebastiendeleze/sonar","description":"SONAR - Swiss Open Access Repository","fork":true,"url":"https://api.github.com/repos/sebastiendeleze/sonar","forks_url":"https://api.github.com/repos/sebastiendeleze/sonar/forks","keys_url":"https://api.github.com/repos/sebastiendeleze/sonar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sebastiendeleze/sonar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sebastiendeleze/sonar/teams","hooks_url":"https://api.github.com/repos/sebastiendeleze/sonar/hooks","issue_events_url":"https://api.github.com/repos/sebastiendeleze/sonar/issues/events{/number}","events_url":"https://api.github.com/repos/sebastiendeleze/sonar/events","assignees_url":"https://api.github.com/repos/sebastiendeleze/sonar/assignees{/user}","branches_url":"https://api.github.com/repos/sebastiendeleze/sonar/branches{/branch}","tags_url":"https://api.github.com/repos/sebastiendeleze/sonar/tags","blobs_url":"https://api.github.com/repos/sebastiendeleze/sonar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sebastiendeleze/sonar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sebastiendeleze/sonar/git/refs{/sha}","trees_url":"https://api.github.com/repos/sebastiendeleze/sonar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sebastiendeleze/sonar/statuses/{sha}","languages_url":"https://api.github.com/repos/sebastiendeleze/sonar/languages","stargazers_url":"https://api.github.com/repos/sebastiendeleze/sonar/stargazers","contributors_url":"https://api.github.com/repos/sebastiendeleze/sonar/contributors","subscribers_url":"https://api.github.com/repos/sebastiendeleze/sonar/subscribers","subscription_url":"https://api.github.com/repos/sebastiendeleze/sonar/subscription","commits_url":"https://api.github.com/repos/sebastiendeleze/sonar/commits{/sha}","git_commits_url":"https://api.github.com/repos/sebastiendeleze/sonar/git/commits{/sha}","comments_url":"https://api.github.com/repos/sebastiendeleze/sonar/comments{/number}","issue_comment_url":"https://api.github.com/repos/sebastiendeleze/sonar/issues/comments{/number}","contents_url":"https://api.github.com/repos/sebastiendeleze/sonar/contents/{+path}","compare_url":"https://api.github.com/repos/sebastiendeleze/sonar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sebastiendeleze/sonar/merges","archive_url":"https://api.github.com/repos/sebastiendeleze/sonar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sebastiendeleze/sonar/downloads","issues_url":"https://api.github.com/repos/sebastiendeleze/sonar/issues{/number}","pulls_url":"https://api.github.com/repos/sebastiendeleze/sonar/pulls{/number}","milestones_url":"https://api.github.com/repos/sebastiendeleze/sonar/milestones{/number}","notifications_url":"https://api.github.com/repos/sebastiendeleze/sonar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sebastiendeleze/sonar/labels{/name}","releases_url":"https://api.github.com/repos/sebastiendeleze/sonar/releases{/id}","deployments_url":"https://api.github.com/repos/sebastiendeleze/sonar/deployments","created_at":"2019-05-08T09:41:49Z","updated_at":"2021-07-14T09:18:29Z","pushed_at":"2021-07-21T07:34:27Z","git_url":"git://github.com/sebastiendeleze/sonar.git","ssh_url":"git@github.com:sebastiendeleze/sonar.git","clone_url":"https://github.com/sebastiendeleze/sonar.git","svn_url":"https://github.com/sebastiendeleze/sonar","homepage":null,"size":14195,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"rero:staging","ref":"staging","sha":"41d3d9f7c919eb851cd30f78fbfbcca5e5d7f289","user":{"login":"rero","id":105555,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNTU1NQ==","avatar_url":"https://avatars.githubusercontent.com/u/105555?v=4","gravatar_id":"","url":"https://api.github.com/users/rero","html_url":"https://github.com/rero","followers_url":"https://api.github.com/users/rero/followers","following_url":"https://api.github.com/users/rero/following{/other_user}","gists_url":"https://api.github.com/users/rero/gists{/gist_id}","starred_url":"https://api.github.com/users/rero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rero/subscriptions","organizations_url":"https://api.github.com/users/rero/orgs","repos_url":"https://api.github.com/users/rero/repos","events_url":"https://api.github.com/users/rero/events{/privacy}","received_events_url":"https://api.github.com/users/rero/received_events","type":"Organization","site_admin":false},"repo":{"id":149142372,"node_id":"MDEwOlJlcG9zaXRvcnkxNDkxNDIzNzI=","name":"sonar","full_name":"rero/sonar","private":false,"owner":{"login":"rero","id":105555,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNTU1NQ==","avatar_url":"https://avatars.githubusercontent.com/u/105555?v=4","gravatar_id":"","url":"https://api.github.com/users/rero","html_url":"https://github.com/rero","followers_url":"https://api.github.com/users/rero/followers","following_url":"https://api.github.com/users/rero/following{/other_user}","gists_url":"https://api.github.com/users/rero/gists{/gist_id}","starred_url":"https://api.github.com/users/rero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rero/subscriptions","organizations_url":"https://api.github.com/users/rero/orgs","repos_url":"https://api.github.com/users/rero/repos","events_url":"https://api.github.com/users/rero/events{/privacy}","received_events_url":"https://api.github.com/users/rero/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/rero/sonar","description":"SONAR - Swiss Open Access Repository","fork":false,"url":"https://api.github.com/repos/rero/sonar","forks_url":"https://api.github.com/repos/rero/sonar/forks","keys_url":"https://api.github.com/repos/rero/sonar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rero/sonar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rero/sonar/teams","hooks_url":"https://api.github.com/repos/rero/sonar/hooks","issue_events_url":"https://api.github.com/repos/rero/sonar/issues/events{/number}","events_url":"https://api.github.com/repos/rero/sonar/events","assignees_url":"https://api.github.com/repos/rero/sonar/assignees{/user}","branches_url":"https://api.github.com/repos/rero/sonar/branches{/branch}","tags_url":"https://api.github.com/repos/rero/sonar/tags","blobs_url":"https://api.github.com/repos/rero/sonar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rero/sonar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rero/sonar/git/refs{/sha}","trees_url":"https://api.github.com/repos/rero/sonar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rero/sonar/statuses/{sha}","languages_url":"https://api.github.com/repos/rero/sonar/languages","stargazers_url":"https://api.github.com/repos/rero/sonar/stargazers","contributors_url":"https://api.github.com/repos/rero/sonar/contributors","subscribers_url":"https://api.github.com/repos/rero/sonar/subscribers","subscription_url":"https://api.github.com/repos/rero/sonar/subscription","commits_url":"https://api.github.com/repos/rero/sonar/commits{/sha}","git_commits_url":"https://api.github.com/repos/rero/sonar/git/commits{/sha}","comments_url":"https://api.github.com/repos/rero/sonar/comments{/number}","issue_comment_url":"https://api.github.com/repos/rero/sonar/issues/comments{/number}","contents_url":"https://api.github.com/repos/rero/sonar/contents/{+path}","compare_url":"https://api.github.com/repos/rero/sonar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rero/sonar/merges","archive_url":"https://api.github.com/repos/rero/sonar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rero/sonar/downloads","issues_url":"https://api.github.com/repos/rero/sonar/issues{/number}","pulls_url":"https://api.github.com/repos/rero/sonar/pulls{/number}","milestones_url":"https://api.github.com/repos/rero/sonar/milestones{/number}","notifications_url":"https://api.github.com/repos/rero/sonar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rero/sonar/labels{/name}","releases_url":"https://api.github.com/repos/rero/sonar/releases{/id}","deployments_url":"https://api.github.com/repos/rero/sonar/deployments","created_at":"2018-09-17T14:57:08Z","updated_at":"2021-07-21T07:34:28Z","pushed_at":"2021-07-21T08:00:02Z","git_url":"git://github.com/rero/sonar.git","ssh_url":"git@github.com:rero/sonar.git","clone_url":"https://github.com/rero/sonar.git","svn_url":"https://github.com/rero/sonar","homepage":null,"size":14184,"stargazers_count":9,"watchers_count":9,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":45,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":7,"open_issues":45,"watchers":9,"default_branch":"staging"}},"_links":{"self":{"href":"https://api.github.com/repos/rero/sonar/pulls/601"},"html":{"href":"https://github.com/rero/sonar/pull/601"},"issue":{"href":"https://api.github.com/repos/rero/sonar/issues/601"},"comments":{"href":"https://api.github.com/repos/rero/sonar/issues/601/comments"},"review_comments":{"href":"https://api.github.com/repos/rero/sonar/pulls/601/comments"},"review_comment":{"href":"https://api.github.com/repos/rero/sonar/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rero/sonar/pulls/601/commits"},"statuses":{"href":"https://api.github.com/repos/rero/sonar/statuses/ee3d9739e8ebbc2d2168448b47a3a8694249d73f"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"sebastiendeleze","id":6112685,"node_id":"MDQ6VXNlcjYxMTI2ODU=","avatar_url":"https://avatars.githubusercontent.com/u/6112685?v=4","gravatar_id":"","url":"https://api.github.com/users/sebastiendeleze","html_url":"https://github.com/sebastiendeleze","followers_url":"https://api.github.com/users/sebastiendeleze/followers","following_url":"https://api.github.com/users/sebastiendeleze/following{/other_user}","gists_url":"https://api.github.com/users/sebastiendeleze/gists{/gist_id}","starred_url":"https://api.github.com/users/sebastiendeleze/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sebastiendeleze/subscriptions","organizations_url":"https://api.github.com/users/sebastiendeleze/orgs","repos_url":"https://api.github.com/users/sebastiendeleze/repos","events_url":"https://api.github.com/users/sebastiendeleze/events{/privacy}","received_events_url":"https://api.github.com/users/sebastiendeleze/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":47,"deletions":2,"changed_files":5}},"public":true,"created_at":"2021-07-21T08:00:05Z","org":{"id":105555,"login":"rero","gravatar_id":"","url":"https://api.github.com/orgs/rero","avatar_url":"https://avatars.githubusercontent.com/u/105555?"}} +{"id":"17245515843","type":"PushEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":388040553,"name":"thatjohn01/750801443","url":"https://api.github.com/repos/thatjohn01/750801443"},"payload":{"push_id":7562055600,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"5dec6a986bb7a4b1228ddc134058e8e3be9cb212","before":"91dfe44135c81afb50213f0b9053560745a5f60e","commits":[{"sha":"5dec6a986bb7a4b1228ddc134058e8e3be9cb212","author":{"name":"thatjohn01","email":"72eb81e66410b3da65da4cca287ce0578825ce64@users.noreply.github.com"},"message":"change README.md","distinct":true,"url":"https://api.github.com/repos/thatjohn01/750801443/commits/5dec6a986bb7a4b1228ddc134058e8e3be9cb212"}]},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17245515883","type":"PullRequestEvent","actor":{"id":43779561,"login":"marionnousvalentin","display_login":"marionnousvalentin","gravatar_id":"","url":"https://api.github.com/users/marionnousvalentin","avatar_url":"https://avatars.githubusercontent.com/u/43779561?"},"repo":{"id":299546852,"name":"pass-culture/pass-culture-app-native","url":"https://api.github.com/repos/pass-culture/pass-culture-app-native"},"payload":{"action":"closed","number":1370,"pull_request":{"url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/1370","id":693630697,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNjMwNjk3","html_url":"https://github.com/pass-culture/pass-culture-app-native/pull/1370","diff_url":"https://github.com/pass-culture/pass-culture-app-native/pull/1370.diff","patch_url":"https://github.com/pass-culture/pass-culture-app-native/pull/1370.patch","issue_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/1370","number":1370,"state":"closed","locked":false,"title":"[PC-10051] fix pressable areas","user":{"login":"marionnousvalentin","id":43779561,"node_id":"MDQ6VXNlcjQzNzc5NTYx","avatar_url":"https://avatars.githubusercontent.com/u/43779561?v=4","gravatar_id":"","url":"https://api.github.com/users/marionnousvalentin","html_url":"https://github.com/marionnousvalentin","followers_url":"https://api.github.com/users/marionnousvalentin/followers","following_url":"https://api.github.com/users/marionnousvalentin/following{/other_user}","gists_url":"https://api.github.com/users/marionnousvalentin/gists{/gist_id}","starred_url":"https://api.github.com/users/marionnousvalentin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marionnousvalentin/subscriptions","organizations_url":"https://api.github.com/users/marionnousvalentin/orgs","repos_url":"https://api.github.com/users/marionnousvalentin/repos","events_url":"https://api.github.com/users/marionnousvalentin/events{/privacy}","received_events_url":"https://api.github.com/users/marionnousvalentin/received_events","type":"User","site_admin":false},"body":"Link to JIRA ticket: https://passculture.atlassian.net/browse/PC-10051\r\n\r\n## Checklist\r\n\r\nI have:\r\n\r\n- [ ] Made sure the title of my PR follows the convention `[PC-XXX] `.\r\n- [ ] Made sure my feature is working on the relevant real / virtual devices.\r\n- [ ] Written **unit tests** for my feature.\r\n- [ ] Written **documentation on Notion** for my feature.\r\n- [ ] Added new reusable components to the AppComponents page and communicate to the team on slack\r\n- [ ] Added a **screenshot** for UI tickets.\r\n- [ ] Attached a **ticket number** for any added TODO/FIXME \\\r\n (for tech tasks, give the best context about the TODO resolution: what/who/when).\r\n\r\n## Screenshots\r\n\r\navant : zone bleue cliquable (qui fermait la modale)\r\naprès : zone bleue non cliquable\r\n| \\*iOS - [Phone name] | \\*Android - [Phone name] | Tablet - [Phone name] | Little - [Phone name] |\r\n| -------------------- | :----------------------: | --------------------: | --------------------: |\r\n| | | \r\n![image](https://user-images.githubusercontent.com/43779561/126448133-c44425d1-9e12-4efa-bbc5-c233fd7ac92d.png)\r\n \r\n![image](https://user-images.githubusercontent.com/43779561/126449944-db72af33-976d-4551-aaa6-486dd9cd99aa.png)\r\n | |\r\n\r\n## Deploy hard\r\n\r\nIf native code (ios/android) was modified, **after** the PR is merged, on the master branch, upgrade the **app version** (+1 patch):\r\n\r\n- if you want an hard deployment of the testing environment, use `yarn trigger:testing:deploy`\r\n","created_at":"2021-07-20T16:46:02Z","updated_at":"2021-07-21T08:00:05Z","closed_at":"2021-07-21T08:00:05Z","merged_at":"2021-07-21T08:00:05Z","merge_commit_sha":"a56c3a33603cccd4dd61bb8f6aad9946779fd1d5","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/1370/commits","review_comments_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/1370/comments","review_comment_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/comments{/number}","comments_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/1370/comments","statuses_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/statuses/7cf719b69fdfbe0debe65f1686bcc8fc59981964","head":{"label":"pass-culture:pc-10051-fix-cliclable-areas","ref":"pc-10051-fix-cliclable-areas","sha":"7cf719b69fdfbe0debe65f1686bcc8fc59981964","user":{"login":"pass-culture","id":52448870,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyNDQ4ODcw","avatar_url":"https://avatars.githubusercontent.com/u/52448870?v=4","gravatar_id":"","url":"https://api.github.com/users/pass-culture","html_url":"https://github.com/pass-culture","followers_url":"https://api.github.com/users/pass-culture/followers","following_url":"https://api.github.com/users/pass-culture/following{/other_user}","gists_url":"https://api.github.com/users/pass-culture/gists{/gist_id}","starred_url":"https://api.github.com/users/pass-culture/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pass-culture/subscriptions","organizations_url":"https://api.github.com/users/pass-culture/orgs","repos_url":"https://api.github.com/users/pass-culture/repos","events_url":"https://api.github.com/users/pass-culture/events{/privacy}","received_events_url":"https://api.github.com/users/pass-culture/received_events","type":"Organization","site_admin":false},"repo":{"id":299546852,"node_id":"MDEwOlJlcG9zaXRvcnkyOTk1NDY4NTI=","name":"pass-culture-app-native","full_name":"pass-culture/pass-culture-app-native","private":false,"owner":{"login":"pass-culture","id":52448870,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyNDQ4ODcw","avatar_url":"https://avatars.githubusercontent.com/u/52448870?v=4","gravatar_id":"","url":"https://api.github.com/users/pass-culture","html_url":"https://github.com/pass-culture","followers_url":"https://api.github.com/users/pass-culture/followers","following_url":"https://api.github.com/users/pass-culture/following{/other_user}","gists_url":"https://api.github.com/users/pass-culture/gists{/gist_id}","starred_url":"https://api.github.com/users/pass-culture/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pass-culture/subscriptions","organizations_url":"https://api.github.com/users/pass-culture/orgs","repos_url":"https://api.github.com/users/pass-culture/repos","events_url":"https://api.github.com/users/pass-culture/events{/privacy}","received_events_url":"https://api.github.com/users/pass-culture/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/pass-culture/pass-culture-app-native","description":null,"fork":false,"url":"https://api.github.com/repos/pass-culture/pass-culture-app-native","forks_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/forks","keys_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/teams","hooks_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/hooks","issue_events_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/events{/number}","events_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/events","assignees_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/assignees{/user}","branches_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/branches{/branch}","tags_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/tags","blobs_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/refs{/sha}","trees_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/statuses/{sha}","languages_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/languages","stargazers_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/stargazers","contributors_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/contributors","subscribers_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/subscribers","subscription_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/subscription","commits_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/commits{/sha}","git_commits_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/commits{/sha}","comments_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/comments{/number}","issue_comment_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/comments{/number}","contents_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/contents/{+path}","compare_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/merges","archive_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/downloads","issues_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues{/number}","pulls_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls{/number}","milestones_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/milestones{/number}","notifications_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/labels{/name}","releases_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/releases{/id}","deployments_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/deployments","created_at":"2020-09-29T08:03:18Z","updated_at":"2021-07-20T18:54:49Z","pushed_at":"2021-07-21T08:00:05Z","git_url":"git://github.com/pass-culture/pass-culture-app-native.git","ssh_url":"git@github.com:pass-culture/pass-culture-app-native.git","clone_url":"https://github.com/pass-culture/pass-culture-app-native.git","svn_url":"https://github.com/pass-culture/pass-culture-app-native","homepage":null,"size":83028,"stargazers_count":5,"watchers_count":5,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":null,"forks":2,"open_issues":2,"watchers":5,"default_branch":"master"}},"base":{"label":"pass-culture:master","ref":"master","sha":"0ea462362beb43548655992ae20847858ca73739","user":{"login":"pass-culture","id":52448870,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyNDQ4ODcw","avatar_url":"https://avatars.githubusercontent.com/u/52448870?v=4","gravatar_id":"","url":"https://api.github.com/users/pass-culture","html_url":"https://github.com/pass-culture","followers_url":"https://api.github.com/users/pass-culture/followers","following_url":"https://api.github.com/users/pass-culture/following{/other_user}","gists_url":"https://api.github.com/users/pass-culture/gists{/gist_id}","starred_url":"https://api.github.com/users/pass-culture/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pass-culture/subscriptions","organizations_url":"https://api.github.com/users/pass-culture/orgs","repos_url":"https://api.github.com/users/pass-culture/repos","events_url":"https://api.github.com/users/pass-culture/events{/privacy}","received_events_url":"https://api.github.com/users/pass-culture/received_events","type":"Organization","site_admin":false},"repo":{"id":299546852,"node_id":"MDEwOlJlcG9zaXRvcnkyOTk1NDY4NTI=","name":"pass-culture-app-native","full_name":"pass-culture/pass-culture-app-native","private":false,"owner":{"login":"pass-culture","id":52448870,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyNDQ4ODcw","avatar_url":"https://avatars.githubusercontent.com/u/52448870?v=4","gravatar_id":"","url":"https://api.github.com/users/pass-culture","html_url":"https://github.com/pass-culture","followers_url":"https://api.github.com/users/pass-culture/followers","following_url":"https://api.github.com/users/pass-culture/following{/other_user}","gists_url":"https://api.github.com/users/pass-culture/gists{/gist_id}","starred_url":"https://api.github.com/users/pass-culture/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pass-culture/subscriptions","organizations_url":"https://api.github.com/users/pass-culture/orgs","repos_url":"https://api.github.com/users/pass-culture/repos","events_url":"https://api.github.com/users/pass-culture/events{/privacy}","received_events_url":"https://api.github.com/users/pass-culture/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/pass-culture/pass-culture-app-native","description":null,"fork":false,"url":"https://api.github.com/repos/pass-culture/pass-culture-app-native","forks_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/forks","keys_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/teams","hooks_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/hooks","issue_events_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/events{/number}","events_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/events","assignees_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/assignees{/user}","branches_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/branches{/branch}","tags_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/tags","blobs_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/refs{/sha}","trees_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/statuses/{sha}","languages_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/languages","stargazers_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/stargazers","contributors_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/contributors","subscribers_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/subscribers","subscription_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/subscription","commits_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/commits{/sha}","git_commits_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/git/commits{/sha}","comments_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/comments{/number}","issue_comment_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/comments{/number}","contents_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/contents/{+path}","compare_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/merges","archive_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/downloads","issues_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues{/number}","pulls_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls{/number}","milestones_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/milestones{/number}","notifications_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/labels{/name}","releases_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/releases{/id}","deployments_url":"https://api.github.com/repos/pass-culture/pass-culture-app-native/deployments","created_at":"2020-09-29T08:03:18Z","updated_at":"2021-07-20T18:54:49Z","pushed_at":"2021-07-21T08:00:05Z","git_url":"git://github.com/pass-culture/pass-culture-app-native.git","ssh_url":"git@github.com:pass-culture/pass-culture-app-native.git","clone_url":"https://github.com/pass-culture/pass-culture-app-native.git","svn_url":"https://github.com/pass-culture/pass-culture-app-native","homepage":null,"size":83028,"stargazers_count":5,"watchers_count":5,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":null,"forks":2,"open_issues":2,"watchers":5,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/1370"},"html":{"href":"https://github.com/pass-culture/pass-culture-app-native/pull/1370"},"issue":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/1370"},"comments":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/issues/1370/comments"},"review_comments":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/1370/comments"},"review_comment":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/pulls/1370/commits"},"statuses":{"href":"https://api.github.com/repos/pass-culture/pass-culture-app-native/statuses/7cf719b69fdfbe0debe65f1686bcc8fc59981964"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"marionnousvalentin","id":43779561,"node_id":"MDQ6VXNlcjQzNzc5NTYx","avatar_url":"https://avatars.githubusercontent.com/u/43779561?v=4","gravatar_id":"","url":"https://api.github.com/users/marionnousvalentin","html_url":"https://github.com/marionnousvalentin","followers_url":"https://api.github.com/users/marionnousvalentin/followers","following_url":"https://api.github.com/users/marionnousvalentin/following{/other_user}","gists_url":"https://api.github.com/users/marionnousvalentin/gists{/gist_id}","starred_url":"https://api.github.com/users/marionnousvalentin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marionnousvalentin/subscriptions","organizations_url":"https://api.github.com/users/marionnousvalentin/orgs","repos_url":"https://api.github.com/users/marionnousvalentin/repos","events_url":"https://api.github.com/users/marionnousvalentin/events{/privacy}","received_events_url":"https://api.github.com/users/marionnousvalentin/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":69,"deletions":198,"changed_files":7}},"public":true,"created_at":"2021-07-21T08:00:06Z","org":{"id":52448870,"login":"pass-culture","gravatar_id":"","url":"https://api.github.com/orgs/pass-culture","avatar_url":"https://avatars.githubusercontent.com/u/52448870?"}} +{"id":"17245515888","type":"PushEvent","actor":{"id":526307,"login":"endorama","display_login":"endorama","gravatar_id":"","url":"https://api.github.com/users/endorama","avatar_url":"https://avatars.githubusercontent.com/u/526307?"},"repo":{"id":16554739,"name":"elastic/beats","url":"https://api.github.com/repos/elastic/beats"},"payload":{"push_id":7562055563,"size":1,"distinct_size":1,"ref":"refs/heads/7.x","head":"9f0cc3a673a75248ad7d7e0005c0c5303fbae609","before":"3ec07ce1e40d808e2121809294b56118630a2afb","commits":[{"sha":"9f0cc3a673a75248ad7d7e0005c0c5303fbae609","author":{"name":"mergify[bot]","email":"b09a6ee808b67e98a221404e7aaa52e0398a4954@users.noreply.github.com"},"message":"[7.x](backport #26870) [gcp/billing] always quote table name identifier (#26932)\n\n(cherry picked from commit f731832277488ad5d3f6153adc19b8de679bbb01)\r\n\r\nCo-authored-by: endorama <526307+endorama@users.noreply.github.com>","distinct":true,"url":"https://api.github.com/repos/elastic/beats/commits/9f0cc3a673a75248ad7d7e0005c0c5303fbae609"}]},"public":true,"created_at":"2021-07-21T08:00:06Z","org":{"id":6764390,"login":"elastic","gravatar_id":"","url":"https://api.github.com/orgs/elastic","avatar_url":"https://avatars.githubusercontent.com/u/6764390?"}} +{"id":"17245515908","type":"WatchEvent","actor":{"id":544296,"login":"nolar","display_login":"nolar","gravatar_id":"","url":"https://api.github.com/users/nolar","avatar_url":"https://avatars.githubusercontent.com/u/544296?"},"repo":{"id":197192691,"name":"thilp/aiopykube","url":"https://api.github.com/repos/thilp/aiopykube"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17245515916","type":"PushEvent","actor":{"id":1739,"login":"brentp","display_login":"brentp","gravatar_id":"","url":"https://api.github.com/users/brentp","avatar_url":"https://avatars.githubusercontent.com/u/1739?"},"repo":{"id":34803627,"name":"brentp/vcfanno","url":"https://api.github.com/repos/brentp/vcfanno"},"payload":{"push_id":7562055607,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"49ef5005c2c76073d7a15a5a030eba0ee5f46b3b","before":"6e5a12ab6b104fd00f627bc41d1a967d0929b5b9","commits":[{"sha":"49ef5005c2c76073d7a15a5a030eba0ee5f46b3b","author":{"name":"Brent Pedersen","email":"dbfb915fc4c51a9c36a01385c4753fb269356914@gmail.com"},"message":"non-zero exit when no args are given\n\nsee #143\n\nalso add go.mod","distinct":true,"url":"https://api.github.com/repos/brentp/vcfanno/commits/49ef5005c2c76073d7a15a5a030eba0ee5f46b3b"}]},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17245515928","type":"CreateEvent","actor":{"id":87752629,"login":"lucasgs55","display_login":"lucasgs55","gravatar_id":"","url":"https://api.github.com/users/lucasgs55","avatar_url":"https://avatars.githubusercontent.com/u/87752629?"},"repo":{"id":388040583,"name":"lucasgs55/lucastest","url":"https://api.github.com/repos/lucasgs55/lucastest"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":"Test","pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17245515932","type":"ForkEvent","actor":{"id":35286162,"login":"huiyueun","display_login":"huiyueun","gravatar_id":"","url":"https://api.github.com/users/huiyueun","avatar_url":"https://avatars.githubusercontent.com/u/35286162?"},"repo":{"id":384031496,"name":"nui-dali/NUITizenGallery","url":"https://api.github.com/repos/nui-dali/NUITizenGallery"},"payload":{"forkee":{"id":388040585,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNDA1ODU=","name":"NUITizenGallery","full_name":"huiyueun/NUITizenGallery","private":false,"owner":{"login":"huiyueun","id":35286162,"node_id":"MDQ6VXNlcjM1Mjg2MTYy","avatar_url":"https://avatars.githubusercontent.com/u/35286162?v=4","gravatar_id":"","url":"https://api.github.com/users/huiyueun","html_url":"https://github.com/huiyueun","followers_url":"https://api.github.com/users/huiyueun/followers","following_url":"https://api.github.com/users/huiyueun/following{/other_user}","gists_url":"https://api.github.com/users/huiyueun/gists{/gist_id}","starred_url":"https://api.github.com/users/huiyueun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/huiyueun/subscriptions","organizations_url":"https://api.github.com/users/huiyueun/orgs","repos_url":"https://api.github.com/users/huiyueun/repos","events_url":"https://api.github.com/users/huiyueun/events{/privacy}","received_events_url":"https://api.github.com/users/huiyueun/received_events","type":"User","site_admin":false},"html_url":"https://github.com/huiyueun/NUITizenGallery","description":null,"fork":true,"url":"https://api.github.com/repos/huiyueun/NUITizenGallery","forks_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/forks","keys_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/keys{/key_id}","collaborators_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/teams","hooks_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/hooks","issue_events_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/issues/events{/number}","events_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/events","assignees_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/assignees{/user}","branches_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/branches{/branch}","tags_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/tags","blobs_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/git/refs{/sha}","trees_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/git/trees{/sha}","statuses_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/statuses/{sha}","languages_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/languages","stargazers_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/stargazers","contributors_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/contributors","subscribers_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/subscribers","subscription_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/subscription","commits_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/commits{/sha}","git_commits_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/git/commits{/sha}","comments_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/comments{/number}","issue_comment_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/issues/comments{/number}","contents_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/contents/{+path}","compare_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/compare/{base}...{head}","merges_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/merges","archive_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/downloads","issues_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/issues{/number}","pulls_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/pulls{/number}","milestones_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/milestones{/number}","notifications_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/labels{/name}","releases_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/releases{/id}","deployments_url":"https://api.github.com/repos/huiyueun/NUITizenGallery/deployments","created_at":"2021-07-21T08:00:05Z","updated_at":"2021-07-21T07:27:47Z","pushed_at":"2021-07-21T07:27:44Z","git_url":"git://github.com/huiyueun/NUITizenGallery.git","ssh_url":"git@github.com:huiyueun/NUITizenGallery.git","clone_url":"https://github.com/huiyueun/NUITizenGallery.git","svn_url":"https://github.com/huiyueun/NUITizenGallery","homepage":null,"size":4679,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T08:00:06Z","org":{"id":50942044,"login":"nui-dali","gravatar_id":"","url":"https://api.github.com/orgs/nui-dali","avatar_url":"https://avatars.githubusercontent.com/u/50942044?"}} +{"id":"17245515937","type":"IssueCommentEvent","actor":{"id":27856297,"login":"dependabot-preview[bot]","display_login":"dependabot-preview","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview[bot]","avatar_url":"https://avatars.githubusercontent.com/u/27856297?"},"repo":{"id":337718447,"name":"Alberto-S-P/Boilerplate_Nextjs","url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/85","repository_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs","labels_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/85/labels{/name}","comments_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/85/comments","events_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/85/events","html_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/85","id":947390757,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkyMzc4NzY1","number":85,"title":"Bump @types/node from 14.14.25 to 16.3.3","user":{"login":"dependabot-preview[bot]","id":27856297,"node_id":"MDM6Qm90Mjc4NTYyOTc=","avatar_url":"https://avatars.githubusercontent.com/in/2141?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview%5Bbot%5D","html_url":"https://github.com/apps/dependabot-preview","followers_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/received_events","type":"Bot","site_admin":false},"labels":[{"id":2806840825,"node_id":"MDU6TGFiZWwyODA2ODQwODI1","url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/labels/dependencies","name":"dependencies","color":"0366d6","default":false,"description":"Pull requests that update a dependency file"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-07-19T08:12:28Z","updated_at":"2021-07-21T08:00:06Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/pulls/85","html_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/85","diff_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/85.diff","patch_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/85.patch"},"body":"Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.25 to 16.3.3.\n
    \nCommits\n\n
    \n
    \n\n\n[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=14.14.25&new-version=16.3.3)](https://dependabot.com/compatibility-score/?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=14.14.25&new-version=16.3.3)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language\n- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language\n- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language\n- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language\n- `@dependabot badge me` will comment on this PR with code to add a \"Dependabot enabled\" badge to your readme\n\nAdditionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):\n- Update frequency (including time of day and day of week)\n- Pull request limits (per update run and/or open at any time)\n- Out-of-range updates (receive only lockfile updates, if desired)\n- Security updates (receive only security updates, if desired)\n\n\n\n
    ","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/comments/883977156","html_url":"https://github.com/Alberto-S-P/Boilerplate_Nextjs/pull/85#issuecomment-883977156","issue_url":"https://api.github.com/repos/Alberto-S-P/Boilerplate_Nextjs/issues/85","id":883977156,"node_id":"IC_kwDOFCEsr840sGvE","user":{"login":"dependabot-preview[bot]","id":27856297,"node_id":"MDM6Qm90Mjc4NTYyOTc=","avatar_url":"https://avatars.githubusercontent.com/in/2141?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot-preview%5Bbot%5D","html_url":"https://github.com/apps/dependabot-preview","followers_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot-preview%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2021-07-21T08:00:05Z","updated_at":"2021-07-21T08:00:05Z","author_association":"CONTRIBUTOR","body":"Superseded by #86.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17245515940","type":"CreateEvent","actor":{"id":44154733,"login":"sebastian-zieba","display_login":"sebastian-zieba","gravatar_id":"","url":"https://api.github.com/users/sebastian-zieba","avatar_url":"https://avatars.githubusercontent.com/u/44154733?"},"repo":{"id":388040577,"name":"sebastian-zieba/betaPic_timedelay","url":"https://api.github.com/repos/sebastian-zieba/betaPic_timedelay"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17245515942","type":"PushEvent","actor":{"id":85473844,"login":"carryimg1","display_login":"carryimg1","gravatar_id":"","url":"https://api.github.com/users/carryimg1","avatar_url":"https://avatars.githubusercontent.com/u/85473844?"},"repo":{"id":375075446,"name":"carryimg1/wanmei","url":"https://api.github.com/repos/carryimg1/wanmei"},"payload":{"push_id":7562055620,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f240a70c93b961994dbdb10ca928da60a1c57908","before":"70c10339e9b7dc9d14e785007a3a7e42d118d9f9","commits":[{"sha":"f240a70c93b961994dbdb10ca928da60a1c57908","author":{"name":"carryimg1","email":"5d7993012f205b5caf4c16cfd1216a723b81a33a@users.noreply.github.com"},"message":"2021-07-21 16:00:05 上传","distinct":true,"url":"https://api.github.com/repos/carryimg1/wanmei/commits/f240a70c93b961994dbdb10ca928da60a1c57908"}]},"public":true,"created_at":"2021-07-21T08:00:06Z"} +{"id":"17246339245","type":"PushEvent","actor":{"id":58833107,"login":"hrhwve3553","display_login":"hrhwve3553","gravatar_id":"","url":"https://api.github.com/users/hrhwve3553","avatar_url":"https://avatars.githubusercontent.com/u/58833107?"},"repo":{"id":227725053,"name":"hrhwve3553/ntdtv","url":"https://api.github.com/repos/hrhwve3553/ntdtv"},"payload":{"push_id":7562450993,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"2eb4f5b9b48c2a1db54cad856021f0dad94bf211","before":"bee0038eedffb985a0b7f4cde5b6d8e430cf1bdc","commits":[{"sha":"2eb4f5b9b48c2a1db54cad856021f0dad94bf211","author":{"name":"hrhwve3553","email":"10135ac1af9bed4c0bec7c29db44050e24a097b2@users.noreply.github.com"},"message":"Update 442749_2.md","distinct":true,"url":"https://api.github.com/repos/hrhwve3553/ntdtv/commits/2eb4f5b9b48c2a1db54cad856021f0dad94bf211"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339251","type":"CreateEvent","actor":{"id":73915791,"login":"maria-canals","display_login":"maria-canals","gravatar_id":"","url":"https://api.github.com/users/maria-canals","avatar_url":"https://avatars.githubusercontent.com/u/73915791?"},"repo":{"id":388056469,"name":"maria-canals/React-from-0-to-expert","url":"https://api.github.com/repos/maria-canals/React-from-0-to-expert"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339255","type":"PullRequestReviewEvent","actor":{"id":35560568,"login":"robinmetral","display_login":"robinmetral","gravatar_id":"","url":"https://api.github.com/users/robinmetral","avatar_url":"https://avatars.githubusercontent.com/u/35560568?"},"repo":{"id":95433249,"name":"sumup-oss/circuit-ui","url":"https://api.github.com/repos/sumup-oss/circuit-ui"},"payload":{"action":"created","review":{"id":711326737,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzI2NzM3","user":{"login":"robinmetral","id":35560568,"node_id":"MDQ6VXNlcjM1NTYwNTY4","avatar_url":"https://avatars.githubusercontent.com/u/35560568?v=4","gravatar_id":"","url":"https://api.github.com/users/robinmetral","html_url":"https://github.com/robinmetral","followers_url":"https://api.github.com/users/robinmetral/followers","following_url":"https://api.github.com/users/robinmetral/following{/other_user}","gists_url":"https://api.github.com/users/robinmetral/gists{/gist_id}","starred_url":"https://api.github.com/users/robinmetral/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robinmetral/subscriptions","organizations_url":"https://api.github.com/users/robinmetral/orgs","repos_url":"https://api.github.com/users/robinmetral/repos","events_url":"https://api.github.com/users/robinmetral/events{/privacy}","received_events_url":"https://api.github.com/users/robinmetral/received_events","type":"User","site_admin":false},"body":"","commit_id":"cf36d9948e816632153fb51523a5890496f21e95","submitted_at":"2021-07-21T08:59:59Z","state":"commented","html_url":"https://github.com/sumup-oss/circuit-ui/pull/1047#pullrequestreview-711326737","pull_request_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/sumup-oss/circuit-ui/pull/1047#pullrequestreview-711326737"},"pull_request":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047"}}},"pull_request":{"url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047","id":693321734,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzMzIxNzM0","html_url":"https://github.com/sumup-oss/circuit-ui/pull/1047","diff_url":"https://github.com/sumup-oss/circuit-ui/pull/1047.diff","patch_url":"https://github.com/sumup-oss/circuit-ui/pull/1047.patch","issue_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047","number":1047,"state":"open","locked":false,"title":"Improve the ImageInput for custom components","user":{"login":"connor-baer","id":11017722,"node_id":"MDQ6VXNlcjExMDE3NzIy","avatar_url":"https://avatars.githubusercontent.com/u/11017722?v=4","gravatar_id":"","url":"https://api.github.com/users/connor-baer","html_url":"https://github.com/connor-baer","followers_url":"https://api.github.com/users/connor-baer/followers","following_url":"https://api.github.com/users/connor-baer/following{/other_user}","gists_url":"https://api.github.com/users/connor-baer/gists{/gist_id}","starred_url":"https://api.github.com/users/connor-baer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/connor-baer/subscriptions","organizations_url":"https://api.github.com/users/connor-baer/orgs","repos_url":"https://api.github.com/users/connor-baer/repos","events_url":"https://api.github.com/users/connor-baer/events{/privacy}","received_events_url":"https://api.github.com/users/connor-baer/received_events","type":"User","site_admin":false},"body":"## Purpose\r\n\r\n@justman00 and I are trying to use the ImageInput inside the Popover component. When clicking the button to clear the image, the popover is closed. We confirmed that the popover is closed by the `useOutsideClick` hook. We suspect this happens because the clear button is removed from the DOM before the outside click handler is executed, so it doesn't know that the clear button was _inside_ the popover. In order to prevent this, we plan to use `event.stopPropagation()` in the `onClear` callback.\r\n\r\nThe second challenge we faced was using the ImageInput with the `identity` variant of the Avatar component which has a circular border radius. \r\n\r\n## Approach and changes\r\n\r\n- Pass the click event to the `onClear` prop of the ImageInput\r\n- Make the ImageInput's border-radius configurable through the addition of a `borderRadius` prop (I’m not happy about exposing such a verbose styling prop, but I don’t see a better way 😕)\r\n\r\n## Definition of done\r\n\r\n* [x] Development completed\r\n* [x] Reviewers assigned\r\n* [x] Unit and integration tests\r\n* [x] Meets minimum browser support\r\n* [x] Meets accessibility requirements\r\n","created_at":"2021-07-20T09:46:56Z","updated_at":"2021-07-21T08:59:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c9981e00b8873ffec048751cff67c1124ee77975","assignee":null,"assignees":[],"requested_reviewers":[{"login":"justman00","id":36477870,"node_id":"MDQ6VXNlcjM2NDc3ODcw","avatar_url":"https://avatars.githubusercontent.com/u/36477870?v=4","gravatar_id":"","url":"https://api.github.com/users/justman00","html_url":"https://github.com/justman00","followers_url":"https://api.github.com/users/justman00/followers","following_url":"https://api.github.com/users/justman00/following{/other_user}","gists_url":"https://api.github.com/users/justman00/gists{/gist_id}","starred_url":"https://api.github.com/users/justman00/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/justman00/subscriptions","organizations_url":"https://api.github.com/users/justman00/orgs","repos_url":"https://api.github.com/users/justman00/repos","events_url":"https://api.github.com/users/justman00/events{/privacy}","received_events_url":"https://api.github.com/users/justman00/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":634343322,"node_id":"MDU6TGFiZWw2MzQzNDMzMjI=","url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels/%F0%9F%90%9E%20bug","name":"🐞 bug","color":"E99695","default":false,"description":"Something isn't working as it should"},{"id":2913852247,"node_id":"MDU6TGFiZWwyOTEzODUyMjQ3","url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels/%F0%9F%97%82%20circuit-ui","name":"🗂 circuit-ui","color":"ededed","default":false,"description":null}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/commits","review_comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/comments","review_comment_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047/comments","statuses_url":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/cf36d9948e816632153fb51523a5890496f21e95","head":{"label":"sumup-oss:bugfix/imageinput-clear-event","ref":"bugfix/imageinput-clear-event","sha":"cf36d9948e816632153fb51523a5890496f21e95","user":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"repo":{"id":95433249,"node_id":"MDEwOlJlcG9zaXRvcnk5NTQzMzI0OQ==","name":"circuit-ui","full_name":"sumup-oss/circuit-ui","private":false,"owner":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sumup-oss/circuit-ui","description":"SumUp's component library for the web","fork":false,"url":"https://api.github.com/repos/sumup-oss/circuit-ui","forks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/forks","keys_url":"https://api.github.com/repos/sumup-oss/circuit-ui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sumup-oss/circuit-ui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sumup-oss/circuit-ui/teams","hooks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/hooks","issue_events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/events{/number}","events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/events","assignees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/assignees{/user}","branches_url":"https://api.github.com/repos/sumup-oss/circuit-ui/branches{/branch}","tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/tags","blobs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/refs{/sha}","trees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/{sha}","languages_url":"https://api.github.com/repos/sumup-oss/circuit-ui/languages","stargazers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/stargazers","contributors_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contributors","subscribers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscribers","subscription_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscription","commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/commits{/sha}","git_commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/commits{/sha}","comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/comments{/number}","issue_comment_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/comments{/number}","contents_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contents/{+path}","compare_url":"https://api.github.com/repos/sumup-oss/circuit-ui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sumup-oss/circuit-ui/merges","archive_url":"https://api.github.com/repos/sumup-oss/circuit-ui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sumup-oss/circuit-ui/downloads","issues_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues{/number}","pulls_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls{/number}","milestones_url":"https://api.github.com/repos/sumup-oss/circuit-ui/milestones{/number}","notifications_url":"https://api.github.com/repos/sumup-oss/circuit-ui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels{/name}","releases_url":"https://api.github.com/repos/sumup-oss/circuit-ui/releases{/id}","deployments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/deployments","created_at":"2017-06-26T09:57:50Z","updated_at":"2021-07-21T08:01:20Z","pushed_at":"2021-07-20T15:49:41Z","git_url":"git://github.com/sumup-oss/circuit-ui.git","ssh_url":"git@github.com:sumup-oss/circuit-ui.git","clone_url":"https://github.com/sumup-oss/circuit-ui.git","svn_url":"https://github.com/sumup-oss/circuit-ui","homepage":"https://circuit.sumup.com","size":19657,"stargazers_count":664,"watchers_count":664,"language":"TypeScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":114,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":31,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":114,"open_issues":31,"watchers":664,"default_branch":"main"}},"base":{"label":"sumup-oss:main","ref":"main","sha":"96e03ac4055310a4c7afc72344e3331fa778cacf","user":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"repo":{"id":95433249,"node_id":"MDEwOlJlcG9zaXRvcnk5NTQzMzI0OQ==","name":"circuit-ui","full_name":"sumup-oss/circuit-ui","private":false,"owner":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sumup-oss/circuit-ui","description":"SumUp's component library for the web","fork":false,"url":"https://api.github.com/repos/sumup-oss/circuit-ui","forks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/forks","keys_url":"https://api.github.com/repos/sumup-oss/circuit-ui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sumup-oss/circuit-ui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sumup-oss/circuit-ui/teams","hooks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/hooks","issue_events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/events{/number}","events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/events","assignees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/assignees{/user}","branches_url":"https://api.github.com/repos/sumup-oss/circuit-ui/branches{/branch}","tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/tags","blobs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/refs{/sha}","trees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/{sha}","languages_url":"https://api.github.com/repos/sumup-oss/circuit-ui/languages","stargazers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/stargazers","contributors_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contributors","subscribers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscribers","subscription_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscription","commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/commits{/sha}","git_commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/commits{/sha}","comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/comments{/number}","issue_comment_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/comments{/number}","contents_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contents/{+path}","compare_url":"https://api.github.com/repos/sumup-oss/circuit-ui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sumup-oss/circuit-ui/merges","archive_url":"https://api.github.com/repos/sumup-oss/circuit-ui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sumup-oss/circuit-ui/downloads","issues_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues{/number}","pulls_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls{/number}","milestones_url":"https://api.github.com/repos/sumup-oss/circuit-ui/milestones{/number}","notifications_url":"https://api.github.com/repos/sumup-oss/circuit-ui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels{/name}","releases_url":"https://api.github.com/repos/sumup-oss/circuit-ui/releases{/id}","deployments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/deployments","created_at":"2017-06-26T09:57:50Z","updated_at":"2021-07-21T08:01:20Z","pushed_at":"2021-07-20T15:49:41Z","git_url":"git://github.com/sumup-oss/circuit-ui.git","ssh_url":"git@github.com:sumup-oss/circuit-ui.git","clone_url":"https://github.com/sumup-oss/circuit-ui.git","svn_url":"https://github.com/sumup-oss/circuit-ui","homepage":"https://circuit.sumup.com","size":19657,"stargazers_count":664,"watchers_count":664,"language":"TypeScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":114,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":31,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":114,"open_issues":31,"watchers":664,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047"},"html":{"href":"https://github.com/sumup-oss/circuit-ui/pull/1047"},"issue":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047"},"comments":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047/comments"},"review_comments":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/comments"},"review_comment":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/commits"},"statuses":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/cf36d9948e816632153fb51523a5890496f21e95"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":47663619,"login":"sumup-oss","gravatar_id":"","url":"https://api.github.com/orgs/sumup-oss","avatar_url":"https://avatars.githubusercontent.com/u/47663619?"}} +{"id":"17246339256","type":"PullRequestEvent","actor":{"id":29139614,"login":"renovate[bot]","display_login":"renovate","gravatar_id":"","url":"https://api.github.com/users/renovate[bot]","avatar_url":"https://avatars.githubusercontent.com/u/29139614?"},"repo":{"id":388000702,"name":"egarciahz/sgen-client","url":"https://api.github.com/repos/egarciahz/sgen-client"},"payload":{"action":"opened","number":4,"pull_request":{"url":"https://api.github.com/repos/egarciahz/sgen-client/pulls/4","id":694185695,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1Njk1","html_url":"https://github.com/egarciahz/sgen-client/pull/4","diff_url":"https://github.com/egarciahz/sgen-client/pull/4.diff","patch_url":"https://github.com/egarciahz/sgen-client/pull/4.patch","issue_url":"https://api.github.com/repos/egarciahz/sgen-client/issues/4","number":4,"state":"open","locked":false,"title":"Pin dependencies","user":{"login":"renovate[bot]","id":29139614,"node_id":"MDM6Qm90MjkxMzk2MTQ=","avatar_url":"https://avatars.githubusercontent.com/in/2740?v=4","gravatar_id":"","url":"https://api.github.com/users/renovate%5Bbot%5D","html_url":"https://github.com/apps/renovate","followers_url":"https://api.github.com/users/renovate%5Bbot%5D/followers","following_url":"https://api.github.com/users/renovate%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/renovate%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/renovate%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/renovate%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/renovate%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/renovate%5Bbot%5D/repos","events_url":"https://api.github.com/users/renovate%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/renovate%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)\n\nThis PR contains the following updates:\n\n| Package | Type | Update | Change |\n|---|---|---|---|\n| [react](https://reactjs.org/) ([source](https://togithub.com/facebook/react)) | dependencies | pin | [`^17.0.2` -> `17.0.2`](https://renovatebot.com/diffs/npm/react/17.0.2/17.0.2) |\n| [react-dom](https://reactjs.org/) ([source](https://togithub.com/facebook/react)) | dependencies | pin | [`^17.0.2` -> `17.0.2`](https://renovatebot.com/diffs/npm/react-dom/17.0.2/17.0.2) |\n\n📌 **Important**: Renovate will wait until you have merged this Pin PR before creating any *upgrade* PRs for the affected packages. Add the preset `:preserveSemverRanges` to your config if you instead don't wish to pin dependencies.\n\n---\n\n### Configuration\n\n📅 **Schedule**: At any time (no schedule defined).\n\n🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.\n\n♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.\n\n🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.\n\n---\n\n - [ ] If you want to rebase/retry this PR, check this box.\n\n---\n\nThis PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/egarciahz/sgen-client).","created_at":"2021-07-21T08:59:59Z","updated_at":"2021-07-21T08:59:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/egarciahz/sgen-client/pulls/4/commits","review_comments_url":"https://api.github.com/repos/egarciahz/sgen-client/pulls/4/comments","review_comment_url":"https://api.github.com/repos/egarciahz/sgen-client/pulls/comments{/number}","comments_url":"https://api.github.com/repos/egarciahz/sgen-client/issues/4/comments","statuses_url":"https://api.github.com/repos/egarciahz/sgen-client/statuses/2898832468e2a664e528515708c08e89d718981e","head":{"label":"egarciahz:renovate/react-monorepo","ref":"renovate/react-monorepo","sha":"2898832468e2a664e528515708c08e89d718981e","user":{"login":"egarciahz","id":44587679,"node_id":"MDQ6VXNlcjQ0NTg3Njc5","avatar_url":"https://avatars.githubusercontent.com/u/44587679?v=4","gravatar_id":"","url":"https://api.github.com/users/egarciahz","html_url":"https://github.com/egarciahz","followers_url":"https://api.github.com/users/egarciahz/followers","following_url":"https://api.github.com/users/egarciahz/following{/other_user}","gists_url":"https://api.github.com/users/egarciahz/gists{/gist_id}","starred_url":"https://api.github.com/users/egarciahz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/egarciahz/subscriptions","organizations_url":"https://api.github.com/users/egarciahz/orgs","repos_url":"https://api.github.com/users/egarciahz/repos","events_url":"https://api.github.com/users/egarciahz/events{/privacy}","received_events_url":"https://api.github.com/users/egarciahz/received_events","type":"User","site_admin":false},"repo":{"id":388000702,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMDA3MDI=","name":"sgen-client","full_name":"egarciahz/sgen-client","private":false,"owner":{"login":"egarciahz","id":44587679,"node_id":"MDQ6VXNlcjQ0NTg3Njc5","avatar_url":"https://avatars.githubusercontent.com/u/44587679?v=4","gravatar_id":"","url":"https://api.github.com/users/egarciahz","html_url":"https://github.com/egarciahz","followers_url":"https://api.github.com/users/egarciahz/followers","following_url":"https://api.github.com/users/egarciahz/following{/other_user}","gists_url":"https://api.github.com/users/egarciahz/gists{/gist_id}","starred_url":"https://api.github.com/users/egarciahz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/egarciahz/subscriptions","organizations_url":"https://api.github.com/users/egarciahz/orgs","repos_url":"https://api.github.com/users/egarciahz/repos","events_url":"https://api.github.com/users/egarciahz/events{/privacy}","received_events_url":"https://api.github.com/users/egarciahz/received_events","type":"User","site_admin":false},"html_url":"https://github.com/egarciahz/sgen-client","description":"web client for sgen server project","fork":false,"url":"https://api.github.com/repos/egarciahz/sgen-client","forks_url":"https://api.github.com/repos/egarciahz/sgen-client/forks","keys_url":"https://api.github.com/repos/egarciahz/sgen-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/egarciahz/sgen-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/egarciahz/sgen-client/teams","hooks_url":"https://api.github.com/repos/egarciahz/sgen-client/hooks","issue_events_url":"https://api.github.com/repos/egarciahz/sgen-client/issues/events{/number}","events_url":"https://api.github.com/repos/egarciahz/sgen-client/events","assignees_url":"https://api.github.com/repos/egarciahz/sgen-client/assignees{/user}","branches_url":"https://api.github.com/repos/egarciahz/sgen-client/branches{/branch}","tags_url":"https://api.github.com/repos/egarciahz/sgen-client/tags","blobs_url":"https://api.github.com/repos/egarciahz/sgen-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/egarciahz/sgen-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/egarciahz/sgen-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/egarciahz/sgen-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/egarciahz/sgen-client/statuses/{sha}","languages_url":"https://api.github.com/repos/egarciahz/sgen-client/languages","stargazers_url":"https://api.github.com/repos/egarciahz/sgen-client/stargazers","contributors_url":"https://api.github.com/repos/egarciahz/sgen-client/contributors","subscribers_url":"https://api.github.com/repos/egarciahz/sgen-client/subscribers","subscription_url":"https://api.github.com/repos/egarciahz/sgen-client/subscription","commits_url":"https://api.github.com/repos/egarciahz/sgen-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/egarciahz/sgen-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/egarciahz/sgen-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/egarciahz/sgen-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/egarciahz/sgen-client/contents/{+path}","compare_url":"https://api.github.com/repos/egarciahz/sgen-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/egarciahz/sgen-client/merges","archive_url":"https://api.github.com/repos/egarciahz/sgen-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/egarciahz/sgen-client/downloads","issues_url":"https://api.github.com/repos/egarciahz/sgen-client/issues{/number}","pulls_url":"https://api.github.com/repos/egarciahz/sgen-client/pulls{/number}","milestones_url":"https://api.github.com/repos/egarciahz/sgen-client/milestones{/number}","notifications_url":"https://api.github.com/repos/egarciahz/sgen-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/egarciahz/sgen-client/labels{/name}","releases_url":"https://api.github.com/repos/egarciahz/sgen-client/releases{/id}","deployments_url":"https://api.github.com/repos/egarciahz/sgen-client/deployments","created_at":"2021-07-21T05:10:50Z","updated_at":"2021-07-21T05:12:40Z","pushed_at":"2021-07-21T08:59:59Z","git_url":"git://github.com/egarciahz/sgen-client.git","ssh_url":"git@github.com:egarciahz/sgen-client.git","clone_url":"https://github.com/egarciahz/sgen-client.git","svn_url":"https://github.com/egarciahz/sgen-client","homepage":null,"size":295,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":null,"forks":0,"open_issues":4,"watchers":0,"default_branch":"master"}},"base":{"label":"egarciahz:master","ref":"master","sha":"f6dd3adb54202c85ae1981e1a31cae1e66dd9ff5","user":{"login":"egarciahz","id":44587679,"node_id":"MDQ6VXNlcjQ0NTg3Njc5","avatar_url":"https://avatars.githubusercontent.com/u/44587679?v=4","gravatar_id":"","url":"https://api.github.com/users/egarciahz","html_url":"https://github.com/egarciahz","followers_url":"https://api.github.com/users/egarciahz/followers","following_url":"https://api.github.com/users/egarciahz/following{/other_user}","gists_url":"https://api.github.com/users/egarciahz/gists{/gist_id}","starred_url":"https://api.github.com/users/egarciahz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/egarciahz/subscriptions","organizations_url":"https://api.github.com/users/egarciahz/orgs","repos_url":"https://api.github.com/users/egarciahz/repos","events_url":"https://api.github.com/users/egarciahz/events{/privacy}","received_events_url":"https://api.github.com/users/egarciahz/received_events","type":"User","site_admin":false},"repo":{"id":388000702,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwMDA3MDI=","name":"sgen-client","full_name":"egarciahz/sgen-client","private":false,"owner":{"login":"egarciahz","id":44587679,"node_id":"MDQ6VXNlcjQ0NTg3Njc5","avatar_url":"https://avatars.githubusercontent.com/u/44587679?v=4","gravatar_id":"","url":"https://api.github.com/users/egarciahz","html_url":"https://github.com/egarciahz","followers_url":"https://api.github.com/users/egarciahz/followers","following_url":"https://api.github.com/users/egarciahz/following{/other_user}","gists_url":"https://api.github.com/users/egarciahz/gists{/gist_id}","starred_url":"https://api.github.com/users/egarciahz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/egarciahz/subscriptions","organizations_url":"https://api.github.com/users/egarciahz/orgs","repos_url":"https://api.github.com/users/egarciahz/repos","events_url":"https://api.github.com/users/egarciahz/events{/privacy}","received_events_url":"https://api.github.com/users/egarciahz/received_events","type":"User","site_admin":false},"html_url":"https://github.com/egarciahz/sgen-client","description":"web client for sgen server project","fork":false,"url":"https://api.github.com/repos/egarciahz/sgen-client","forks_url":"https://api.github.com/repos/egarciahz/sgen-client/forks","keys_url":"https://api.github.com/repos/egarciahz/sgen-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/egarciahz/sgen-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/egarciahz/sgen-client/teams","hooks_url":"https://api.github.com/repos/egarciahz/sgen-client/hooks","issue_events_url":"https://api.github.com/repos/egarciahz/sgen-client/issues/events{/number}","events_url":"https://api.github.com/repos/egarciahz/sgen-client/events","assignees_url":"https://api.github.com/repos/egarciahz/sgen-client/assignees{/user}","branches_url":"https://api.github.com/repos/egarciahz/sgen-client/branches{/branch}","tags_url":"https://api.github.com/repos/egarciahz/sgen-client/tags","blobs_url":"https://api.github.com/repos/egarciahz/sgen-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/egarciahz/sgen-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/egarciahz/sgen-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/egarciahz/sgen-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/egarciahz/sgen-client/statuses/{sha}","languages_url":"https://api.github.com/repos/egarciahz/sgen-client/languages","stargazers_url":"https://api.github.com/repos/egarciahz/sgen-client/stargazers","contributors_url":"https://api.github.com/repos/egarciahz/sgen-client/contributors","subscribers_url":"https://api.github.com/repos/egarciahz/sgen-client/subscribers","subscription_url":"https://api.github.com/repos/egarciahz/sgen-client/subscription","commits_url":"https://api.github.com/repos/egarciahz/sgen-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/egarciahz/sgen-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/egarciahz/sgen-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/egarciahz/sgen-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/egarciahz/sgen-client/contents/{+path}","compare_url":"https://api.github.com/repos/egarciahz/sgen-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/egarciahz/sgen-client/merges","archive_url":"https://api.github.com/repos/egarciahz/sgen-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/egarciahz/sgen-client/downloads","issues_url":"https://api.github.com/repos/egarciahz/sgen-client/issues{/number}","pulls_url":"https://api.github.com/repos/egarciahz/sgen-client/pulls{/number}","milestones_url":"https://api.github.com/repos/egarciahz/sgen-client/milestones{/number}","notifications_url":"https://api.github.com/repos/egarciahz/sgen-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/egarciahz/sgen-client/labels{/name}","releases_url":"https://api.github.com/repos/egarciahz/sgen-client/releases{/id}","deployments_url":"https://api.github.com/repos/egarciahz/sgen-client/deployments","created_at":"2021-07-21T05:10:50Z","updated_at":"2021-07-21T05:12:40Z","pushed_at":"2021-07-21T08:59:59Z","git_url":"git://github.com/egarciahz/sgen-client.git","ssh_url":"git@github.com:egarciahz/sgen-client.git","clone_url":"https://github.com/egarciahz/sgen-client.git","svn_url":"https://github.com/egarciahz/sgen-client","homepage":null,"size":295,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":null,"forks":0,"open_issues":4,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/egarciahz/sgen-client/pulls/4"},"html":{"href":"https://github.com/egarciahz/sgen-client/pull/4"},"issue":{"href":"https://api.github.com/repos/egarciahz/sgen-client/issues/4"},"comments":{"href":"https://api.github.com/repos/egarciahz/sgen-client/issues/4/comments"},"review_comments":{"href":"https://api.github.com/repos/egarciahz/sgen-client/pulls/4/comments"},"review_comment":{"href":"https://api.github.com/repos/egarciahz/sgen-client/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/egarciahz/sgen-client/pulls/4/commits"},"statuses":{"href":"https://api.github.com/repos/egarciahz/sgen-client/statuses/2898832468e2a664e528515708c08e89d718981e"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":2,"deletions":2,"changed_files":1}},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339259","type":"PullRequestReviewEvent","actor":{"id":35560568,"login":"robinmetral","display_login":"robinmetral","gravatar_id":"","url":"https://api.github.com/users/robinmetral","avatar_url":"https://avatars.githubusercontent.com/u/35560568?"},"repo":{"id":95433249,"name":"sumup-oss/circuit-ui","url":"https://api.github.com/repos/sumup-oss/circuit-ui"},"payload":{"action":"created","review":{"id":711326737,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExMzI2NzM3","user":{"login":"robinmetral","id":35560568,"node_id":"MDQ6VXNlcjM1NTYwNTY4","avatar_url":"https://avatars.githubusercontent.com/u/35560568?v=4","gravatar_id":"","url":"https://api.github.com/users/robinmetral","html_url":"https://github.com/robinmetral","followers_url":"https://api.github.com/users/robinmetral/followers","following_url":"https://api.github.com/users/robinmetral/following{/other_user}","gists_url":"https://api.github.com/users/robinmetral/gists{/gist_id}","starred_url":"https://api.github.com/users/robinmetral/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robinmetral/subscriptions","organizations_url":"https://api.github.com/users/robinmetral/orgs","repos_url":"https://api.github.com/users/robinmetral/repos","events_url":"https://api.github.com/users/robinmetral/events{/privacy}","received_events_url":"https://api.github.com/users/robinmetral/received_events","type":"User","site_admin":false},"body":"","commit_id":"cf36d9948e816632153fb51523a5890496f21e95","submitted_at":"2021-07-21T08:59:59Z","state":"commented","html_url":"https://github.com/sumup-oss/circuit-ui/pull/1047#pullrequestreview-711326737","pull_request_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/sumup-oss/circuit-ui/pull/1047#pullrequestreview-711326737"},"pull_request":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047"}}},"pull_request":{"url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047","id":693321734,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzMzIxNzM0","html_url":"https://github.com/sumup-oss/circuit-ui/pull/1047","diff_url":"https://github.com/sumup-oss/circuit-ui/pull/1047.diff","patch_url":"https://github.com/sumup-oss/circuit-ui/pull/1047.patch","issue_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047","number":1047,"state":"open","locked":false,"title":"Improve the ImageInput for custom components","user":{"login":"connor-baer","id":11017722,"node_id":"MDQ6VXNlcjExMDE3NzIy","avatar_url":"https://avatars.githubusercontent.com/u/11017722?v=4","gravatar_id":"","url":"https://api.github.com/users/connor-baer","html_url":"https://github.com/connor-baer","followers_url":"https://api.github.com/users/connor-baer/followers","following_url":"https://api.github.com/users/connor-baer/following{/other_user}","gists_url":"https://api.github.com/users/connor-baer/gists{/gist_id}","starred_url":"https://api.github.com/users/connor-baer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/connor-baer/subscriptions","organizations_url":"https://api.github.com/users/connor-baer/orgs","repos_url":"https://api.github.com/users/connor-baer/repos","events_url":"https://api.github.com/users/connor-baer/events{/privacy}","received_events_url":"https://api.github.com/users/connor-baer/received_events","type":"User","site_admin":false},"body":"## Purpose\r\n\r\n@justman00 and I are trying to use the ImageInput inside the Popover component. When clicking the button to clear the image, the popover is closed. We confirmed that the popover is closed by the `useOutsideClick` hook. We suspect this happens because the clear button is removed from the DOM before the outside click handler is executed, so it doesn't know that the clear button was _inside_ the popover. In order to prevent this, we plan to use `event.stopPropagation()` in the `onClear` callback.\r\n\r\nThe second challenge we faced was using the ImageInput with the `identity` variant of the Avatar component which has a circular border radius. \r\n\r\n## Approach and changes\r\n\r\n- Pass the click event to the `onClear` prop of the ImageInput\r\n- Make the ImageInput's border-radius configurable through the addition of a `borderRadius` prop (I’m not happy about exposing such a verbose styling prop, but I don’t see a better way 😕)\r\n\r\n## Definition of done\r\n\r\n* [x] Development completed\r\n* [x] Reviewers assigned\r\n* [x] Unit and integration tests\r\n* [x] Meets minimum browser support\r\n* [x] Meets accessibility requirements\r\n","created_at":"2021-07-20T09:46:56Z","updated_at":"2021-07-21T08:59:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c9981e00b8873ffec048751cff67c1124ee77975","assignee":null,"assignees":[],"requested_reviewers":[{"login":"justman00","id":36477870,"node_id":"MDQ6VXNlcjM2NDc3ODcw","avatar_url":"https://avatars.githubusercontent.com/u/36477870?v=4","gravatar_id":"","url":"https://api.github.com/users/justman00","html_url":"https://github.com/justman00","followers_url":"https://api.github.com/users/justman00/followers","following_url":"https://api.github.com/users/justman00/following{/other_user}","gists_url":"https://api.github.com/users/justman00/gists{/gist_id}","starred_url":"https://api.github.com/users/justman00/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/justman00/subscriptions","organizations_url":"https://api.github.com/users/justman00/orgs","repos_url":"https://api.github.com/users/justman00/repos","events_url":"https://api.github.com/users/justman00/events{/privacy}","received_events_url":"https://api.github.com/users/justman00/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":634343322,"node_id":"MDU6TGFiZWw2MzQzNDMzMjI=","url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels/%F0%9F%90%9E%20bug","name":"🐞 bug","color":"E99695","default":false,"description":"Something isn't working as it should"},{"id":2913852247,"node_id":"MDU6TGFiZWwyOTEzODUyMjQ3","url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels/%F0%9F%97%82%20circuit-ui","name":"🗂 circuit-ui","color":"ededed","default":false,"description":null}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/commits","review_comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/comments","review_comment_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047/comments","statuses_url":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/cf36d9948e816632153fb51523a5890496f21e95","head":{"label":"sumup-oss:bugfix/imageinput-clear-event","ref":"bugfix/imageinput-clear-event","sha":"cf36d9948e816632153fb51523a5890496f21e95","user":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"repo":{"id":95433249,"node_id":"MDEwOlJlcG9zaXRvcnk5NTQzMzI0OQ==","name":"circuit-ui","full_name":"sumup-oss/circuit-ui","private":false,"owner":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sumup-oss/circuit-ui","description":"SumUp's component library for the web","fork":false,"url":"https://api.github.com/repos/sumup-oss/circuit-ui","forks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/forks","keys_url":"https://api.github.com/repos/sumup-oss/circuit-ui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sumup-oss/circuit-ui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sumup-oss/circuit-ui/teams","hooks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/hooks","issue_events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/events{/number}","events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/events","assignees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/assignees{/user}","branches_url":"https://api.github.com/repos/sumup-oss/circuit-ui/branches{/branch}","tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/tags","blobs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/refs{/sha}","trees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/{sha}","languages_url":"https://api.github.com/repos/sumup-oss/circuit-ui/languages","stargazers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/stargazers","contributors_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contributors","subscribers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscribers","subscription_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscription","commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/commits{/sha}","git_commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/commits{/sha}","comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/comments{/number}","issue_comment_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/comments{/number}","contents_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contents/{+path}","compare_url":"https://api.github.com/repos/sumup-oss/circuit-ui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sumup-oss/circuit-ui/merges","archive_url":"https://api.github.com/repos/sumup-oss/circuit-ui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sumup-oss/circuit-ui/downloads","issues_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues{/number}","pulls_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls{/number}","milestones_url":"https://api.github.com/repos/sumup-oss/circuit-ui/milestones{/number}","notifications_url":"https://api.github.com/repos/sumup-oss/circuit-ui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels{/name}","releases_url":"https://api.github.com/repos/sumup-oss/circuit-ui/releases{/id}","deployments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/deployments","created_at":"2017-06-26T09:57:50Z","updated_at":"2021-07-21T08:01:20Z","pushed_at":"2021-07-20T15:49:41Z","git_url":"git://github.com/sumup-oss/circuit-ui.git","ssh_url":"git@github.com:sumup-oss/circuit-ui.git","clone_url":"https://github.com/sumup-oss/circuit-ui.git","svn_url":"https://github.com/sumup-oss/circuit-ui","homepage":"https://circuit.sumup.com","size":19657,"stargazers_count":664,"watchers_count":664,"language":"TypeScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":114,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":31,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":114,"open_issues":31,"watchers":664,"default_branch":"main"}},"base":{"label":"sumup-oss:main","ref":"main","sha":"96e03ac4055310a4c7afc72344e3331fa778cacf","user":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"repo":{"id":95433249,"node_id":"MDEwOlJlcG9zaXRvcnk5NTQzMzI0OQ==","name":"circuit-ui","full_name":"sumup-oss/circuit-ui","private":false,"owner":{"login":"sumup-oss","id":47663619,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3NjYzNjE5","avatar_url":"https://avatars.githubusercontent.com/u/47663619?v=4","gravatar_id":"","url":"https://api.github.com/users/sumup-oss","html_url":"https://github.com/sumup-oss","followers_url":"https://api.github.com/users/sumup-oss/followers","following_url":"https://api.github.com/users/sumup-oss/following{/other_user}","gists_url":"https://api.github.com/users/sumup-oss/gists{/gist_id}","starred_url":"https://api.github.com/users/sumup-oss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sumup-oss/subscriptions","organizations_url":"https://api.github.com/users/sumup-oss/orgs","repos_url":"https://api.github.com/users/sumup-oss/repos","events_url":"https://api.github.com/users/sumup-oss/events{/privacy}","received_events_url":"https://api.github.com/users/sumup-oss/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sumup-oss/circuit-ui","description":"SumUp's component library for the web","fork":false,"url":"https://api.github.com/repos/sumup-oss/circuit-ui","forks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/forks","keys_url":"https://api.github.com/repos/sumup-oss/circuit-ui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sumup-oss/circuit-ui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sumup-oss/circuit-ui/teams","hooks_url":"https://api.github.com/repos/sumup-oss/circuit-ui/hooks","issue_events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/events{/number}","events_url":"https://api.github.com/repos/sumup-oss/circuit-ui/events","assignees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/assignees{/user}","branches_url":"https://api.github.com/repos/sumup-oss/circuit-ui/branches{/branch}","tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/tags","blobs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/refs{/sha}","trees_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/{sha}","languages_url":"https://api.github.com/repos/sumup-oss/circuit-ui/languages","stargazers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/stargazers","contributors_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contributors","subscribers_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscribers","subscription_url":"https://api.github.com/repos/sumup-oss/circuit-ui/subscription","commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/commits{/sha}","git_commits_url":"https://api.github.com/repos/sumup-oss/circuit-ui/git/commits{/sha}","comments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/comments{/number}","issue_comment_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/comments{/number}","contents_url":"https://api.github.com/repos/sumup-oss/circuit-ui/contents/{+path}","compare_url":"https://api.github.com/repos/sumup-oss/circuit-ui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sumup-oss/circuit-ui/merges","archive_url":"https://api.github.com/repos/sumup-oss/circuit-ui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sumup-oss/circuit-ui/downloads","issues_url":"https://api.github.com/repos/sumup-oss/circuit-ui/issues{/number}","pulls_url":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls{/number}","milestones_url":"https://api.github.com/repos/sumup-oss/circuit-ui/milestones{/number}","notifications_url":"https://api.github.com/repos/sumup-oss/circuit-ui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sumup-oss/circuit-ui/labels{/name}","releases_url":"https://api.github.com/repos/sumup-oss/circuit-ui/releases{/id}","deployments_url":"https://api.github.com/repos/sumup-oss/circuit-ui/deployments","created_at":"2017-06-26T09:57:50Z","updated_at":"2021-07-21T08:01:20Z","pushed_at":"2021-07-20T15:49:41Z","git_url":"git://github.com/sumup-oss/circuit-ui.git","ssh_url":"git@github.com:sumup-oss/circuit-ui.git","clone_url":"https://github.com/sumup-oss/circuit-ui.git","svn_url":"https://github.com/sumup-oss/circuit-ui","homepage":"https://circuit.sumup.com","size":19657,"stargazers_count":664,"watchers_count":664,"language":"TypeScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":114,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":31,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":114,"open_issues":31,"watchers":664,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047"},"html":{"href":"https://github.com/sumup-oss/circuit-ui/pull/1047"},"issue":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047"},"comments":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/issues/1047/comments"},"review_comments":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/comments"},"review_comment":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/pulls/1047/commits"},"statuses":{"href":"https://api.github.com/repos/sumup-oss/circuit-ui/statuses/cf36d9948e816632153fb51523a5890496f21e95"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":47663619,"login":"sumup-oss","gravatar_id":"","url":"https://api.github.com/orgs/sumup-oss","avatar_url":"https://avatars.githubusercontent.com/u/47663619?"}} +{"id":"17246339270","type":"CreateEvent","actor":{"id":19872105,"login":"Sahil-Gulati","display_login":"Sahil-Gulati","gravatar_id":"","url":"https://api.github.com/users/Sahil-Gulati","avatar_url":"https://avatars.githubusercontent.com/u/19872105?"},"repo":{"id":388056956,"name":"Sahil-Gulati/backstage-demo","url":"https://api.github.com/repos/Sahil-Gulati/backstage-demo"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339283","type":"PushEvent","actor":{"id":36457245,"login":"t3dkich","display_login":"t3dkich","gravatar_id":"","url":"https://api.github.com/users/t3dkich","avatar_url":"https://avatars.githubusercontent.com/u/36457245?"},"repo":{"id":388014103,"name":"weichain/hydraswap-token-list","url":"https://api.github.com/repos/weichain/hydraswap-token-list"},"payload":{"push_id":7562450996,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"5b557eef0e779b803820806a152bd19b788a94b7","before":"24de463b55a3eaf80b0579845d77391945771d85","commits":[{"sha":"5b557eef0e779b803820806a152bd19b788a94b7","author":{"name":"Teodor Zafirov","email":"674b4d4f08a012bd5744abc82614b90bb0de19bd@users.noreply.github.com"},"message":"added hydraswap-list json file and token icons","distinct":true,"url":"https://api.github.com/repos/weichain/hydraswap-token-list/commits/5b557eef0e779b803820806a152bd19b788a94b7"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":66565535,"login":"weichain","gravatar_id":"","url":"https://api.github.com/orgs/weichain","avatar_url":"https://avatars.githubusercontent.com/u/66565535?"}} +{"id":"17246339293","type":"PushEvent","actor":{"id":81258380,"login":"Helikopter-Bojowy","display_login":"Helikopter-Bojowy","gravatar_id":"","url":"https://api.github.com/users/Helikopter-Bojowy","avatar_url":"https://avatars.githubusercontent.com/u/81258380?"},"repo":{"id":352069365,"name":"Helikopter-Bojowy/Exp-na-helikopterze","url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze"},"payload":{"push_id":7562451014,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"47d3d6dd2d206a05c36104f502f202465a7b0a3b","before":"b92f8ef2ffaca678f13645cd8cc352807d9c86ae","commits":[{"sha":"47d3d6dd2d206a05c36104f502f202465a7b0a3b","author":{"name":"Helikopter-Bojowy","email":"733707e04c8bf7c874ce97516f8f637586fc79d7@users.noreply.github.com"},"message":"1626857999365:847486319664169040","distinct":true,"url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze/commits/47d3d6dd2d206a05c36104f502f202465a7b0a3b"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339295","type":"PushEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":385328662,"name":"thatjohn01/751618019","url":"https://api.github.com/repos/thatjohn01/751618019"},"payload":{"push_id":7562451001,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"474602f1bb47b9c773edf2dbff05de5d245c9272","before":"8609284b216bbc403327fd2acda1d90d5a55e223","commits":[{"sha":"474602f1bb47b9c773edf2dbff05de5d245c9272","author":{"name":"thatjohn01","email":"72eb81e66410b3da65da4cca287ce0578825ce64@users.noreply.github.com"},"message":"change README.md","distinct":true,"url":"https://api.github.com/repos/thatjohn01/751618019/commits/474602f1bb47b9c773edf2dbff05de5d245c9272"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339307","type":"PushEvent","actor":{"id":31834656,"login":"angelia-yuqi-personal","display_login":"angelia-yuqi-personal","gravatar_id":"","url":"https://api.github.com/users/angelia-yuqi-personal","avatar_url":"https://avatars.githubusercontent.com/u/31834656?"},"repo":{"id":357159441,"name":"Conflux-Chain/rigel","url":"https://api.github.com/repos/Conflux-Chain/rigel"},"payload":{"push_id":7562451021,"size":1,"distinct_size":1,"ref":"refs/heads/feature-history","head":"6edcf752f958925e5dfd730f3f5be2f8c9095e3f","before":"5c06dbd894131b9a85258149a660d732927c5af5","commits":[{"sha":"6edcf752f958925e5dfd730f3f5be2f8c9095e3f","author":{"name":"angelia","email":"3b539c6a1b7cc89542e684400f3757269d80c913@126.com"},"message":"fix: ui style","distinct":true,"url":"https://api.github.com/repos/Conflux-Chain/rigel/commits/6edcf752f958925e5dfd730f3f5be2f8c9095e3f"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":46887720,"login":"Conflux-Chain","gravatar_id":"","url":"https://api.github.com/orgs/Conflux-Chain","avatar_url":"https://avatars.githubusercontent.com/u/46887720?"}} +{"id":"17246339310","type":"PushEvent","actor":{"id":48402225,"login":"SiongSng","display_login":"SiongSng","gravatar_id":"","url":"https://api.github.com/users/SiongSng","avatar_url":"https://avatars.githubusercontent.com/u/48402225?"},"repo":{"id":339984937,"name":"RPMTW/ResourcePack-Mod-zh_tw","url":"https://api.github.com/repos/RPMTW/ResourcePack-Mod-zh_tw"},"payload":{"push_id":7562450997,"size":1,"distinct_size":1,"ref":"refs/heads/Translated-1.12","head":"a45aa85e87eb7daa75b7bbce10c833954681fed9","before":"70ed25735169cb4fe26ba5646e2a3f4776f918e8","commits":[{"sha":"a45aa85e87eb7daa75b7bbce10c833954681fed9","author":{"name":"菘菘","email":"0797749c333bd5e99128fa8b6a96bed1d5c7004f@gmail.com"},"message":"New translations zh_tw.json (Chinese Traditional)","distinct":true,"url":"https://api.github.com/repos/RPMTW/ResourcePack-Mod-zh_tw/commits/a45aa85e87eb7daa75b7bbce10c833954681fed9"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":83767842,"login":"RPMTW","gravatar_id":"","url":"https://api.github.com/orgs/RPMTW","avatar_url":"https://avatars.githubusercontent.com/u/83767842?"}} +{"id":"17246339327","type":"PushEvent","actor":{"id":87263540,"login":"garrydb","display_login":"garrydb","gravatar_id":"","url":"https://api.github.com/users/garrydb","avatar_url":"https://avatars.githubusercontent.com/u/87263540?"},"repo":{"id":384916504,"name":"garrydb/garrydb","url":"https://api.github.com/repos/garrydb/garrydb"},"payload":{"push_id":7562451016,"size":1,"distinct_size":1,"ref":"refs/heads/Autofac","head":"ce3d29304e014475e1db462cdf43a9c8de7a1962","before":"b8910e35b26cb7d5f5a515a4840f1431d00d67a1","commits":[{"sha":"ce3d29304e014475e1db462cdf43a9c8de7a1962","author":{"name":"GarryDB","email":"64b2b6d12bfe4baae7dad3d018f8cbf6b0e7a044@garrydb.com"},"message":"Implement a very basic plugin-to-plugin communication with Akka.NET","distinct":true,"url":"https://api.github.com/repos/garrydb/garrydb/commits/ce3d29304e014475e1db462cdf43a9c8de7a1962"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339328","type":"PushEvent","actor":{"id":58833107,"login":"hrhwve3553","display_login":"hrhwve3553","gravatar_id":"","url":"https://api.github.com/users/hrhwve3553","avatar_url":"https://avatars.githubusercontent.com/u/58833107?"},"repo":{"id":227724995,"name":"hrhwve3553/djy","url":"https://api.github.com/repos/hrhwve3553/djy"},"payload":{"push_id":7562451004,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8bcd3dcb3024f61d129bac8b13410a314c29191a","before":"9ae9ce3e94f27081e9865a5d0034d1944ac0e5cb","commits":[{"sha":"8bcd3dcb3024f61d129bac8b13410a314c29191a","author":{"name":"hrhwve3553","email":"10135ac1af9bed4c0bec7c29db44050e24a097b2@users.noreply.github.com"},"message":"Update nsc413_2.md","distinct":true,"url":"https://api.github.com/repos/hrhwve3553/djy/commits/8bcd3dcb3024f61d129bac8b13410a314c29191a"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339334","type":"CommitCommentEvent","actor":{"id":87752408,"login":"githubname21","display_login":"githubname21","gravatar_id":"","url":"https://api.github.com/users/githubname21","avatar_url":"https://avatars.githubusercontent.com/u/87752408?"},"repo":{"id":388055710,"name":"githubname21/test","url":"https://api.github.com/repos/githubname21/test"},"payload":{"comment":{"url":"https://api.github.com/repos/githubname21/test/comments/53771640","html_url":"https://github.com/githubname21/test/commit/9d746167b35af02a151bbed3ae3548af5b531ef9#commitcomment-53771640","id":53771640,"node_id":"MDEzOkNvbW1pdENvbW1lbnQ1Mzc3MTY0MA==","user":{"login":"githubname21","id":87752408,"node_id":"MDQ6VXNlcjg3NzUyNDA4","avatar_url":"https://avatars.githubusercontent.com/u/87752408?v=4","gravatar_id":"","url":"https://api.github.com/users/githubname21","html_url":"https://github.com/githubname21","followers_url":"https://api.github.com/users/githubname21/followers","following_url":"https://api.github.com/users/githubname21/following{/other_user}","gists_url":"https://api.github.com/users/githubname21/gists{/gist_id}","starred_url":"https://api.github.com/users/githubname21/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/githubname21/subscriptions","organizations_url":"https://api.github.com/users/githubname21/orgs","repos_url":"https://api.github.com/users/githubname21/repos","events_url":"https://api.github.com/users/githubname21/events{/privacy}","received_events_url":"https://api.github.com/users/githubname21/received_events","type":"User","site_admin":false},"position":null,"line":null,"path":null,"commit_id":"9d746167b35af02a151bbed3ae3548af5b531ef9","created_at":"2021-07-21T09:00:00Z","updated_at":"2021-07-21T09:00:00Z","author_association":"OWNER","body":"a.php"}},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339335","type":"WatchEvent","actor":{"id":45672104,"login":"FDlucifer","display_login":"FDlucifer","gravatar_id":"","url":"https://api.github.com/users/FDlucifer","avatar_url":"https://avatars.githubusercontent.com/u/45672104?"},"repo":{"id":167602620,"name":"a0rtega/cpufuzz","url":"https://api.github.com/repos/a0rtega/cpufuzz"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339337","type":"CreateEvent","actor":{"id":190640,"login":"kahgeh","display_login":"kahgeh","gravatar_id":"","url":"https://api.github.com/users/kahgeh","avatar_url":"https://avatars.githubusercontent.com/u/190640?"},"repo":{"id":385939072,"name":"kahgeh/open-sse","url":"https://api.github.com/repos/kahgeh/open-sse"},"payload":{"ref":"v0.0.2","ref_type":"tag","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339339","type":"CreateEvent","actor":{"id":73436831,"login":"mayank-mallick","display_login":"mayank-mallick","gravatar_id":"","url":"https://api.github.com/users/mayank-mallick","avatar_url":"https://avatars.githubusercontent.com/u/73436831?"},"repo":{"id":388056957,"name":"mayank-mallick/password-generator","url":"https://api.github.com/repos/mayank-mallick/password-generator"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":"password-generator","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339349","type":"CreateEvent","actor":{"id":29139614,"login":"renovate[bot]","display_login":"renovate","gravatar_id":"","url":"https://api.github.com/users/renovate[bot]","avatar_url":"https://avatars.githubusercontent.com/u/29139614?"},"repo":{"id":315713528,"name":"Yfill/waves","url":"https://api.github.com/repos/Yfill/waves"},"payload":{"ref":"renovate/rollup-2.x","ref_type":"branch","master_branch":"main","description":"Click the wave effect","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339350","type":"PushEvent","actor":{"id":34973707,"login":"hyossid","display_login":"hyossid","gravatar_id":"","url":"https://api.github.com/users/hyossid","avatar_url":"https://avatars.githubusercontent.com/u/34973707?"},"repo":{"id":376212449,"name":"alexgo-io/alex-v1","url":"https://api.github.com/repos/alexgo-io/alex-v1"},"payload":{"push_id":7562451024,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"48da21038bec12d9f76d00f4cb3dafa2a697b453","before":"43e01f989430ca4a7b401cecc2d32833424e03c0","commits":[{"sha":"48da21038bec12d9f76d00f4cb3dafa2a697b453","author":{"name":"hyossid","email":"e2b25fcbfaa66f84d32bf6e2e1b3979fefe37d0c@gmail.com"},"message":"clear vault","distinct":true,"url":"https://api.github.com/repos/alexgo-io/alex-v1/commits/48da21038bec12d9f76d00f4cb3dafa2a697b453"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":85433080,"login":"alexgo-io","gravatar_id":"","url":"https://api.github.com/orgs/alexgo-io","avatar_url":"https://avatars.githubusercontent.com/u/85433080?"}} +{"id":"17246339352","type":"PushEvent","actor":{"id":83697333,"login":"a-Cash-dixit","display_login":"a-Cash-dixit","gravatar_id":"","url":"https://api.github.com/users/a-Cash-dixit","avatar_url":"https://avatars.githubusercontent.com/u/83697333?"},"repo":{"id":388016859,"name":"a-Cash-dixit/Story","url":"https://api.github.com/repos/a-Cash-dixit/Story"},"payload":{"push_id":7562451043,"size":3,"distinct_size":3,"ref":"refs/heads/master","head":"ccae12e9b1cf21f22d3471c3a1703e9961a70a45","before":"1f4a437d0421a397264b8256d5665b9e2b072985","commits":[{"sha":"fc03062f1b50501074e6633a934d6988adeb814b","author":{"name":"Akash Dixit","email":"2e65d41939fafbacaf69a0b0e485b44ce28a2b11@gmail.com"},"message":"modify chapter 1 and 2 to have alien theme","distinct":true,"url":"https://api.github.com/repos/a-Cash-dixit/Story/commits/fc03062f1b50501074e6633a934d6988adeb814b"},{"sha":"5b795e1ea0a86e49dd5a99734a4383dc393a7eeb","author":{"name":"Akash Dixit","email":"2e65d41939fafbacaf69a0b0e485b44ce28a2b11@gmail.com"},"message":"add chapter4","distinct":true,"url":"https://api.github.com/repos/a-Cash-dixit/Story/commits/5b795e1ea0a86e49dd5a99734a4383dc393a7eeb"},{"sha":"ccae12e9b1cf21f22d3471c3a1703e9961a70a45","author":{"name":"Akash Dixit","email":"2e65d41939fafbacaf69a0b0e485b44ce28a2b11@gmail.com"},"message":"Merge branch 'alien-plot'","distinct":true,"url":"https://api.github.com/repos/a-Cash-dixit/Story/commits/ccae12e9b1cf21f22d3471c3a1703e9961a70a45"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339356","type":"PushEvent","actor":{"id":54520846,"login":"holloer","display_login":"holloer","gravatar_id":"","url":"https://api.github.com/users/holloer","avatar_url":"https://avatars.githubusercontent.com/u/54520846?"},"repo":{"id":387765978,"name":"holloer/html-css-js-","url":"https://api.github.com/repos/holloer/html-css-js-"},"payload":{"push_id":7562451027,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"99089a2c8ebeb002ea94335f441b74bb0415c53c","before":"fe9081e81fccb17d5b4fd8d437ae14b9eeaa6750","commits":[{"sha":"99089a2c8ebeb002ea94335f441b74bb0415c53c","author":{"name":"将也ka","email":"7fa7ff4dc8b93edba9197cd4c4524ddbfc1d0d05@qq.com"},"message":"提交测试","distinct":true,"url":"https://api.github.com/repos/holloer/html-css-js-/commits/99089a2c8ebeb002ea94335f441b74bb0415c53c"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339360","type":"IssueCommentEvent","actor":{"id":5970879,"login":"Mathadon","display_login":"Mathadon","gravatar_id":"","url":"https://api.github.com/users/Mathadon","avatar_url":"https://avatars.githubusercontent.com/u/5970879?"},"repo":{"id":16949064,"name":"open-ideas/IDEAS","url":"https://api.github.com/repos/open-ideas/IDEAS"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/open-ideas/IDEAS/issues/1225","repository_url":"https://api.github.com/repos/open-ideas/IDEAS","labels_url":"https://api.github.com/repos/open-ideas/IDEAS/issues/1225/labels{/name}","comments_url":"https://api.github.com/repos/open-ideas/IDEAS/issues/1225/comments","events_url":"https://api.github.com/repos/open-ideas/IDEAS/issues/1225/events","html_url":"https://github.com/open-ideas/IDEAS/issues/1225","id":949444367,"node_id":"MDU6SXNzdWU5NDk0NDQzNjc=","number":1225,"title":"Error in AHU example model simulation in Open Modelica ","user":{"login":"Kanchn12345","id":87692105,"node_id":"MDQ6VXNlcjg3NjkyMTA1","avatar_url":"https://avatars.githubusercontent.com/u/87692105?v=4","gravatar_id":"","url":"https://api.github.com/users/Kanchn12345","html_url":"https://github.com/Kanchn12345","followers_url":"https://api.github.com/users/Kanchn12345/followers","following_url":"https://api.github.com/users/Kanchn12345/following{/other_user}","gists_url":"https://api.github.com/users/Kanchn12345/gists{/gist_id}","starred_url":"https://api.github.com/users/Kanchn12345/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kanchn12345/subscriptions","organizations_url":"https://api.github.com/users/Kanchn12345/orgs","repos_url":"https://api.github.com/users/Kanchn12345/repos","events_url":"https://api.github.com/users/Kanchn12345/events{/privacy}","received_events_url":"https://api.github.com/users/Kanchn12345/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2021-07-21T08:10:22Z","updated_at":"2021-07-21T09:00:00Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"As discussed on the issue for Openmodelica with #7705, I am creating an issue here. \r\n\r\nI am trying to simulate the AHU example model (Adsolair58) available in IDEAS.AIRFLOW.AHU package of the IDEAS library. When I am trying to simulate the example it is giving the following errors “Illegal access of protected element XiSat.” and “Variable wetBulOut.XiSat not found in scope IndirectEvaporativeHex.”\r\n\r\n@Mathadon , kindly look into it and help me resolve the issue.\r\n\r\nThanks","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/open-ideas/IDEAS/issues/comments/884018688","html_url":"https://github.com/open-ideas/IDEAS/issues/1225#issuecomment-884018688","issue_url":"https://api.github.com/repos/open-ideas/IDEAS/issues/1225","id":884018688,"node_id":"IC_kwDOAQKfSM40sQ4A","user":{"login":"Mathadon","id":5970879,"node_id":"MDQ6VXNlcjU5NzA4Nzk=","avatar_url":"https://avatars.githubusercontent.com/u/5970879?v=4","gravatar_id":"","url":"https://api.github.com/users/Mathadon","html_url":"https://github.com/Mathadon","followers_url":"https://api.github.com/users/Mathadon/followers","following_url":"https://api.github.com/users/Mathadon/following{/other_user}","gists_url":"https://api.github.com/users/Mathadon/gists{/gist_id}","starred_url":"https://api.github.com/users/Mathadon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Mathadon/subscriptions","organizations_url":"https://api.github.com/users/Mathadon/orgs","repos_url":"https://api.github.com/users/Mathadon/repos","events_url":"https://api.github.com/users/Mathadon/events{/privacy}","received_events_url":"https://api.github.com/users/Mathadon/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:00Z","updated_at":"2021-07-21T09:00:00Z","author_association":"MEMBER","body":"@Kanchn12345 @YashwanthVellala can you try again?","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":5467967,"login":"open-ideas","gravatar_id":"","url":"https://api.github.com/orgs/open-ideas","avatar_url":"https://avatars.githubusercontent.com/u/5467967?"}} +{"id":"17246339364","type":"PushEvent","actor":{"id":58665444,"login":"sandy1709","display_login":"sandy1709","gravatar_id":"","url":"https://api.github.com/users/sandy1709","avatar_url":"https://avatars.githubusercontent.com/u/58665444?"},"repo":{"id":256730612,"name":"sandy1709/catuserbot","url":"https://api.github.com/repos/sandy1709/catuserbot"},"payload":{"push_id":7562451052,"size":1,"distinct_size":1,"ref":"refs/heads/bugs","head":"37665ef04e5ffa8a0133596223fdecd19419c584","before":"10796270a5f038d403c68b67dfa67e24bcecfecd","commits":[{"sha":"37665ef04e5ffa8a0133596223fdecd19419c584","author":{"name":"sandeep.n","email":"50e816377cccb5e63b5f11b5b87a1649ec7e7c43@users.noreply.github.com"},"message":"Update ytdl.py","distinct":true,"url":"https://api.github.com/repos/sandy1709/catuserbot/commits/37665ef04e5ffa8a0133596223fdecd19419c584"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339367","type":"ForkEvent","actor":{"id":44321653,"login":"fengza","display_login":"fengza","gravatar_id":"","url":"https://api.github.com/users/fengza","avatar_url":"https://avatars.githubusercontent.com/u/44321653?"},"repo":{"id":181076419,"name":"JiancongWang/dummy","url":"https://api.github.com/repos/JiancongWang/dummy"},"payload":{"forkee":{"id":388056959,"node_id":"MDEwOlJlcG9zaXRvcnkzODgwNTY5NTk=","name":"dummy","full_name":"fengza/dummy","private":false,"owner":{"login":"fengza","id":44321653,"node_id":"MDQ6VXNlcjQ0MzIxNjUz","avatar_url":"https://avatars.githubusercontent.com/u/44321653?v=4","gravatar_id":"","url":"https://api.github.com/users/fengza","html_url":"https://github.com/fengza","followers_url":"https://api.github.com/users/fengza/followers","following_url":"https://api.github.com/users/fengza/following{/other_user}","gists_url":"https://api.github.com/users/fengza/gists{/gist_id}","starred_url":"https://api.github.com/users/fengza/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fengza/subscriptions","organizations_url":"https://api.github.com/users/fengza/orgs","repos_url":"https://api.github.com/users/fengza/repos","events_url":"https://api.github.com/users/fengza/events{/privacy}","received_events_url":"https://api.github.com/users/fengza/received_events","type":"User","site_admin":false},"html_url":"https://github.com/fengza/dummy","description":null,"fork":true,"url":"https://api.github.com/repos/fengza/dummy","forks_url":"https://api.github.com/repos/fengza/dummy/forks","keys_url":"https://api.github.com/repos/fengza/dummy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fengza/dummy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fengza/dummy/teams","hooks_url":"https://api.github.com/repos/fengza/dummy/hooks","issue_events_url":"https://api.github.com/repos/fengza/dummy/issues/events{/number}","events_url":"https://api.github.com/repos/fengza/dummy/events","assignees_url":"https://api.github.com/repos/fengza/dummy/assignees{/user}","branches_url":"https://api.github.com/repos/fengza/dummy/branches{/branch}","tags_url":"https://api.github.com/repos/fengza/dummy/tags","blobs_url":"https://api.github.com/repos/fengza/dummy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fengza/dummy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fengza/dummy/git/refs{/sha}","trees_url":"https://api.github.com/repos/fengza/dummy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fengza/dummy/statuses/{sha}","languages_url":"https://api.github.com/repos/fengza/dummy/languages","stargazers_url":"https://api.github.com/repos/fengza/dummy/stargazers","contributors_url":"https://api.github.com/repos/fengza/dummy/contributors","subscribers_url":"https://api.github.com/repos/fengza/dummy/subscribers","subscription_url":"https://api.github.com/repos/fengza/dummy/subscription","commits_url":"https://api.github.com/repos/fengza/dummy/commits{/sha}","git_commits_url":"https://api.github.com/repos/fengza/dummy/git/commits{/sha}","comments_url":"https://api.github.com/repos/fengza/dummy/comments{/number}","issue_comment_url":"https://api.github.com/repos/fengza/dummy/issues/comments{/number}","contents_url":"https://api.github.com/repos/fengza/dummy/contents/{+path}","compare_url":"https://api.github.com/repos/fengza/dummy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fengza/dummy/merges","archive_url":"https://api.github.com/repos/fengza/dummy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fengza/dummy/downloads","issues_url":"https://api.github.com/repos/fengza/dummy/issues{/number}","pulls_url":"https://api.github.com/repos/fengza/dummy/pulls{/number}","milestones_url":"https://api.github.com/repos/fengza/dummy/milestones{/number}","notifications_url":"https://api.github.com/repos/fengza/dummy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fengza/dummy/labels{/name}","releases_url":"https://api.github.com/repos/fengza/dummy/releases{/id}","deployments_url":"https://api.github.com/repos/fengza/dummy/deployments","created_at":"2021-07-21T09:00:00Z","updated_at":"2019-04-12T20:07:32Z","pushed_at":"2019-04-12T20:07:31Z","git_url":"git://github.com/fengza/dummy.git","ssh_url":"git@github.com:fengza/dummy.git","clone_url":"https://github.com/fengza/dummy.git","svn_url":"https://github.com/fengza/dummy","homepage":null,"size":5,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"main","public":true}},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339369","type":"PushEvent","actor":{"id":69119241,"login":"mohammadTaghiElmi","display_login":"mohammadTaghiElmi","gravatar_id":"","url":"https://api.github.com/users/mohammadTaghiElmi","avatar_url":"https://avatars.githubusercontent.com/u/69119241?"},"repo":{"id":76907276,"name":"oceanwp/oceanwp","url":"https://api.github.com/repos/oceanwp/oceanwp"},"payload":{"push_id":7562451039,"size":1,"distinct_size":1,"ref":"refs/heads/vanilla-js-fs","head":"bc4c6c00fe8efbe4cf6525a143d3a41c5f10574d","before":"78ac5a187db58ad6529bbe18987f36582f9e6fbf","commits":[{"sha":"bc4c6c00fe8efbe4cf6525a143d3a41c5f10574d","author":{"name":"Mohammad Taghi Elmi","email":"189a65585692af893470c7494471e2315218bcb3@gmail.com"},"message":"Compatible with WP 5.8.0","distinct":true,"url":"https://api.github.com/repos/oceanwp/oceanwp/commits/bc4c6c00fe8efbe4cf6525a143d3a41c5f10574d"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":54744968,"login":"oceanwp","gravatar_id":"","url":"https://api.github.com/orgs/oceanwp","avatar_url":"https://avatars.githubusercontent.com/u/54744968?"}} +{"id":"17246339373","type":"CreateEvent","actor":{"id":11707729,"login":"gabrieldonadel","display_login":"gabrieldonadel","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","avatar_url":"https://avatars.githubusercontent.com/u/11707729?"},"repo":{"id":387916158,"name":"gabrieldonadel/pull-requests-limits","url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits"},"payload":{"ref":"5555","ref_type":"branch","master_branch":"master","description":"Testing Github Pull requests limits","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339375","type":"PushEvent","actor":{"id":67594991,"login":"makidai1212","display_login":"makidai1212","gravatar_id":"","url":"https://api.github.com/users/makidai1212","avatar_url":"https://avatars.githubusercontent.com/u/67594991?"},"repo":{"id":386547022,"name":"makidai1212/kenkou","url":"https://api.github.com/repos/makidai1212/kenkou"},"payload":{"push_id":7562451063,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"5903e2dcacaf927dc880224f1b5adda318626126","before":"65b8ae8f4505e259c08a00d2043d80b273089c26","commits":[{"sha":"5903e2dcacaf927dc880224f1b5adda318626126","author":{"name":"makidai","email":"506f96d91d55111ac48729c3ec0e64734b0e6776@yahoo.co.jp"},"message":"uwagakishitai","distinct":true,"url":"https://api.github.com/repos/makidai1212/kenkou/commits/5903e2dcacaf927dc880224f1b5adda318626126"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339378","type":"PushEvent","actor":{"id":81258380,"login":"Helikopter-Bojowy","display_login":"Helikopter-Bojowy","gravatar_id":"","url":"https://api.github.com/users/Helikopter-Bojowy","avatar_url":"https://avatars.githubusercontent.com/u/81258380?"},"repo":{"id":352069365,"name":"Helikopter-Bojowy/Exp-na-helikopterze","url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze"},"payload":{"push_id":7562451058,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"0bfd0a1c279b39653a869a9c26a60e478c0aef34","before":"47d3d6dd2d206a05c36104f502f202465a7b0a3b","commits":[{"sha":"0bfd0a1c279b39653a869a9c26a60e478c0aef34","author":{"name":"Helikopter-Bojowy","email":"733707e04c8bf7c874ce97516f8f637586fc79d7@users.noreply.github.com"},"message":"1626857999689:419059893383856140","distinct":true,"url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze/commits/0bfd0a1c279b39653a869a9c26a60e478c0aef34"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339379","type":"PushEvent","actor":{"id":76887010,"login":"KoteTheInnkeeper","display_login":"KoteTheInnkeeper","gravatar_id":"","url":"https://api.github.com/users/KoteTheInnkeeper","avatar_url":"https://avatars.githubusercontent.com/u/76887010?"},"repo":{"id":384450476,"name":"KoteTheInnkeeper/fiados_qt","url":"https://api.github.com/repos/KoteTheInnkeeper/fiados_qt"},"payload":{"push_id":7562451061,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"b1eb3463849b2b37d23974e6b0466c5b8a91403f","before":"43fbc87863c4bb39d462bd06408dc3fda057c07d","commits":[{"sha":"b1eb3463849b2b37d23974e6b0466c5b8a91403f","author":{"name":"KoteTheInnkeeper","email":"356b38822cc26315cd5c80f5c36b65b26337c391@gmail.com"},"message":"Updated .gitignore to ignore pyisntaller files.","distinct":true,"url":"https://api.github.com/repos/KoteTheInnkeeper/fiados_qt/commits/b1eb3463849b2b37d23974e6b0466c5b8a91403f"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339393","type":"IssuesEvent","actor":{"id":17969015,"login":"oguzdemirbasci","display_login":"oguzdemirbasci","gravatar_id":"","url":"https://api.github.com/users/oguzdemirbasci","avatar_url":"https://avatars.githubusercontent.com/u/17969015?"},"repo":{"id":161622906,"name":"inspirehep/inspirehep","url":"https://api.github.com/repos/inspirehep/inspirehep"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/inspirehep/inspirehep/issues/2058","repository_url":"https://api.github.com/repos/inspirehep/inspirehep","labels_url":"https://api.github.com/repos/inspirehep/inspirehep/issues/2058/labels{/name}","comments_url":"https://api.github.com/repos/inspirehep/inspirehep/issues/2058/comments","events_url":"https://api.github.com/repos/inspirehep/inspirehep/issues/2058/events","html_url":"https://github.com/inspirehep/inspirehep/issues/2058","id":949486827,"node_id":"MDU6SXNzdWU5NDk0ODY4Mjc=","number":2058,"title":"DESY parser does not strip auther id value","user":{"login":"oguzdemirbasci","id":17969015,"node_id":"MDQ6VXNlcjE3OTY5MDE1","avatar_url":"https://avatars.githubusercontent.com/u/17969015?v=4","gravatar_id":"","url":"https://api.github.com/users/oguzdemirbasci","html_url":"https://github.com/oguzdemirbasci","followers_url":"https://api.github.com/users/oguzdemirbasci/followers","following_url":"https://api.github.com/users/oguzdemirbasci/following{/other_user}","gists_url":"https://api.github.com/users/oguzdemirbasci/gists{/gist_id}","starred_url":"https://api.github.com/users/oguzdemirbasci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oguzdemirbasci/subscriptions","organizations_url":"https://api.github.com/users/oguzdemirbasci/orgs","repos_url":"https://api.github.com/users/oguzdemirbasci/repos","events_url":"https://api.github.com/users/oguzdemirbasci/events{/privacy}","received_events_url":"https://api.github.com/users/oguzdemirbasci/received_events","type":"User","site_admin":false},"labels":[{"id":2155634872,"node_id":"MDU6TGFiZWwyMTU1NjM0ODcy","url":"https://api.github.com/repos/inspirehep/inspirehep/labels/status:%20new","name":"status: new","color":"f237c6","default":false,"description":""},{"id":1160200155,"node_id":"MDU6TGFiZWwxMTYwMjAwMTU1","url":"https://api.github.com/repos/inspirehep/inspirehep/labels/type:%20bug","name":"type: bug","color":"d73a4a","default":false,"description":"Something isn't working"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T09:00:00Z","updated_at":"2021-07-21T09:00:00Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"body":"For DESY workflows, author ORCID ids are incorrect. \r\n\r\n`https://orcidorg/0000-0001-7325-2274` should be `0000-0001-7325-2274`.\r\n\r\nhttps://inspirehep.net/holdingpen/3174764\r\n\r\n```\r\nValidationError: {u'value': u'https://orcidorg/0000-0001-7325-2274', u'schema': u'ORCID'} is not valid under any of the given schemas\r\n```\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":356664,"login":"inspirehep","gravatar_id":"","url":"https://api.github.com/orgs/inspirehep","avatar_url":"https://avatars.githubusercontent.com/u/356664?"}} +{"id":"17246339396","type":"PushEvent","actor":{"id":87330551,"login":"xz241","display_login":"xz241","gravatar_id":"","url":"https://api.github.com/users/xz241","avatar_url":"https://avatars.githubusercontent.com/u/87330551?"},"repo":{"id":385328687,"name":"xz241/wp-uploads","url":"https://api.github.com/repos/xz241/wp-uploads"},"payload":{"push_id":7562451065,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"ce7556e4c681ba0ee5334ee87ffa9cfd612a7916","before":"d065823c96427d420d2ac4c8c03990ff2c14ffd4","commits":[{"sha":"ce7556e4c681ba0ee5334ee87ffa9cfd612a7916","author":{"name":"xz241","email":"e17353502d0344fb9b4d10b6a471cc23c11850ec@users.noreply.github.com"},"message":"","distinct":true,"url":"https://api.github.com/repos/xz241/wp-uploads/commits/ce7556e4c681ba0ee5334ee87ffa9cfd612a7916"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339408","type":"GollumEvent","actor":{"id":63827197,"login":"Frank-cc0426","display_login":"Frank-cc0426","gravatar_id":"","url":"https://api.github.com/users/Frank-cc0426","avatar_url":"https://avatars.githubusercontent.com/u/63827197?"},"repo":{"id":388000013,"name":"Frank-cc0426/cc_leetcode","url":"https://api.github.com/repos/Frank-cc0426/cc_leetcode"},"payload":{"pages":[{"page_name":"Wiki-grammar","title":"Wiki grammar","summary":null,"action":"edited","sha":"0b55912abfcb5a6dffdf7fb20a922eb5feba670e","html_url":"https://github.com/Frank-cc0426/cc_leetcode/wiki/Wiki-grammar"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339412","type":"PushEvent","actor":{"id":2041960,"login":"pofider","display_login":"pofider","gravatar_id":"","url":"https://api.github.com/users/pofider","avatar_url":"https://avatars.githubusercontent.com/u/2041960?"},"repo":{"id":304702492,"name":"jsreport/new-arch","url":"https://api.github.com/repos/jsreport/new-arch"},"payload":{"push_id":7562451059,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"667db2b2aae2eae645ef97859fea5d39e91927ba","before":"a62dbb7f21f9fd509002d8ec576aa4be66b32de3","commits":[{"sha":"667db2b2aae2eae645ef97859fea5d39e91927ba","author":{"name":"pofider","email":"94482ae862d6b703916f4cc2b746d969fcde1a63@seznam.cz"},"message":"fix errors when asset seems text but we fail to decode it","distinct":true,"url":"https://api.github.com/repos/jsreport/new-arch/commits/667db2b2aae2eae645ef97859fea5d39e91927ba"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":3989182,"login":"jsreport","gravatar_id":"","url":"https://api.github.com/orgs/jsreport","avatar_url":"https://avatars.githubusercontent.com/u/3989182?"}} +{"id":"17246339414","type":"WatchEvent","actor":{"id":76192995,"login":"Y-yb","display_login":"Y-yb","gravatar_id":"","url":"https://api.github.com/users/Y-yb","avatar_url":"https://avatars.githubusercontent.com/u/76192995?"},"repo":{"id":177736533,"name":"996icu/996.ICU","url":"https://api.github.com/repos/996icu/996.ICU"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339416","type":"PullRequestEvent","actor":{"id":46416545,"login":"helmuth12081999","display_login":"helmuth12081999","gravatar_id":"","url":"https://api.github.com/users/helmuth12081999","avatar_url":"https://avatars.githubusercontent.com/u/46416545?"},"repo":{"id":330831044,"name":"jansennn/Plan-it-BE","url":"https://api.github.com/repos/jansennn/Plan-it-BE"},"payload":{"action":"opened","number":22,"pull_request":{"url":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/22","id":694185702,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzAy","html_url":"https://github.com/jansennn/Plan-it-BE/pull/22","diff_url":"https://github.com/jansennn/Plan-it-BE/pull/22.diff","patch_url":"https://github.com/jansennn/Plan-it-BE/pull/22.patch","issue_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/22","number":22,"state":"open","locked":false,"title":"async rute","user":{"login":"helmuth12081999","id":46416545,"node_id":"MDQ6VXNlcjQ2NDE2NTQ1","avatar_url":"https://avatars.githubusercontent.com/u/46416545?v=4","gravatar_id":"","url":"https://api.github.com/users/helmuth12081999","html_url":"https://github.com/helmuth12081999","followers_url":"https://api.github.com/users/helmuth12081999/followers","following_url":"https://api.github.com/users/helmuth12081999/following{/other_user}","gists_url":"https://api.github.com/users/helmuth12081999/gists{/gist_id}","starred_url":"https://api.github.com/users/helmuth12081999/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/helmuth12081999/subscriptions","organizations_url":"https://api.github.com/users/helmuth12081999/orgs","repos_url":"https://api.github.com/users/helmuth12081999/repos","events_url":"https://api.github.com/users/helmuth12081999/events{/privacy}","received_events_url":"https://api.github.com/users/helmuth12081999/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T09:00:00Z","updated_at":"2021-07-21T09:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/22/commits","review_comments_url":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/22/comments","review_comment_url":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/22/comments","statuses_url":"https://api.github.com/repos/jansennn/Plan-it-BE/statuses/47ee42ed38e37c3382af72a399c0ff8532493494","head":{"label":"jansennn:helmuth/async-rute","ref":"helmuth/async-rute","sha":"47ee42ed38e37c3382af72a399c0ff8532493494","user":{"login":"jansennn","id":38531347,"node_id":"MDQ6VXNlcjM4NTMxMzQ3","avatar_url":"https://avatars.githubusercontent.com/u/38531347?v=4","gravatar_id":"","url":"https://api.github.com/users/jansennn","html_url":"https://github.com/jansennn","followers_url":"https://api.github.com/users/jansennn/followers","following_url":"https://api.github.com/users/jansennn/following{/other_user}","gists_url":"https://api.github.com/users/jansennn/gists{/gist_id}","starred_url":"https://api.github.com/users/jansennn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jansennn/subscriptions","organizations_url":"https://api.github.com/users/jansennn/orgs","repos_url":"https://api.github.com/users/jansennn/repos","events_url":"https://api.github.com/users/jansennn/events{/privacy}","received_events_url":"https://api.github.com/users/jansennn/received_events","type":"User","site_admin":false},"repo":{"id":330831044,"node_id":"MDEwOlJlcG9zaXRvcnkzMzA4MzEwNDQ=","name":"Plan-it-BE","full_name":"jansennn/Plan-it-BE","private":false,"owner":{"login":"jansennn","id":38531347,"node_id":"MDQ6VXNlcjM4NTMxMzQ3","avatar_url":"https://avatars.githubusercontent.com/u/38531347?v=4","gravatar_id":"","url":"https://api.github.com/users/jansennn","html_url":"https://github.com/jansennn","followers_url":"https://api.github.com/users/jansennn/followers","following_url":"https://api.github.com/users/jansennn/following{/other_user}","gists_url":"https://api.github.com/users/jansennn/gists{/gist_id}","starred_url":"https://api.github.com/users/jansennn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jansennn/subscriptions","organizations_url":"https://api.github.com/users/jansennn/orgs","repos_url":"https://api.github.com/users/jansennn/repos","events_url":"https://api.github.com/users/jansennn/events{/privacy}","received_events_url":"https://api.github.com/users/jansennn/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jansennn/Plan-it-BE","description":"Tugas Akhir","fork":false,"url":"https://api.github.com/repos/jansennn/Plan-it-BE","forks_url":"https://api.github.com/repos/jansennn/Plan-it-BE/forks","keys_url":"https://api.github.com/repos/jansennn/Plan-it-BE/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jansennn/Plan-it-BE/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jansennn/Plan-it-BE/teams","hooks_url":"https://api.github.com/repos/jansennn/Plan-it-BE/hooks","issue_events_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/events{/number}","events_url":"https://api.github.com/repos/jansennn/Plan-it-BE/events","assignees_url":"https://api.github.com/repos/jansennn/Plan-it-BE/assignees{/user}","branches_url":"https://api.github.com/repos/jansennn/Plan-it-BE/branches{/branch}","tags_url":"https://api.github.com/repos/jansennn/Plan-it-BE/tags","blobs_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/refs{/sha}","trees_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jansennn/Plan-it-BE/statuses/{sha}","languages_url":"https://api.github.com/repos/jansennn/Plan-it-BE/languages","stargazers_url":"https://api.github.com/repos/jansennn/Plan-it-BE/stargazers","contributors_url":"https://api.github.com/repos/jansennn/Plan-it-BE/contributors","subscribers_url":"https://api.github.com/repos/jansennn/Plan-it-BE/subscribers","subscription_url":"https://api.github.com/repos/jansennn/Plan-it-BE/subscription","commits_url":"https://api.github.com/repos/jansennn/Plan-it-BE/commits{/sha}","git_commits_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/commits{/sha}","comments_url":"https://api.github.com/repos/jansennn/Plan-it-BE/comments{/number}","issue_comment_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/comments{/number}","contents_url":"https://api.github.com/repos/jansennn/Plan-it-BE/contents/{+path}","compare_url":"https://api.github.com/repos/jansennn/Plan-it-BE/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jansennn/Plan-it-BE/merges","archive_url":"https://api.github.com/repos/jansennn/Plan-it-BE/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jansennn/Plan-it-BE/downloads","issues_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues{/number}","pulls_url":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls{/number}","milestones_url":"https://api.github.com/repos/jansennn/Plan-it-BE/milestones{/number}","notifications_url":"https://api.github.com/repos/jansennn/Plan-it-BE/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jansennn/Plan-it-BE/labels{/name}","releases_url":"https://api.github.com/repos/jansennn/Plan-it-BE/releases{/id}","deployments_url":"https://api.github.com/repos/jansennn/Plan-it-BE/deployments","created_at":"2021-01-19T01:32:14Z","updated_at":"2021-07-17T12:57:48Z","pushed_at":"2021-07-21T08:57:08Z","git_url":"git://github.com/jansennn/Plan-it-BE.git","ssh_url":"git@github.com:jansennn/Plan-it-BE.git","clone_url":"https://github.com/jansennn/Plan-it-BE.git","svn_url":"https://github.com/jansennn/Plan-it-BE","homepage":null,"size":9052,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"jansennn:master","ref":"master","sha":"77f1ea70526d4218efba6bcb111ace91d2461895","user":{"login":"jansennn","id":38531347,"node_id":"MDQ6VXNlcjM4NTMxMzQ3","avatar_url":"https://avatars.githubusercontent.com/u/38531347?v=4","gravatar_id":"","url":"https://api.github.com/users/jansennn","html_url":"https://github.com/jansennn","followers_url":"https://api.github.com/users/jansennn/followers","following_url":"https://api.github.com/users/jansennn/following{/other_user}","gists_url":"https://api.github.com/users/jansennn/gists{/gist_id}","starred_url":"https://api.github.com/users/jansennn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jansennn/subscriptions","organizations_url":"https://api.github.com/users/jansennn/orgs","repos_url":"https://api.github.com/users/jansennn/repos","events_url":"https://api.github.com/users/jansennn/events{/privacy}","received_events_url":"https://api.github.com/users/jansennn/received_events","type":"User","site_admin":false},"repo":{"id":330831044,"node_id":"MDEwOlJlcG9zaXRvcnkzMzA4MzEwNDQ=","name":"Plan-it-BE","full_name":"jansennn/Plan-it-BE","private":false,"owner":{"login":"jansennn","id":38531347,"node_id":"MDQ6VXNlcjM4NTMxMzQ3","avatar_url":"https://avatars.githubusercontent.com/u/38531347?v=4","gravatar_id":"","url":"https://api.github.com/users/jansennn","html_url":"https://github.com/jansennn","followers_url":"https://api.github.com/users/jansennn/followers","following_url":"https://api.github.com/users/jansennn/following{/other_user}","gists_url":"https://api.github.com/users/jansennn/gists{/gist_id}","starred_url":"https://api.github.com/users/jansennn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jansennn/subscriptions","organizations_url":"https://api.github.com/users/jansennn/orgs","repos_url":"https://api.github.com/users/jansennn/repos","events_url":"https://api.github.com/users/jansennn/events{/privacy}","received_events_url":"https://api.github.com/users/jansennn/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jansennn/Plan-it-BE","description":"Tugas Akhir","fork":false,"url":"https://api.github.com/repos/jansennn/Plan-it-BE","forks_url":"https://api.github.com/repos/jansennn/Plan-it-BE/forks","keys_url":"https://api.github.com/repos/jansennn/Plan-it-BE/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jansennn/Plan-it-BE/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jansennn/Plan-it-BE/teams","hooks_url":"https://api.github.com/repos/jansennn/Plan-it-BE/hooks","issue_events_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/events{/number}","events_url":"https://api.github.com/repos/jansennn/Plan-it-BE/events","assignees_url":"https://api.github.com/repos/jansennn/Plan-it-BE/assignees{/user}","branches_url":"https://api.github.com/repos/jansennn/Plan-it-BE/branches{/branch}","tags_url":"https://api.github.com/repos/jansennn/Plan-it-BE/tags","blobs_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/refs{/sha}","trees_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jansennn/Plan-it-BE/statuses/{sha}","languages_url":"https://api.github.com/repos/jansennn/Plan-it-BE/languages","stargazers_url":"https://api.github.com/repos/jansennn/Plan-it-BE/stargazers","contributors_url":"https://api.github.com/repos/jansennn/Plan-it-BE/contributors","subscribers_url":"https://api.github.com/repos/jansennn/Plan-it-BE/subscribers","subscription_url":"https://api.github.com/repos/jansennn/Plan-it-BE/subscription","commits_url":"https://api.github.com/repos/jansennn/Plan-it-BE/commits{/sha}","git_commits_url":"https://api.github.com/repos/jansennn/Plan-it-BE/git/commits{/sha}","comments_url":"https://api.github.com/repos/jansennn/Plan-it-BE/comments{/number}","issue_comment_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/comments{/number}","contents_url":"https://api.github.com/repos/jansennn/Plan-it-BE/contents/{+path}","compare_url":"https://api.github.com/repos/jansennn/Plan-it-BE/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jansennn/Plan-it-BE/merges","archive_url":"https://api.github.com/repos/jansennn/Plan-it-BE/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jansennn/Plan-it-BE/downloads","issues_url":"https://api.github.com/repos/jansennn/Plan-it-BE/issues{/number}","pulls_url":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls{/number}","milestones_url":"https://api.github.com/repos/jansennn/Plan-it-BE/milestones{/number}","notifications_url":"https://api.github.com/repos/jansennn/Plan-it-BE/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jansennn/Plan-it-BE/labels{/name}","releases_url":"https://api.github.com/repos/jansennn/Plan-it-BE/releases{/id}","deployments_url":"https://api.github.com/repos/jansennn/Plan-it-BE/deployments","created_at":"2021-01-19T01:32:14Z","updated_at":"2021-07-17T12:57:48Z","pushed_at":"2021-07-21T08:57:08Z","git_url":"git://github.com/jansennn/Plan-it-BE.git","ssh_url":"git@github.com:jansennn/Plan-it-BE.git","clone_url":"https://github.com/jansennn/Plan-it-BE.git","svn_url":"https://github.com/jansennn/Plan-it-BE","homepage":null,"size":9052,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/22"},"html":{"href":"https://github.com/jansennn/Plan-it-BE/pull/22"},"issue":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/22"},"comments":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/issues/22/comments"},"review_comments":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/22/comments"},"review_comment":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/pulls/22/commits"},"statuses":{"href":"https://api.github.com/repos/jansennn/Plan-it-BE/statuses/47ee42ed38e37c3382af72a399c0ff8532493494"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":914,"deletions":742,"changed_files":5}},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339424","type":"CreateEvent","actor":{"id":79913779,"login":"conda-forge-curator[bot]","display_login":"conda-forge-curator","gravatar_id":"","url":"https://api.github.com/users/conda-forge-curator[bot]","avatar_url":"https://avatars.githubusercontent.com/u/79913779?"},"repo":{"id":288431736,"name":"conda-forge/releases","url":"https://api.github.com/repos/conda-forge/releases"},"payload":{"ref":"win-64/astroid-2.6.5-py38haa244fe_0.tar.bz2","ref_type":"tag","master_branch":"master","description":"releases of conda-forge builds","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":11897326,"login":"conda-forge","gravatar_id":"","url":"https://api.github.com/orgs/conda-forge","avatar_url":"https://avatars.githubusercontent.com/u/11897326?"}} +{"id":"17246339427","type":"PushEvent","actor":{"id":615952,"login":"billybobza","display_login":"billybobza","gravatar_id":"","url":"https://api.github.com/users/billybobza","avatar_url":"https://avatars.githubusercontent.com/u/615952?"},"repo":{"id":325979131,"name":"newstools/2021-deutsche-welle-africa","url":"https://api.github.com/repos/newstools/2021-deutsche-welle-africa"},"payload":{"push_id":7562451076,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"dfca4b939881d4a1dac14fa038ab3ff7d0becb2c","before":"ef61bc73742941a3bfd390eecf3b3a74ad56f0d6","commits":[{"sha":"dfca4b939881d4a1dac14fa038ab3ff7d0becb2c","author":{"name":"Billy Einkamerer","email":"051522d0c46404d8ba5b692a10a37b99b8186360@assemble.co.za"},"message":"Created Text For URL [www.dw.com/en/climate-change-migration-how-merkels-successors-measure-up/a-58261469]","distinct":true,"url":"https://api.github.com/repos/newstools/2021-deutsche-welle-africa/commits/dfca4b939881d4a1dac14fa038ab3ff7d0becb2c"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":17193977,"login":"newstools","gravatar_id":"","url":"https://api.github.com/orgs/newstools","avatar_url":"https://avatars.githubusercontent.com/u/17193977?"}} +{"id":"17246339433","type":"PushEvent","actor":{"id":74347264,"login":"boost-e2e-tester-user","display_login":"boost-e2e-tester-user","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-tester-user","avatar_url":"https://avatars.githubusercontent.com/u/74347264?"},"repo":{"id":376948559,"name":"boost-e2e-stage-ci-buildkite/pr-comments","url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments"},"payload":{"push_id":7562451080,"size":1,"distinct_size":1,"ref":"refs/heads/pr-comment","head":"cbfe19073ba710838d05858cdce5a23bc3c57894","before":"8402da60c61dd3efa44f3424268d09bbf44d850c","commits":[{"sha":"cbfe19073ba710838d05858cdce5a23bc3c57894","author":{"name":"Boost E2E tester script","email":"6bedb45c7c0408832d60210d552723fdc15d851e@boostsecurity.io"},"message":"[ci skip] Test PR Comment","distinct":true,"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/commits/cbfe19073ba710838d05858cdce5a23bc3c57894"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":81248900,"login":"boost-e2e-stage-ci-buildkite","gravatar_id":"","url":"https://api.github.com/orgs/boost-e2e-stage-ci-buildkite","avatar_url":"https://avatars.githubusercontent.com/u/81248900?"}} +{"id":"17246339434","type":"PushEvent","actor":{"id":56023474,"login":"liulka-oxid","display_login":"liulka-oxid","gravatar_id":"","url":"https://api.github.com/users/liulka-oxid","avatar_url":"https://avatars.githubusercontent.com/u/56023474?"},"repo":{"id":45610331,"name":"OXID-eSales/developer_documentation","url":"https://api.github.com/repos/OXID-eSales/developer_documentation"},"payload":{"push_id":7562451075,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8961304f2bbdc712eb30b08bd8304c4ff042eca3","before":"70ff218a3aa206f305e180a91e3afa4599b5bfe6","commits":[{"sha":"8961304f2bbdc712eb30b08bd8304c4ff042eca3","author":{"name":"Vasyl Liulka","email":"8816c4cfb90e471b9a1568f5c9b62556f5e8017c@oxid-esales.com"},"message":"OXDEV-4739 - Remove assign_adv examples","distinct":true,"url":"https://api.github.com/repos/OXID-eSales/developer_documentation/commits/8961304f2bbdc712eb30b08bd8304c4ff042eca3"}]},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":3502610,"login":"OXID-eSales","gravatar_id":"","url":"https://api.github.com/orgs/OXID-eSales","avatar_url":"https://avatars.githubusercontent.com/u/3502610?"}} +{"id":"17246339455","type":"PushEvent","actor":{"id":87718722,"login":"alezfergusonn9054","display_login":"alezfergusonn9054","gravatar_id":"","url":"https://api.github.com/users/alezfergusonn9054","avatar_url":"https://avatars.githubusercontent.com/u/87718722?"},"repo":{"id":387866864,"name":"alezfergusonn9054/hfdbx4","url":"https://api.github.com/repos/alezfergusonn9054/hfdbx4"},"payload":{"push_id":7562451095,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"a79af7a3248fc813269b95e0a487d32c3b080c74","before":"07a6007f00815c2bb5b8793e3c8d20ed80984b2c","commits":[{"sha":"a79af7a3248fc813269b95e0a487d32c3b080c74","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/alezfergusonn9054/hfdbx4/commits/a79af7a3248fc813269b95e0a487d32c3b080c74"}]},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339461","type":"PullRequestEvent","actor":{"id":29139614,"login":"renovate[bot]","display_login":"renovate","gravatar_id":"","url":"https://api.github.com/users/renovate[bot]","avatar_url":"https://avatars.githubusercontent.com/u/29139614?"},"repo":{"id":315713528,"name":"Yfill/waves","url":"https://api.github.com/repos/Yfill/waves"},"payload":{"action":"opened","number":168,"pull_request":{"url":"https://api.github.com/repos/Yfill/waves/pulls/168","id":694185705,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzA1","html_url":"https://github.com/Yfill/waves/pull/168","diff_url":"https://github.com/Yfill/waves/pull/168.diff","patch_url":"https://github.com/Yfill/waves/pull/168.patch","issue_url":"https://api.github.com/repos/Yfill/waves/issues/168","number":168,"state":"open","locked":false,"title":"chore(deps): update dependency rollup to v2.53.3","user":{"login":"renovate[bot]","id":29139614,"node_id":"MDM6Qm90MjkxMzk2MTQ=","avatar_url":"https://avatars.githubusercontent.com/in/2740?v=4","gravatar_id":"","url":"https://api.github.com/users/renovate%5Bbot%5D","html_url":"https://github.com/apps/renovate","followers_url":"https://api.github.com/users/renovate%5Bbot%5D/followers","following_url":"https://api.github.com/users/renovate%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/renovate%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/renovate%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/renovate%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/renovate%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/renovate%5Bbot%5D/repos","events_url":"https://api.github.com/users/renovate%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/renovate%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)\n\nThis PR contains the following updates:\n\n| Package | Change | Age | Adoption | Passing | Confidence |\n|---|---|---|---|---|---|\n| [rollup](https://rollupjs.org/) ([source](https://togithub.com/rollup/rollup)) | [`2.53.2` -> `2.53.3`](https://renovatebot.com/diffs/npm/rollup/2.53.2/2.53.3) | [![age](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/compatibility-slim/2.53.2)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/npm/rollup/2.53.3/confidence-slim/2.53.2)](https://docs.renovatebot.com/merge-confidence/) |\n\n---\n\n### Release Notes\n\n
    \nrollup/rollup\n\n### [`v2.53.3`](https://togithub.com/rollup/rollup/blob/master/CHANGELOG.md#​2533)\n\n[Compare Source](https://togithub.com/rollup/rollup/compare/v2.53.2...v2.53.3)\n\n*2021-07-21*\n\n##### Bug Fixes\n\n- Solve an issue that could lead to severe memory issues and crashes when there are a lot of hoisted variables ([#​4183](https://togithub.com/rollup/rollup/issues/4183))\n\n##### Pull Requests\n\n- [#​4183](https://togithub.com/rollup/rollup/pull/4183): Avoid memory issues with hoisted variables ([@​lukastaegert](https://togithub.com/lukastaegert))\n\n
    \n\n---\n\n### Configuration\n\n📅 **Schedule**: At any time (no schedule defined).\n\n🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.\n\n♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.\n\n🔕 **Ignore**: Close this PR and you won't be reminded about this update again.\n\n---\n\n - [ ] If you want to rebase/retry this PR, check this box.\n\n---\n\nThis PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/Yfill/waves).","created_at":"2021-07-21T09:00:00Z","updated_at":"2021-07-21T09:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Yfill/waves/pulls/168/commits","review_comments_url":"https://api.github.com/repos/Yfill/waves/pulls/168/comments","review_comment_url":"https://api.github.com/repos/Yfill/waves/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Yfill/waves/issues/168/comments","statuses_url":"https://api.github.com/repos/Yfill/waves/statuses/6b24ceac101363876e2c89e7ecc65ad0137b06c3","head":{"label":"Yfill:renovate/rollup-2.x","ref":"renovate/rollup-2.x","sha":"6b24ceac101363876e2c89e7ecc65ad0137b06c3","user":{"login":"Yfill","id":33168885,"node_id":"MDQ6VXNlcjMzMTY4ODg1","avatar_url":"https://avatars.githubusercontent.com/u/33168885?v=4","gravatar_id":"","url":"https://api.github.com/users/Yfill","html_url":"https://github.com/Yfill","followers_url":"https://api.github.com/users/Yfill/followers","following_url":"https://api.github.com/users/Yfill/following{/other_user}","gists_url":"https://api.github.com/users/Yfill/gists{/gist_id}","starred_url":"https://api.github.com/users/Yfill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Yfill/subscriptions","organizations_url":"https://api.github.com/users/Yfill/orgs","repos_url":"https://api.github.com/users/Yfill/repos","events_url":"https://api.github.com/users/Yfill/events{/privacy}","received_events_url":"https://api.github.com/users/Yfill/received_events","type":"User","site_admin":false},"repo":{"id":315713528,"node_id":"MDEwOlJlcG9zaXRvcnkzMTU3MTM1Mjg=","name":"waves","full_name":"Yfill/waves","private":false,"owner":{"login":"Yfill","id":33168885,"node_id":"MDQ6VXNlcjMzMTY4ODg1","avatar_url":"https://avatars.githubusercontent.com/u/33168885?v=4","gravatar_id":"","url":"https://api.github.com/users/Yfill","html_url":"https://github.com/Yfill","followers_url":"https://api.github.com/users/Yfill/followers","following_url":"https://api.github.com/users/Yfill/following{/other_user}","gists_url":"https://api.github.com/users/Yfill/gists{/gist_id}","starred_url":"https://api.github.com/users/Yfill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Yfill/subscriptions","organizations_url":"https://api.github.com/users/Yfill/orgs","repos_url":"https://api.github.com/users/Yfill/repos","events_url":"https://api.github.com/users/Yfill/events{/privacy}","received_events_url":"https://api.github.com/users/Yfill/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Yfill/waves","description":"Click the wave effect","fork":false,"url":"https://api.github.com/repos/Yfill/waves","forks_url":"https://api.github.com/repos/Yfill/waves/forks","keys_url":"https://api.github.com/repos/Yfill/waves/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Yfill/waves/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Yfill/waves/teams","hooks_url":"https://api.github.com/repos/Yfill/waves/hooks","issue_events_url":"https://api.github.com/repos/Yfill/waves/issues/events{/number}","events_url":"https://api.github.com/repos/Yfill/waves/events","assignees_url":"https://api.github.com/repos/Yfill/waves/assignees{/user}","branches_url":"https://api.github.com/repos/Yfill/waves/branches{/branch}","tags_url":"https://api.github.com/repos/Yfill/waves/tags","blobs_url":"https://api.github.com/repos/Yfill/waves/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Yfill/waves/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Yfill/waves/git/refs{/sha}","trees_url":"https://api.github.com/repos/Yfill/waves/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Yfill/waves/statuses/{sha}","languages_url":"https://api.github.com/repos/Yfill/waves/languages","stargazers_url":"https://api.github.com/repos/Yfill/waves/stargazers","contributors_url":"https://api.github.com/repos/Yfill/waves/contributors","subscribers_url":"https://api.github.com/repos/Yfill/waves/subscribers","subscription_url":"https://api.github.com/repos/Yfill/waves/subscription","commits_url":"https://api.github.com/repos/Yfill/waves/commits{/sha}","git_commits_url":"https://api.github.com/repos/Yfill/waves/git/commits{/sha}","comments_url":"https://api.github.com/repos/Yfill/waves/comments{/number}","issue_comment_url":"https://api.github.com/repos/Yfill/waves/issues/comments{/number}","contents_url":"https://api.github.com/repos/Yfill/waves/contents/{+path}","compare_url":"https://api.github.com/repos/Yfill/waves/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Yfill/waves/merges","archive_url":"https://api.github.com/repos/Yfill/waves/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Yfill/waves/downloads","issues_url":"https://api.github.com/repos/Yfill/waves/issues{/number}","pulls_url":"https://api.github.com/repos/Yfill/waves/pulls{/number}","milestones_url":"https://api.github.com/repos/Yfill/waves/milestones{/number}","notifications_url":"https://api.github.com/repos/Yfill/waves/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Yfill/waves/labels{/name}","releases_url":"https://api.github.com/repos/Yfill/waves/releases{/id}","deployments_url":"https://api.github.com/repos/Yfill/waves/deployments","created_at":"2020-11-24T18:01:57Z","updated_at":"2021-07-20T02:47:37Z","pushed_at":"2021-07-21T08:59:59Z","git_url":"git://github.com/Yfill/waves.git","ssh_url":"git@github.com:Yfill/waves.git","clone_url":"https://github.com/Yfill/waves.git","svn_url":"https://github.com/Yfill/waves","homepage":"https://yfill.cn/waves","size":248,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":2,"watchers":0,"default_branch":"main"}},"base":{"label":"Yfill:main","ref":"main","sha":"c00ef7e4569ffa4056c700ee3753b1ad0ccc4706","user":{"login":"Yfill","id":33168885,"node_id":"MDQ6VXNlcjMzMTY4ODg1","avatar_url":"https://avatars.githubusercontent.com/u/33168885?v=4","gravatar_id":"","url":"https://api.github.com/users/Yfill","html_url":"https://github.com/Yfill","followers_url":"https://api.github.com/users/Yfill/followers","following_url":"https://api.github.com/users/Yfill/following{/other_user}","gists_url":"https://api.github.com/users/Yfill/gists{/gist_id}","starred_url":"https://api.github.com/users/Yfill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Yfill/subscriptions","organizations_url":"https://api.github.com/users/Yfill/orgs","repos_url":"https://api.github.com/users/Yfill/repos","events_url":"https://api.github.com/users/Yfill/events{/privacy}","received_events_url":"https://api.github.com/users/Yfill/received_events","type":"User","site_admin":false},"repo":{"id":315713528,"node_id":"MDEwOlJlcG9zaXRvcnkzMTU3MTM1Mjg=","name":"waves","full_name":"Yfill/waves","private":false,"owner":{"login":"Yfill","id":33168885,"node_id":"MDQ6VXNlcjMzMTY4ODg1","avatar_url":"https://avatars.githubusercontent.com/u/33168885?v=4","gravatar_id":"","url":"https://api.github.com/users/Yfill","html_url":"https://github.com/Yfill","followers_url":"https://api.github.com/users/Yfill/followers","following_url":"https://api.github.com/users/Yfill/following{/other_user}","gists_url":"https://api.github.com/users/Yfill/gists{/gist_id}","starred_url":"https://api.github.com/users/Yfill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Yfill/subscriptions","organizations_url":"https://api.github.com/users/Yfill/orgs","repos_url":"https://api.github.com/users/Yfill/repos","events_url":"https://api.github.com/users/Yfill/events{/privacy}","received_events_url":"https://api.github.com/users/Yfill/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Yfill/waves","description":"Click the wave effect","fork":false,"url":"https://api.github.com/repos/Yfill/waves","forks_url":"https://api.github.com/repos/Yfill/waves/forks","keys_url":"https://api.github.com/repos/Yfill/waves/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Yfill/waves/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Yfill/waves/teams","hooks_url":"https://api.github.com/repos/Yfill/waves/hooks","issue_events_url":"https://api.github.com/repos/Yfill/waves/issues/events{/number}","events_url":"https://api.github.com/repos/Yfill/waves/events","assignees_url":"https://api.github.com/repos/Yfill/waves/assignees{/user}","branches_url":"https://api.github.com/repos/Yfill/waves/branches{/branch}","tags_url":"https://api.github.com/repos/Yfill/waves/tags","blobs_url":"https://api.github.com/repos/Yfill/waves/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Yfill/waves/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Yfill/waves/git/refs{/sha}","trees_url":"https://api.github.com/repos/Yfill/waves/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Yfill/waves/statuses/{sha}","languages_url":"https://api.github.com/repos/Yfill/waves/languages","stargazers_url":"https://api.github.com/repos/Yfill/waves/stargazers","contributors_url":"https://api.github.com/repos/Yfill/waves/contributors","subscribers_url":"https://api.github.com/repos/Yfill/waves/subscribers","subscription_url":"https://api.github.com/repos/Yfill/waves/subscription","commits_url":"https://api.github.com/repos/Yfill/waves/commits{/sha}","git_commits_url":"https://api.github.com/repos/Yfill/waves/git/commits{/sha}","comments_url":"https://api.github.com/repos/Yfill/waves/comments{/number}","issue_comment_url":"https://api.github.com/repos/Yfill/waves/issues/comments{/number}","contents_url":"https://api.github.com/repos/Yfill/waves/contents/{+path}","compare_url":"https://api.github.com/repos/Yfill/waves/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Yfill/waves/merges","archive_url":"https://api.github.com/repos/Yfill/waves/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Yfill/waves/downloads","issues_url":"https://api.github.com/repos/Yfill/waves/issues{/number}","pulls_url":"https://api.github.com/repos/Yfill/waves/pulls{/number}","milestones_url":"https://api.github.com/repos/Yfill/waves/milestones{/number}","notifications_url":"https://api.github.com/repos/Yfill/waves/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Yfill/waves/labels{/name}","releases_url":"https://api.github.com/repos/Yfill/waves/releases{/id}","deployments_url":"https://api.github.com/repos/Yfill/waves/deployments","created_at":"2020-11-24T18:01:57Z","updated_at":"2021-07-20T02:47:37Z","pushed_at":"2021-07-21T08:59:59Z","git_url":"git://github.com/Yfill/waves.git","ssh_url":"git@github.com:Yfill/waves.git","clone_url":"https://github.com/Yfill/waves.git","svn_url":"https://github.com/Yfill/waves","homepage":"https://yfill.cn/waves","size":248,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":2,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/Yfill/waves/pulls/168"},"html":{"href":"https://github.com/Yfill/waves/pull/168"},"issue":{"href":"https://api.github.com/repos/Yfill/waves/issues/168"},"comments":{"href":"https://api.github.com/repos/Yfill/waves/issues/168/comments"},"review_comments":{"href":"https://api.github.com/repos/Yfill/waves/pulls/168/comments"},"review_comment":{"href":"https://api.github.com/repos/Yfill/waves/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Yfill/waves/pulls/168/commits"},"statuses":{"href":"https://api.github.com/repos/Yfill/waves/statuses/6b24ceac101363876e2c89e7ecc65ad0137b06c3"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T09:00:00Z"} +{"id":"17246339465","type":"DeleteEvent","actor":{"id":42348110,"login":"johanneshiry","display_login":"johanneshiry","gravatar_id":"","url":"https://api.github.com/users/johanneshiry","avatar_url":"https://avatars.githubusercontent.com/u/42348110?"},"repo":{"id":235830706,"name":"ie3-institute/PowerSystemDataModel","url":"https://api.github.com/repos/ie3-institute/PowerSystemDataModel"},"payload":{"ref":"dependabot/gradle/dev/testcontainersVersion-1.16.0","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":58265273,"login":"ie3-institute","gravatar_id":"","url":"https://api.github.com/orgs/ie3-institute","avatar_url":"https://avatars.githubusercontent.com/u/58265273?"}} +{"id":"17246339468","type":"PushEvent","actor":{"id":87652464,"login":"nature011235","display_login":"nature011235","gravatar_id":"","url":"https://api.github.com/users/nature011235","avatar_url":"https://avatars.githubusercontent.com/u/87652464?"},"repo":{"id":387830647,"name":"nature011235/Little-game","url":"https://api.github.com/repos/nature011235/Little-game"},"payload":{"push_id":7562451098,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"aa95bf483b1b30e6c39a1b711731cd428f2c5226","before":"11c59ea186e1579bba8c1ee5919fd71c0282c823","commits":[{"sha":"aa95bf483b1b30e6c39a1b711731cd428f2c5226","author":{"name":"nature011235","email":"d707a7066e8a92010dfd651e03305c5e523f4d61@users.noreply.github.com"},"message":"Add files via upload","distinct":true,"url":"https://api.github.com/repos/nature011235/Little-game/commits/aa95bf483b1b30e6c39a1b711731cd428f2c5226"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339490","type":"PushEvent","actor":{"id":81258380,"login":"Helikopter-Bojowy","display_login":"Helikopter-Bojowy","gravatar_id":"","url":"https://api.github.com/users/Helikopter-Bojowy","avatar_url":"https://avatars.githubusercontent.com/u/81258380?"},"repo":{"id":352069365,"name":"Helikopter-Bojowy/Exp-na-helikopterze","url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze"},"payload":{"push_id":7562451103,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"2003d5f663f56005804f900324a569f18e221913","before":"0bfd0a1c279b39653a869a9c26a60e478c0aef34","commits":[{"sha":"2003d5f663f56005804f900324a569f18e221913","author":{"name":"Helikopter-Bojowy","email":"733707e04c8bf7c874ce97516f8f637586fc79d7@users.noreply.github.com"},"message":"1626858000021:627954597599641601","distinct":true,"url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze/commits/2003d5f663f56005804f900324a569f18e221913"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339497","type":"PushEvent","actor":{"id":11331188,"login":"angulito","display_login":"angulito","gravatar_id":"","url":"https://api.github.com/users/angulito","avatar_url":"https://avatars.githubusercontent.com/u/11331188?"},"repo":{"id":387742598,"name":"angulito/sling-org-apache-sling-servlets-resolver","url":"https://api.github.com/repos/angulito/sling-org-apache-sling-servlets-resolver"},"payload":{"push_id":7562451104,"size":1,"distinct_size":1,"ref":"refs/heads/SLING-10644-return-servlet-resolver-information-in-json-format","head":"1bcf52e6d266743df8c2f9b7ba7bfbf7a53fb67c","before":"9f451c2c3d695a5535d6e993fbc48eb31bb4d59a","commits":[{"sha":"1bcf52e6d266743df8c2f9b7ba7bfbf7a53fb67c","author":{"name":"angulito","email":"c5035bfb9c1ac92bc56577d3d31b1940cf52b111@gmail.com"},"message":"Add more test cases to address sonar coverage\n\nSLING-10644","distinct":true,"url":"https://api.github.com/repos/angulito/sling-org-apache-sling-servlets-resolver/commits/1bcf52e6d266743df8c2f9b7ba7bfbf7a53fb67c"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339502","type":"PushEvent","actor":{"id":58833107,"login":"hrhwve3553","display_login":"hrhwve3553","gravatar_id":"","url":"https://api.github.com/users/hrhwve3553","avatar_url":"https://avatars.githubusercontent.com/u/58833107?"},"repo":{"id":227725053,"name":"hrhwve3553/ntdtv","url":"https://api.github.com/repos/hrhwve3553/ntdtv"},"payload":{"push_id":7562451096,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"7a4554a96e3e4f8a92b3179615a8ae26af3fcfc5","before":"2eb4f5b9b48c2a1db54cad856021f0dad94bf211","commits":[{"sha":"7a4554a96e3e4f8a92b3179615a8ae26af3fcfc5","author":{"name":"hrhwve3553","email":"10135ac1af9bed4c0bec7c29db44050e24a097b2@users.noreply.github.com"},"message":"Update 442749_3.md","distinct":true,"url":"https://api.github.com/repos/hrhwve3553/ntdtv/commits/7a4554a96e3e4f8a92b3179615a8ae26af3fcfc5"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339504","type":"WatchEvent","actor":{"id":37009435,"login":"zepherusqin","display_login":"zepherusqin","gravatar_id":"","url":"https://api.github.com/users/zepherusqin","avatar_url":"https://avatars.githubusercontent.com/u/37009435?"},"repo":{"id":375257099,"name":"yanlong-sun/lstm_unet","url":"https://api.github.com/repos/yanlong-sun/lstm_unet"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339505","type":"PushEvent","actor":{"id":58833107,"login":"hrhwve3553","display_login":"hrhwve3553","gravatar_id":"","url":"https://api.github.com/users/hrhwve3553","avatar_url":"https://avatars.githubusercontent.com/u/58833107?"},"repo":{"id":227724995,"name":"hrhwve3553/djy","url":"https://api.github.com/repos/hrhwve3553/djy"},"payload":{"push_id":7562451122,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"31ac9a45007e5c34dc9d3375d6b31ce590c9ad50","before":"8bcd3dcb3024f61d129bac8b13410a314c29191a","commits":[{"sha":"31ac9a45007e5c34dc9d3375d6b31ce590c9ad50","author":{"name":"hrhwve3553","email":"10135ac1af9bed4c0bec7c29db44050e24a097b2@users.noreply.github.com"},"message":"Update nsc413_3.md","distinct":true,"url":"https://api.github.com/repos/hrhwve3553/djy/commits/31ac9a45007e5c34dc9d3375d6b31ce590c9ad50"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339506","type":"CreateEvent","actor":{"id":73436831,"login":"mayank-mallick","display_login":"mayank-mallick","gravatar_id":"","url":"https://api.github.com/users/mayank-mallick","avatar_url":"https://avatars.githubusercontent.com/u/73436831?"},"repo":{"id":388056957,"name":"mayank-mallick/password-generator","url":"https://api.github.com/repos/mayank-mallick/password-generator"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":"password-generator","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339510","type":"WatchEvent","actor":{"id":86214990,"login":"px1794","display_login":"px1794","gravatar_id":"","url":"https://api.github.com/users/px1794","avatar_url":"https://avatars.githubusercontent.com/u/86214990?"},"repo":{"id":93947448,"name":"twintproject/twint","url":"https://api.github.com/repos/twintproject/twint"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":40190352,"login":"twintproject","gravatar_id":"","url":"https://api.github.com/orgs/twintproject","avatar_url":"https://avatars.githubusercontent.com/u/40190352?"}} +{"id":"17246339514","type":"CreateEvent","actor":{"id":80614815,"login":"DimpleGore","display_login":"DimpleGore","gravatar_id":"","url":"https://api.github.com/users/DimpleGore","avatar_url":"https://avatars.githubusercontent.com/u/80614815?"},"repo":{"id":388056961,"name":"DimpleGore/pixel","url":"https://api.github.com/repos/DimpleGore/pixel"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339515","type":"PushEvent","actor":{"id":87110291,"login":"RohKumarT","display_login":"RohKumarT","gravatar_id":"","url":"https://api.github.com/users/RohKumarT","avatar_url":"https://avatars.githubusercontent.com/u/87110291?"},"repo":{"id":387377532,"name":"RohKumarT/Github-Action-test","url":"https://api.github.com/repos/RohKumarT/Github-Action-test"},"payload":{"push_id":7562451112,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"5aaa190d13ed7c9c061b512085c0836a4c4f035d","before":"0e35e1a37b1860d3819c9f50e26f610c95a90754","commits":[{"sha":"5aaa190d13ed7c9c061b512085c0836a4c4f035d","author":{"name":"Rohit Kumar","email":"0472d8daeabc9833babe0a28328d70f545aae9ca@tibco.com"},"message":"added the final workflow","distinct":true,"url":"https://api.github.com/repos/RohKumarT/Github-Action-test/commits/5aaa190d13ed7c9c061b512085c0836a4c4f035d"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339516","type":"WatchEvent","actor":{"id":261274,"login":"renothing","display_login":"renothing","gravatar_id":"","url":"https://api.github.com/users/renothing","avatar_url":"https://avatars.githubusercontent.com/u/261274?"},"repo":{"id":385332374,"name":"blueedgetechno/windows11","url":"https://api.github.com/repos/blueedgetechno/windows11"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339526","type":"PushEvent","actor":{"id":87747615,"login":"mitchelmarushh775","display_login":"mitchelmarushh775","gravatar_id":"","url":"https://api.github.com/users/mitchelmarushh775","avatar_url":"https://avatars.githubusercontent.com/u/87747615?"},"repo":{"id":388008198,"name":"mitchelmarushh775/cnlkfhw5","url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw5"},"payload":{"push_id":7562451110,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"a104d91b39bb150eeab640a0940aafeb1945cc9a","before":"0838c7e58caf049e1e18177f3c8b9eb18ddab2a0","commits":[{"sha":"a104d91b39bb150eeab640a0940aafeb1945cc9a","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw5/commits/a104d91b39bb150eeab640a0940aafeb1945cc9a"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339530","type":"PushEvent","actor":{"id":11451757,"login":"dolauli","display_login":"dolauli","gravatar_id":"","url":"https://api.github.com/users/dolauli","avatar_url":"https://avatars.githubusercontent.com/u/11451757?"},"repo":{"id":202304052,"name":"dolauli/autorest.powershell","url":"https://api.github.com/repos/dolauli/autorest.powershell"},"payload":{"push_id":7562451120,"size":1,"distinct_size":1,"ref":"refs/heads/breakingchange","head":"606f72d4b868575522cf4487cf9baa03b5d5e3d4","before":"459b250df37c0f84e02ca24482744dd1478fc74c","commits":[{"sha":"606f72d4b868575522cf4487cf9baa03b5d5e3d4","author":{"name":"derek","email":"2d41aa49352a9846d1919261cdc41d42c783ca3a@microsoft.com"},"message":"get old parameter type and output type from the schema defined in swagger","distinct":true,"url":"https://api.github.com/repos/dolauli/autorest.powershell/commits/606f72d4b868575522cf4487cf9baa03b5d5e3d4"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339534","type":"PullRequestReviewEvent","actor":{"id":40165257,"login":"Adrian-Chlopowiec","display_login":"Adrian-Chlopowiec","gravatar_id":"","url":"https://api.github.com/users/Adrian-Chlopowiec","avatar_url":"https://avatars.githubusercontent.com/u/40165257?"},"repo":{"id":358010569,"name":"PWR-Informatyka-biomedyczna/DoggOSFuzzy","url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy"},"payload":{"action":"created","review":{"id":711417104,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDE3MTA0","user":{"login":"Adrian-Chlopowiec","id":40165257,"node_id":"MDQ6VXNlcjQwMTY1MjU3","avatar_url":"https://avatars.githubusercontent.com/u/40165257?v=4","gravatar_id":"","url":"https://api.github.com/users/Adrian-Chlopowiec","html_url":"https://github.com/Adrian-Chlopowiec","followers_url":"https://api.github.com/users/Adrian-Chlopowiec/followers","following_url":"https://api.github.com/users/Adrian-Chlopowiec/following{/other_user}","gists_url":"https://api.github.com/users/Adrian-Chlopowiec/gists{/gist_id}","starred_url":"https://api.github.com/users/Adrian-Chlopowiec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adrian-Chlopowiec/subscriptions","organizations_url":"https://api.github.com/users/Adrian-Chlopowiec/orgs","repos_url":"https://api.github.com/users/Adrian-Chlopowiec/repos","events_url":"https://api.github.com/users/Adrian-Chlopowiec/events{/privacy}","received_events_url":"https://api.github.com/users/Adrian-Chlopowiec/received_events","type":"User","site_admin":false},"body":"","commit_id":"5ee357510ef6374f658faf9798a96410e910fbbb","submitted_at":"2021-07-21T09:00:00Z","state":"changes_requested","html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71#pullrequestreview-711417104","pull_request_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71","author_association":"NONE","_links":{"html":{"href":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71#pullrequestreview-711417104"},"pull_request":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71"}}},"pull_request":{"url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71","id":693461605,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDYxNjA1","html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71","diff_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71.diff","patch_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71.patch","issue_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71","number":71,"state":"open","locked":false,"title":"Equal gausses generation","user":{"login":"Adam-Chlopowiec","id":54243218,"node_id":"MDQ6VXNlcjU0MjQzMjE4","avatar_url":"https://avatars.githubusercontent.com/u/54243218?v=4","gravatar_id":"","url":"https://api.github.com/users/Adam-Chlopowiec","html_url":"https://github.com/Adam-Chlopowiec","followers_url":"https://api.github.com/users/Adam-Chlopowiec/followers","following_url":"https://api.github.com/users/Adam-Chlopowiec/following{/other_user}","gists_url":"https://api.github.com/users/Adam-Chlopowiec/gists{/gist_id}","starred_url":"https://api.github.com/users/Adam-Chlopowiec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adam-Chlopowiec/subscriptions","organizations_url":"https://api.github.com/users/Adam-Chlopowiec/orgs","repos_url":"https://api.github.com/users/Adam-Chlopowiec/repos","events_url":"https://api.github.com/users/Adam-Chlopowiec/events{/privacy}","received_events_url":"https://api.github.com/users/Adam-Chlopowiec/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T13:20:54Z","updated_at":"2021-07-21T09:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9eb9f57d130266ab256680951e2275f66865aeb4","assignee":null,"assignees":[],"requested_reviewers":[{"login":"chrisgal77","id":70722272,"node_id":"MDQ6VXNlcjcwNzIyMjcy","avatar_url":"https://avatars.githubusercontent.com/u/70722272?v=4","gravatar_id":"","url":"https://api.github.com/users/chrisgal77","html_url":"https://github.com/chrisgal77","followers_url":"https://api.github.com/users/chrisgal77/followers","following_url":"https://api.github.com/users/chrisgal77/following{/other_user}","gists_url":"https://api.github.com/users/chrisgal77/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisgal77/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisgal77/subscriptions","organizations_url":"https://api.github.com/users/chrisgal77/orgs","repos_url":"https://api.github.com/users/chrisgal77/repos","events_url":"https://api.github.com/users/chrisgal77/events{/privacy}","received_events_url":"https://api.github.com/users/chrisgal77/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/commits","review_comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/comments","review_comment_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71/comments","statuses_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/5ee357510ef6374f658faf9798a96410e910fbbb","head":{"label":"PWR-Informatyka-biomedyczna:equalGausses","ref":"equalGausses","sha":"5ee357510ef6374f658faf9798a96410e910fbbb","user":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"repo":{"id":358010569,"node_id":"MDEwOlJlcG9zaXRvcnkzNTgwMTA1Njk=","name":"DoggOSFuzzy","full_name":"PWR-Informatyka-biomedyczna/DoggOSFuzzy","private":false,"owner":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","description":"Open source fuzzy logic library.","fork":false,"url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy","forks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/forks","keys_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/teams","hooks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/hooks","issue_events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/events{/number}","events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/events","assignees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/assignees{/user}","branches_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/branches{/branch}","tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/tags","blobs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/refs{/sha}","trees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/{sha}","languages_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/languages","stargazers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/stargazers","contributors_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contributors","subscribers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscribers","subscription_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscription","commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/commits{/sha}","git_commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/commits{/sha}","comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/comments{/number}","issue_comment_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/comments{/number}","contents_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contents/{+path}","compare_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/merges","archive_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/downloads","issues_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues{/number}","pulls_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls{/number}","milestones_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/milestones{/number}","notifications_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/labels{/name}","releases_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/releases{/id}","deployments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/deployments","created_at":"2021-04-14T18:51:58Z","updated_at":"2021-05-03T22:22:54Z","pushed_at":"2021-07-20T13:23:43Z","git_url":"git://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","ssh_url":"git@github.com:PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","clone_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","svn_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","homepage":null,"size":478,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":13,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":13,"watchers":0,"default_branch":"master"}},"base":{"label":"PWR-Informatyka-biomedyczna:dev","ref":"dev","sha":"c5d8cdd9be05b5d3d34e693a112bb12002c1c46b","user":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"repo":{"id":358010569,"node_id":"MDEwOlJlcG9zaXRvcnkzNTgwMTA1Njk=","name":"DoggOSFuzzy","full_name":"PWR-Informatyka-biomedyczna/DoggOSFuzzy","private":false,"owner":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","description":"Open source fuzzy logic library.","fork":false,"url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy","forks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/forks","keys_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/teams","hooks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/hooks","issue_events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/events{/number}","events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/events","assignees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/assignees{/user}","branches_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/branches{/branch}","tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/tags","blobs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/refs{/sha}","trees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/{sha}","languages_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/languages","stargazers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/stargazers","contributors_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contributors","subscribers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscribers","subscription_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscription","commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/commits{/sha}","git_commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/commits{/sha}","comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/comments{/number}","issue_comment_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/comments{/number}","contents_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contents/{+path}","compare_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/merges","archive_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/downloads","issues_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues{/number}","pulls_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls{/number}","milestones_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/milestones{/number}","notifications_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/labels{/name}","releases_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/releases{/id}","deployments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/deployments","created_at":"2021-04-14T18:51:58Z","updated_at":"2021-05-03T22:22:54Z","pushed_at":"2021-07-20T13:23:43Z","git_url":"git://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","ssh_url":"git@github.com:PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","clone_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","svn_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","homepage":null,"size":478,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":13,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":13,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71"},"html":{"href":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71"},"issue":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71"},"comments":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71/comments"},"review_comments":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/comments"},"review_comment":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/commits"},"statuses":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/5ee357510ef6374f658faf9798a96410e910fbbb"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":67423257,"login":"PWR-Informatyka-biomedyczna","gravatar_id":"","url":"https://api.github.com/orgs/PWR-Informatyka-biomedyczna","avatar_url":"https://avatars.githubusercontent.com/u/67423257?"}} +{"id":"17246339537","type":"CreateEvent","actor":{"id":70198139,"login":"Keisuke-Joulia","display_login":"Keisuke-Joulia","gravatar_id":"","url":"https://api.github.com/users/Keisuke-Joulia","avatar_url":"https://avatars.githubusercontent.com/u/70198139?"},"repo":{"id":388051165,"name":"Keisuke-Joulia/Portfolio","url":"https://api.github.com/repos/Keisuke-Joulia/Portfolio"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339549","type":"PushEvent","actor":{"id":79913779,"login":"conda-forge-curator[bot]","display_login":"conda-forge-curator","gravatar_id":"","url":"https://api.github.com/users/conda-forge-curator[bot]","avatar_url":"https://avatars.githubusercontent.com/u/79913779?"},"repo":{"id":286991282,"name":"conda-forge/repodata-shards","url":"https://api.github.com/repos/conda-forge/repodata-shards"},"payload":{"push_id":7562451124,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"b621128ef3719675e3398376b07e3de84f00c1f1","before":"94b95286b7bdcc5f3109126f224f3c0ebf132a9f","commits":[{"sha":"b621128ef3719675e3398376b07e3de84f00c1f1","author":{"name":"conda-forge-curator[bot]","email":"33ed12a5a1eeb5dbf95e50afdeb87971316ce7da@users.noreply.github.com"},"message":"added win-64/astroid-2.6.5-py38haa244fe_0.tar.bz2 [ci skip] [cf admin skip] ***NO_CI***","distinct":true,"url":"https://api.github.com/repos/conda-forge/repodata-shards/commits/b621128ef3719675e3398376b07e3de84f00c1f1"}]},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":11897326,"login":"conda-forge","gravatar_id":"","url":"https://api.github.com/orgs/conda-forge","avatar_url":"https://avatars.githubusercontent.com/u/11897326?"}} +{"id":"17246339555","type":"PushEvent","actor":{"id":47364493,"login":"kenken1199","display_login":"kenken1199","gravatar_id":"","url":"https://api.github.com/users/kenken1199","avatar_url":"https://avatars.githubusercontent.com/u/47364493?"},"repo":{"id":388023493,"name":"kenken1199/flask-docker-kn","url":"https://api.github.com/repos/kenken1199/flask-docker-kn"},"payload":{"push_id":7562451115,"size":1,"distinct_size":1,"ref":"refs/heads/ci","head":"b4e434b0dc6bb0ae9e7d499216453665321a85cd","before":"f4fc7ccaf6eef1d052251b6c5de5f87ebd97df1d","commits":[{"sha":"b4e434b0dc6bb0ae9e7d499216453665321a85cd","author":{"name":"kenta","email":"21b7f28c6328e04ba68d8a6bec860e5d73516fd8@gmail.com"},"message":"CircleCI修正 sudo3","distinct":true,"url":"https://api.github.com/repos/kenken1199/flask-docker-kn/commits/b4e434b0dc6bb0ae9e7d499216453665321a85cd"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339565","type":"WatchEvent","actor":{"id":12492626,"login":"ck19940316","display_login":"ck19940316","gravatar_id":"","url":"https://api.github.com/users/ck19940316","avatar_url":"https://avatars.githubusercontent.com/u/12492626?"},"repo":{"id":386492981,"name":"bytedance/flutter_ume","url":"https://api.github.com/repos/bytedance/flutter_ume"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":4158466,"login":"bytedance","gravatar_id":"","url":"https://api.github.com/orgs/bytedance","avatar_url":"https://avatars.githubusercontent.com/u/4158466?"}} +{"id":"17246339569","type":"ReleaseEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":386520781,"name":"qcloud-apaas/web-sdk","url":"https://api.github.com/repos/qcloud-apaas/web-sdk"},"payload":{"action":"published","release":{"url":"https://api.github.com/repos/qcloud-apaas/web-sdk/releases/46527562","assets_url":"https://api.github.com/repos/qcloud-apaas/web-sdk/releases/46527562/assets","upload_url":"https://uploads.github.com/repos/qcloud-apaas/web-sdk/releases/46527562/assets{?name,label}","html_url":"https://github.com/qcloud-apaas/web-sdk/releases/tag/v0.1.4","id":46527562,"author":{"login":"github-actions[bot]","id":41898282,"node_id":"MDM6Qm90NDE4OTgyODI=","avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4","gravatar_id":"","url":"https://api.github.com/users/github-actions%5Bbot%5D","html_url":"https://github.com/apps/github-actions","followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers","following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos","events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events","type":"Bot","site_admin":false},"node_id":"MDc6UmVsZWFzZTQ2NTI3NTYy","tag_name":"v0.1.4","target_commitish":"b7171e84919a370d46cf221a7138c2fc751be1ac","name":"v0.1.4","draft":false,"prerelease":false,"created_at":"2021-07-21T08:57:47Z","published_at":"2021-07-21T09:00:01Z","assets":[],"tarball_url":"https://api.github.com/repos/qcloud-apaas/web-sdk/tarball/v0.1.4","zipball_url":"https://api.github.com/repos/qcloud-apaas/web-sdk/zipball/v0.1.4","body":"","short_description_html":null,"is_short_description_html_truncated":false}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":82105299,"login":"qcloud-apaas","gravatar_id":"","url":"https://api.github.com/orgs/qcloud-apaas","avatar_url":"https://avatars.githubusercontent.com/u/82105299?"}} +{"id":"17246339571","type":"PullRequestReviewEvent","actor":{"id":40165257,"login":"Adrian-Chlopowiec","display_login":"Adrian-Chlopowiec","gravatar_id":"","url":"https://api.github.com/users/Adrian-Chlopowiec","avatar_url":"https://avatars.githubusercontent.com/u/40165257?"},"repo":{"id":358010569,"name":"PWR-Informatyka-biomedyczna/DoggOSFuzzy","url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy"},"payload":{"action":"created","review":{"id":711417104,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDE3MTA0","user":{"login":"Adrian-Chlopowiec","id":40165257,"node_id":"MDQ6VXNlcjQwMTY1MjU3","avatar_url":"https://avatars.githubusercontent.com/u/40165257?v=4","gravatar_id":"","url":"https://api.github.com/users/Adrian-Chlopowiec","html_url":"https://github.com/Adrian-Chlopowiec","followers_url":"https://api.github.com/users/Adrian-Chlopowiec/followers","following_url":"https://api.github.com/users/Adrian-Chlopowiec/following{/other_user}","gists_url":"https://api.github.com/users/Adrian-Chlopowiec/gists{/gist_id}","starred_url":"https://api.github.com/users/Adrian-Chlopowiec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adrian-Chlopowiec/subscriptions","organizations_url":"https://api.github.com/users/Adrian-Chlopowiec/orgs","repos_url":"https://api.github.com/users/Adrian-Chlopowiec/repos","events_url":"https://api.github.com/users/Adrian-Chlopowiec/events{/privacy}","received_events_url":"https://api.github.com/users/Adrian-Chlopowiec/received_events","type":"User","site_admin":false},"body":"","commit_id":"5ee357510ef6374f658faf9798a96410e910fbbb","submitted_at":"2021-07-21T09:00:00Z","state":"changes_requested","html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71#pullrequestreview-711417104","pull_request_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71","author_association":"NONE","_links":{"html":{"href":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71#pullrequestreview-711417104"},"pull_request":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71"}}},"pull_request":{"url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71","id":693461605,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDYxNjA1","html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71","diff_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71.diff","patch_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71.patch","issue_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71","number":71,"state":"open","locked":false,"title":"Equal gausses generation","user":{"login":"Adam-Chlopowiec","id":54243218,"node_id":"MDQ6VXNlcjU0MjQzMjE4","avatar_url":"https://avatars.githubusercontent.com/u/54243218?v=4","gravatar_id":"","url":"https://api.github.com/users/Adam-Chlopowiec","html_url":"https://github.com/Adam-Chlopowiec","followers_url":"https://api.github.com/users/Adam-Chlopowiec/followers","following_url":"https://api.github.com/users/Adam-Chlopowiec/following{/other_user}","gists_url":"https://api.github.com/users/Adam-Chlopowiec/gists{/gist_id}","starred_url":"https://api.github.com/users/Adam-Chlopowiec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adam-Chlopowiec/subscriptions","organizations_url":"https://api.github.com/users/Adam-Chlopowiec/orgs","repos_url":"https://api.github.com/users/Adam-Chlopowiec/repos","events_url":"https://api.github.com/users/Adam-Chlopowiec/events{/privacy}","received_events_url":"https://api.github.com/users/Adam-Chlopowiec/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T13:20:54Z","updated_at":"2021-07-21T09:00:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9eb9f57d130266ab256680951e2275f66865aeb4","assignee":null,"assignees":[],"requested_reviewers":[{"login":"chrisgal77","id":70722272,"node_id":"MDQ6VXNlcjcwNzIyMjcy","avatar_url":"https://avatars.githubusercontent.com/u/70722272?v=4","gravatar_id":"","url":"https://api.github.com/users/chrisgal77","html_url":"https://github.com/chrisgal77","followers_url":"https://api.github.com/users/chrisgal77/followers","following_url":"https://api.github.com/users/chrisgal77/following{/other_user}","gists_url":"https://api.github.com/users/chrisgal77/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisgal77/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisgal77/subscriptions","organizations_url":"https://api.github.com/users/chrisgal77/orgs","repos_url":"https://api.github.com/users/chrisgal77/repos","events_url":"https://api.github.com/users/chrisgal77/events{/privacy}","received_events_url":"https://api.github.com/users/chrisgal77/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/commits","review_comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/comments","review_comment_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71/comments","statuses_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/5ee357510ef6374f658faf9798a96410e910fbbb","head":{"label":"PWR-Informatyka-biomedyczna:equalGausses","ref":"equalGausses","sha":"5ee357510ef6374f658faf9798a96410e910fbbb","user":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"repo":{"id":358010569,"node_id":"MDEwOlJlcG9zaXRvcnkzNTgwMTA1Njk=","name":"DoggOSFuzzy","full_name":"PWR-Informatyka-biomedyczna/DoggOSFuzzy","private":false,"owner":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","description":"Open source fuzzy logic library.","fork":false,"url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy","forks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/forks","keys_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/teams","hooks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/hooks","issue_events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/events{/number}","events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/events","assignees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/assignees{/user}","branches_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/branches{/branch}","tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/tags","blobs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/refs{/sha}","trees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/{sha}","languages_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/languages","stargazers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/stargazers","contributors_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contributors","subscribers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscribers","subscription_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscription","commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/commits{/sha}","git_commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/commits{/sha}","comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/comments{/number}","issue_comment_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/comments{/number}","contents_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contents/{+path}","compare_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/merges","archive_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/downloads","issues_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues{/number}","pulls_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls{/number}","milestones_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/milestones{/number}","notifications_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/labels{/name}","releases_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/releases{/id}","deployments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/deployments","created_at":"2021-04-14T18:51:58Z","updated_at":"2021-05-03T22:22:54Z","pushed_at":"2021-07-20T13:23:43Z","git_url":"git://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","ssh_url":"git@github.com:PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","clone_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","svn_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","homepage":null,"size":478,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":13,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":13,"watchers":0,"default_branch":"master"}},"base":{"label":"PWR-Informatyka-biomedyczna:dev","ref":"dev","sha":"c5d8cdd9be05b5d3d34e693a112bb12002c1c46b","user":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"repo":{"id":358010569,"node_id":"MDEwOlJlcG9zaXRvcnkzNTgwMTA1Njk=","name":"DoggOSFuzzy","full_name":"PWR-Informatyka-biomedyczna/DoggOSFuzzy","private":false,"owner":{"login":"PWR-Informatyka-biomedyczna","id":67423257,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NDIzMjU3","avatar_url":"https://avatars.githubusercontent.com/u/67423257?v=4","gravatar_id":"","url":"https://api.github.com/users/PWR-Informatyka-biomedyczna","html_url":"https://github.com/PWR-Informatyka-biomedyczna","followers_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/followers","following_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/following{/other_user}","gists_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/gists{/gist_id}","starred_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/subscriptions","organizations_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/orgs","repos_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/repos","events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/events{/privacy}","received_events_url":"https://api.github.com/users/PWR-Informatyka-biomedyczna/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","description":"Open source fuzzy logic library.","fork":false,"url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy","forks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/forks","keys_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/teams","hooks_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/hooks","issue_events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/events{/number}","events_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/events","assignees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/assignees{/user}","branches_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/branches{/branch}","tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/tags","blobs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/refs{/sha}","trees_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/{sha}","languages_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/languages","stargazers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/stargazers","contributors_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contributors","subscribers_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscribers","subscription_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/subscription","commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/commits{/sha}","git_commits_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/git/commits{/sha}","comments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/comments{/number}","issue_comment_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/comments{/number}","contents_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/contents/{+path}","compare_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/merges","archive_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/downloads","issues_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues{/number}","pulls_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls{/number}","milestones_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/milestones{/number}","notifications_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/labels{/name}","releases_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/releases{/id}","deployments_url":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/deployments","created_at":"2021-04-14T18:51:58Z","updated_at":"2021-05-03T22:22:54Z","pushed_at":"2021-07-20T13:23:43Z","git_url":"git://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","ssh_url":"git@github.com:PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","clone_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy.git","svn_url":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy","homepage":null,"size":478,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":13,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":13,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71"},"html":{"href":"https://github.com/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pull/71"},"issue":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71"},"comments":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/issues/71/comments"},"review_comments":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/comments"},"review_comment":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/pulls/71/commits"},"statuses":{"href":"https://api.github.com/repos/PWR-Informatyka-biomedyczna/DoggOSFuzzy/statuses/5ee357510ef6374f658faf9798a96410e910fbbb"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":67423257,"login":"PWR-Informatyka-biomedyczna","gravatar_id":"","url":"https://api.github.com/orgs/PWR-Informatyka-biomedyczna","avatar_url":"https://avatars.githubusercontent.com/u/67423257?"}} +{"id":"17246339574","type":"IssueCommentEvent","actor":{"id":2748981,"login":"BenjaminSchaaf","display_login":"BenjaminSchaaf","gravatar_id":"","url":"https://api.github.com/users/BenjaminSchaaf","avatar_url":"https://avatars.githubusercontent.com/u/2748981?"},"repo":{"id":9598363,"name":"sublimehq/sublime_text","url":"https://api.github.com/repos/sublimehq/sublime_text"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676","repository_url":"https://api.github.com/repos/sublimehq/sublime_text","labels_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676/labels{/name}","comments_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676/comments","events_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676/events","html_url":"https://github.com/sublimehq/sublime_text/issues/4676","id":949476971,"node_id":"MDU6SXNzdWU5NDk0NzY5NzE=","number":4676,"title":"Latex compiling is correct but opens an additional blank window","user":{"login":"jsbibra","id":55181504,"node_id":"MDQ6VXNlcjU1MTgxNTA0","avatar_url":"https://avatars.githubusercontent.com/u/55181504?v=4","gravatar_id":"","url":"https://api.github.com/users/jsbibra","html_url":"https://github.com/jsbibra","followers_url":"https://api.github.com/users/jsbibra/followers","following_url":"https://api.github.com/users/jsbibra/following{/other_user}","gists_url":"https://api.github.com/users/jsbibra/gists{/gist_id}","starred_url":"https://api.github.com/users/jsbibra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsbibra/subscriptions","organizations_url":"https://api.github.com/users/jsbibra/orgs","repos_url":"https://api.github.com/users/jsbibra/repos","events_url":"https://api.github.com/users/jsbibra/events{/privacy}","received_events_url":"https://api.github.com/users/jsbibra/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-21T08:47:48Z","updated_at":"2021-07-21T09:00:01Z","closed_at":"2021-07-21T09:00:01Z","author_association":"NONE","active_lock_reason":null,"body":"### Description\r\n\r\nAdditional Blank window on compile\r\n### Steps to reproduce\r\n\r\n1. Start Sublime Text \r\n2. compile latex code\r\n3. code compiles in sumatra but also opens up an additional blank window\r\n\r\n### Expected behavior\r\n\r\ncode should compile without opening an additional blank window\r\n\r\n### Actual behavior\r\n\r\n code compiles in sumatra but also opens up an additional blank window\r\n\r\n### Environment\r\n\r\n* Build: [e.g. 4113]\r\n* Operating system and version: [Windows 10, desktop PC]\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/sublimehq/sublime_text/issues/comments/884018693","html_url":"https://github.com/sublimehq/sublime_text/issues/4676#issuecomment-884018693","issue_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676","id":884018693,"node_id":"IC_kwDOAJJ1m840sQ4F","user":{"login":"BenjaminSchaaf","id":2748981,"node_id":"MDQ6VXNlcjI3NDg5ODE=","avatar_url":"https://avatars.githubusercontent.com/u/2748981?v=4","gravatar_id":"","url":"https://api.github.com/users/BenjaminSchaaf","html_url":"https://github.com/BenjaminSchaaf","followers_url":"https://api.github.com/users/BenjaminSchaaf/followers","following_url":"https://api.github.com/users/BenjaminSchaaf/following{/other_user}","gists_url":"https://api.github.com/users/BenjaminSchaaf/gists{/gist_id}","starred_url":"https://api.github.com/users/BenjaminSchaaf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BenjaminSchaaf/subscriptions","organizations_url":"https://api.github.com/users/BenjaminSchaaf/orgs","repos_url":"https://api.github.com/users/BenjaminSchaaf/repos","events_url":"https://api.github.com/users/BenjaminSchaaf/events{/privacy}","received_events_url":"https://api.github.com/users/BenjaminSchaaf/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:00Z","updated_at":"2021-07-21T09:00:00Z","author_association":"MEMBER","body":"See #4621","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":1161584,"login":"sublimehq","gravatar_id":"","url":"https://api.github.com/orgs/sublimehq","avatar_url":"https://avatars.githubusercontent.com/u/1161584?"}} +{"id":"17246339576","type":"PushEvent","actor":{"id":38880224,"login":"fazchanneltv","display_login":"fazchanneltv","gravatar_id":"","url":"https://api.github.com/users/fazchanneltv","avatar_url":"https://avatars.githubusercontent.com/u/38880224?"},"repo":{"id":387105424,"name":"fazchanneltv/radio","url":"https://api.github.com/repos/fazchanneltv/radio"},"payload":{"push_id":7562451136,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"8b95de33a5b969ad713db589a251d54801eddeef","before":"564afb7016516fe9328f2ac1da58fee3182979fc","commits":[{"sha":"8b95de33a5b969ad713db589a251d54801eddeef","author":{"name":"fazchanneltv","email":"7588a9eb7622457cfef32d700780dfed85ec5b9f@users.noreply.github.com"},"message":"Update index.html","distinct":true,"url":"https://api.github.com/repos/fazchanneltv/radio/commits/8b95de33a5b969ad713db589a251d54801eddeef"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339577","type":"PushEvent","actor":{"id":61676330,"login":"gtlrajpatel","display_login":"gtlrajpatel","gravatar_id":"","url":"https://api.github.com/users/gtlrajpatel","avatar_url":"https://avatars.githubusercontent.com/u/61676330?"},"repo":{"id":374989092,"name":"nlplearningteam/pdf-text-extraction","url":"https://api.github.com/repos/nlplearningteam/pdf-text-extraction"},"payload":{"push_id":7562451145,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"276f5817f92f5310202f1322cba6b0a75bba5936","before":"893949858eace8c99cf844f4bd3c67ce4bb2a5a7","commits":[{"sha":"276f5817f92f5310202f1322cba6b0a75bba5936","author":{"name":"Raj Patel","email":"d2e45eb40a6b21b7262aaab5234dccfff40771f3@thegatewaycorp.com"},"message":"Refactor the text extraction code","distinct":true,"url":"https://api.github.com/repos/nlplearningteam/pdf-text-extraction/commits/276f5817f92f5310202f1322cba6b0a75bba5936"}]},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":85568703,"login":"nlplearningteam","gravatar_id":"","url":"https://api.github.com/orgs/nlplearningteam","avatar_url":"https://avatars.githubusercontent.com/u/85568703?"}} +{"id":"17246339581","type":"PullRequestReviewCommentEvent","actor":{"id":35995629,"login":"DmitryChitalov","display_login":"DmitryChitalov","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","avatar_url":"https://avatars.githubusercontent.com/u/35995629?"},"repo":{"id":336795774,"name":"DmitryChitalov/-algorithms_2021","url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/comments/673792284","pull_request_review_id":711422067,"id":673792284,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDY3Mzc5MjI4NA==","diff_hunk":"@@ -20,3 +20,37 @@\n Обязательно усложните задачу! Добавьте сохранение хеша в файле и получение его из файла.\n А если вы знаете как через Python работать с БД, привяжите к заданию БД и сохраняйте хеши там.\n \"\"\"\n+\n+from uuid import uuid4\n+import sqlite3\n+import hashlib\n+\n+\n+def pass_in_db():\n+\tconn = sqlite3.connect(\":memory:\")\n+\tcur = conn.cursor()\n+\tcur.execute(\"\"\"CREATE TABLE passwords (\n+\t\tid INT PRIMARY KEY,\n+\t\tpassword TEXT);\n+\t\"\"\")\n+\tconn.commit()\n+\t\n+\tsalt = uuid4().hex\n+\tcur.execute(\"INSERT INTO passwords VALUES(?, ?)\", (1, hashlib.sha256(salt.encode() + input(\"Введите пароль: \").encode()).hexdigest()))\n+\tconn.commit()\n+\t\n+\tcur.execute(\"SELECT password FROM passwords\")\n+\tprint(\"В базе данных хранится строка:\", cur.fetchone()[0], sep='\\n')\n+\t\n+\tpass_check = hashlib.sha256(salt.encode() + input(\"ВВведите пароль еще раз для проверки: \").encode()).hexdigest()\n+\tcur.execute(\"SELECT password FROM passwords\")\n+\t","path":"Урок 3. Практическое задание/task_2.py","position":28,"original_position":28,"commit_id":"785d22a4245376f077ebfb457b5533af2ebfc023","original_commit_id":"785d22a4245376f077ebfb457b5533af2ebfc023","user":{"login":"DmitryChitalov","id":35995629,"node_id":"MDQ6VXNlcjM1OTk1NjI5","avatar_url":"https://avatars.githubusercontent.com/u/35995629?v=4","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","html_url":"https://github.com/DmitryChitalov","followers_url":"https://api.github.com/users/DmitryChitalov/followers","following_url":"https://api.github.com/users/DmitryChitalov/following{/other_user}","gists_url":"https://api.github.com/users/DmitryChitalov/gists{/gist_id}","starred_url":"https://api.github.com/users/DmitryChitalov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DmitryChitalov/subscriptions","organizations_url":"https://api.github.com/users/DmitryChitalov/orgs","repos_url":"https://api.github.com/users/DmitryChitalov/repos","events_url":"https://api.github.com/users/DmitryChitalov/events{/privacy}","received_events_url":"https://api.github.com/users/DmitryChitalov/received_events","type":"User","site_admin":false},"body":"\tpass_check = hashlib.sha256(salt.encode() + input(\"ВВведите пароль еще раз для проверки: \").encode()).hexdigest()\r\n\tcur.execute(\"SELECT password FROM passwords\")\r\n\r\nнужно убрать дублир-я","created_at":"2021-07-21T09:00:01Z","updated_at":"2021-07-21T09:00:01Z","html_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253#discussion_r673792284","pull_request_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/comments/673792284"},"html":{"href":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253#discussion_r673792284"},"pull_request":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253"}},"start_line":null,"original_start_line":null,"start_side":null,"line":47,"original_line":47,"side":"RIGHT"},"pull_request":{"url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253","id":693635172,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNjM1MTcy","html_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253","diff_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253.diff","patch_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253.patch","issue_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253","number":1253,"state":"open","locked":false,"title":"less-3: 1,2,3","user":{"login":"staphw","id":76226605,"node_id":"MDQ6VXNlcjc2MjI2NjA1","avatar_url":"https://avatars.githubusercontent.com/u/76226605?v=4","gravatar_id":"","url":"https://api.github.com/users/staphw","html_url":"https://github.com/staphw","followers_url":"https://api.github.com/users/staphw/followers","following_url":"https://api.github.com/users/staphw/following{/other_user}","gists_url":"https://api.github.com/users/staphw/gists{/gist_id}","starred_url":"https://api.github.com/users/staphw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/staphw/subscriptions","organizations_url":"https://api.github.com/users/staphw/orgs","repos_url":"https://api.github.com/users/staphw/repos","events_url":"https://api.github.com/users/staphw/events{/privacy}","received_events_url":"https://api.github.com/users/staphw/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T16:53:32Z","updated_at":"2021-07-21T09:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3cd5cf184f6ff3957fc061f8e90753cc516f0792","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/commits","review_comments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/comments","review_comment_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/comments{/number}","comments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253/comments","statuses_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/statuses/785d22a4245376f077ebfb457b5533af2ebfc023","head":{"label":"staphw:lesson-3","ref":"lesson-3","sha":"785d22a4245376f077ebfb457b5533af2ebfc023","user":{"login":"staphw","id":76226605,"node_id":"MDQ6VXNlcjc2MjI2NjA1","avatar_url":"https://avatars.githubusercontent.com/u/76226605?v=4","gravatar_id":"","url":"https://api.github.com/users/staphw","html_url":"https://github.com/staphw","followers_url":"https://api.github.com/users/staphw/followers","following_url":"https://api.github.com/users/staphw/following{/other_user}","gists_url":"https://api.github.com/users/staphw/gists{/gist_id}","starred_url":"https://api.github.com/users/staphw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/staphw/subscriptions","organizations_url":"https://api.github.com/users/staphw/orgs","repos_url":"https://api.github.com/users/staphw/repos","events_url":"https://api.github.com/users/staphw/events{/privacy}","received_events_url":"https://api.github.com/users/staphw/received_events","type":"User","site_admin":false},"repo":{"id":384888939,"node_id":"MDEwOlJlcG9zaXRvcnkzODQ4ODg5Mzk=","name":"-algorithms_2021","full_name":"staphw/-algorithms_2021","private":false,"owner":{"login":"staphw","id":76226605,"node_id":"MDQ6VXNlcjc2MjI2NjA1","avatar_url":"https://avatars.githubusercontent.com/u/76226605?v=4","gravatar_id":"","url":"https://api.github.com/users/staphw","html_url":"https://github.com/staphw","followers_url":"https://api.github.com/users/staphw/followers","following_url":"https://api.github.com/users/staphw/following{/other_user}","gists_url":"https://api.github.com/users/staphw/gists{/gist_id}","starred_url":"https://api.github.com/users/staphw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/staphw/subscriptions","organizations_url":"https://api.github.com/users/staphw/orgs","repos_url":"https://api.github.com/users/staphw/repos","events_url":"https://api.github.com/users/staphw/events{/privacy}","received_events_url":"https://api.github.com/users/staphw/received_events","type":"User","site_admin":false},"html_url":"https://github.com/staphw/-algorithms_2021","description":null,"fork":true,"url":"https://api.github.com/repos/staphw/-algorithms_2021","forks_url":"https://api.github.com/repos/staphw/-algorithms_2021/forks","keys_url":"https://api.github.com/repos/staphw/-algorithms_2021/keys{/key_id}","collaborators_url":"https://api.github.com/repos/staphw/-algorithms_2021/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/staphw/-algorithms_2021/teams","hooks_url":"https://api.github.com/repos/staphw/-algorithms_2021/hooks","issue_events_url":"https://api.github.com/repos/staphw/-algorithms_2021/issues/events{/number}","events_url":"https://api.github.com/repos/staphw/-algorithms_2021/events","assignees_url":"https://api.github.com/repos/staphw/-algorithms_2021/assignees{/user}","branches_url":"https://api.github.com/repos/staphw/-algorithms_2021/branches{/branch}","tags_url":"https://api.github.com/repos/staphw/-algorithms_2021/tags","blobs_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/refs{/sha}","trees_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/trees{/sha}","statuses_url":"https://api.github.com/repos/staphw/-algorithms_2021/statuses/{sha}","languages_url":"https://api.github.com/repos/staphw/-algorithms_2021/languages","stargazers_url":"https://api.github.com/repos/staphw/-algorithms_2021/stargazers","contributors_url":"https://api.github.com/repos/staphw/-algorithms_2021/contributors","subscribers_url":"https://api.github.com/repos/staphw/-algorithms_2021/subscribers","subscription_url":"https://api.github.com/repos/staphw/-algorithms_2021/subscription","commits_url":"https://api.github.com/repos/staphw/-algorithms_2021/commits{/sha}","git_commits_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/commits{/sha}","comments_url":"https://api.github.com/repos/staphw/-algorithms_2021/comments{/number}","issue_comment_url":"https://api.github.com/repos/staphw/-algorithms_2021/issues/comments{/number}","contents_url":"https://api.github.com/repos/staphw/-algorithms_2021/contents/{+path}","compare_url":"https://api.github.com/repos/staphw/-algorithms_2021/compare/{base}...{head}","merges_url":"https://api.github.com/repos/staphw/-algorithms_2021/merges","archive_url":"https://api.github.com/repos/staphw/-algorithms_2021/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/staphw/-algorithms_2021/downloads","issues_url":"https://api.github.com/repos/staphw/-algorithms_2021/issues{/number}","pulls_url":"https://api.github.com/repos/staphw/-algorithms_2021/pulls{/number}","milestones_url":"https://api.github.com/repos/staphw/-algorithms_2021/milestones{/number}","notifications_url":"https://api.github.com/repos/staphw/-algorithms_2021/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/staphw/-algorithms_2021/labels{/name}","releases_url":"https://api.github.com/repos/staphw/-algorithms_2021/releases{/id}","deployments_url":"https://api.github.com/repos/staphw/-algorithms_2021/deployments","created_at":"2021-07-11T07:45:27Z","updated_at":"2021-07-11T07:45:28Z","pushed_at":"2021-07-20T16:52:45Z","git_url":"git://github.com/staphw/-algorithms_2021.git","ssh_url":"git@github.com:staphw/-algorithms_2021.git","clone_url":"https://github.com/staphw/-algorithms_2021.git","svn_url":"https://github.com/staphw/-algorithms_2021","homepage":null,"size":77,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"DmitryChitalov:master","ref":"master","sha":"93d9a166e5a961abf555ace5cb8f3a246510a403","user":{"login":"DmitryChitalov","id":35995629,"node_id":"MDQ6VXNlcjM1OTk1NjI5","avatar_url":"https://avatars.githubusercontent.com/u/35995629?v=4","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","html_url":"https://github.com/DmitryChitalov","followers_url":"https://api.github.com/users/DmitryChitalov/followers","following_url":"https://api.github.com/users/DmitryChitalov/following{/other_user}","gists_url":"https://api.github.com/users/DmitryChitalov/gists{/gist_id}","starred_url":"https://api.github.com/users/DmitryChitalov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DmitryChitalov/subscriptions","organizations_url":"https://api.github.com/users/DmitryChitalov/orgs","repos_url":"https://api.github.com/users/DmitryChitalov/repos","events_url":"https://api.github.com/users/DmitryChitalov/events{/privacy}","received_events_url":"https://api.github.com/users/DmitryChitalov/received_events","type":"User","site_admin":false},"repo":{"id":336795774,"node_id":"MDEwOlJlcG9zaXRvcnkzMzY3OTU3NzQ=","name":"-algorithms_2021","full_name":"DmitryChitalov/-algorithms_2021","private":false,"owner":{"login":"DmitryChitalov","id":35995629,"node_id":"MDQ6VXNlcjM1OTk1NjI5","avatar_url":"https://avatars.githubusercontent.com/u/35995629?v=4","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","html_url":"https://github.com/DmitryChitalov","followers_url":"https://api.github.com/users/DmitryChitalov/followers","following_url":"https://api.github.com/users/DmitryChitalov/following{/other_user}","gists_url":"https://api.github.com/users/DmitryChitalov/gists{/gist_id}","starred_url":"https://api.github.com/users/DmitryChitalov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DmitryChitalov/subscriptions","organizations_url":"https://api.github.com/users/DmitryChitalov/orgs","repos_url":"https://api.github.com/users/DmitryChitalov/repos","events_url":"https://api.github.com/users/DmitryChitalov/events{/privacy}","received_events_url":"https://api.github.com/users/DmitryChitalov/received_events","type":"User","site_admin":false},"html_url":"https://github.com/DmitryChitalov/-algorithms_2021","description":null,"fork":false,"url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021","forks_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/forks","keys_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/teams","hooks_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/hooks","issue_events_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/events{/number}","events_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/events","assignees_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/assignees{/user}","branches_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/branches{/branch}","tags_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/tags","blobs_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/refs{/sha}","trees_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/statuses/{sha}","languages_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/languages","stargazers_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/stargazers","contributors_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/contributors","subscribers_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/subscribers","subscription_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/subscription","commits_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/commits{/sha}","git_commits_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/commits{/sha}","comments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/comments{/number}","issue_comment_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/comments{/number}","contents_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/contents/{+path}","compare_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/merges","archive_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/downloads","issues_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues{/number}","pulls_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls{/number}","milestones_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/milestones{/number}","notifications_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/labels{/name}","releases_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/releases{/id}","deployments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/deployments","created_at":"2021-02-07T13:39:06Z","updated_at":"2021-07-13T18:43:54Z","pushed_at":"2021-07-21T07:44:18Z","git_url":"git://github.com/DmitryChitalov/-algorithms_2021.git","ssh_url":"git@github.com:DmitryChitalov/-algorithms_2021.git","clone_url":"https://github.com/DmitryChitalov/-algorithms_2021.git","svn_url":"https://github.com/DmitryChitalov/-algorithms_2021","homepage":null,"size":72,"stargazers_count":4,"watchers_count":4,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":271,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1152,"license":null,"forks":271,"open_issues":1152,"watchers":4,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253"},"html":{"href":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253"},"issue":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253"},"comments":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253/comments"},"review_comments":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/comments"},"review_comment":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/commits"},"statuses":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/statuses/785d22a4245376f077ebfb457b5533af2ebfc023"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339588","type":"WatchEvent","actor":{"id":20277283,"login":"giannissc","display_login":"giannissc","gravatar_id":"","url":"https://api.github.com/users/giannissc","avatar_url":"https://avatars.githubusercontent.com/u/20277283?"},"repo":{"id":373814100,"name":"dfrg/parley","url":"https://api.github.com/repos/dfrg/parley"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339592","type":"PushEvent","actor":{"id":21108631,"login":"shihabus","display_login":"shihabus","gravatar_id":"","url":"https://api.github.com/users/shihabus","avatar_url":"https://avatars.githubusercontent.com/u/21108631?"},"repo":{"id":367106789,"name":"shihabus/tailwind-portfolio","url":"https://api.github.com/repos/shihabus/tailwind-portfolio"},"payload":{"push_id":7562451127,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"829165c7e2d065eb634d25c1f83f71d15abeaeb8","before":"e6fe79fa956a52629bb98dc2eecfba5529ef5990","commits":[{"sha":"829165c7e2d065eb634d25c1f83f71d15abeaeb8","author":{"name":"Shihabudheen US","email":"d32f07aa6bc6aeff43d3d1837bb4b96684f8ad2c@dunzo.in"},"message":"added resume","distinct":true,"url":"https://api.github.com/repos/shihabus/tailwind-portfolio/commits/829165c7e2d065eb634d25c1f83f71d15abeaeb8"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339593","type":"IssuesEvent","actor":{"id":2748981,"login":"BenjaminSchaaf","display_login":"BenjaminSchaaf","gravatar_id":"","url":"https://api.github.com/users/BenjaminSchaaf","avatar_url":"https://avatars.githubusercontent.com/u/2748981?"},"repo":{"id":9598363,"name":"sublimehq/sublime_text","url":"https://api.github.com/repos/sublimehq/sublime_text"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676","repository_url":"https://api.github.com/repos/sublimehq/sublime_text","labels_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676/labels{/name}","comments_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676/comments","events_url":"https://api.github.com/repos/sublimehq/sublime_text/issues/4676/events","html_url":"https://github.com/sublimehq/sublime_text/issues/4676","id":949476971,"node_id":"MDU6SXNzdWU5NDk0NzY5NzE=","number":4676,"title":"Latex compiling is correct but opens an additional blank window","user":{"login":"jsbibra","id":55181504,"node_id":"MDQ6VXNlcjU1MTgxNTA0","avatar_url":"https://avatars.githubusercontent.com/u/55181504?v=4","gravatar_id":"","url":"https://api.github.com/users/jsbibra","html_url":"https://github.com/jsbibra","followers_url":"https://api.github.com/users/jsbibra/followers","following_url":"https://api.github.com/users/jsbibra/following{/other_user}","gists_url":"https://api.github.com/users/jsbibra/gists{/gist_id}","starred_url":"https://api.github.com/users/jsbibra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsbibra/subscriptions","organizations_url":"https://api.github.com/users/jsbibra/orgs","repos_url":"https://api.github.com/users/jsbibra/repos","events_url":"https://api.github.com/users/jsbibra/events{/privacy}","received_events_url":"https://api.github.com/users/jsbibra/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2021-07-21T08:47:48Z","updated_at":"2021-07-21T09:00:01Z","closed_at":"2021-07-21T09:00:01Z","author_association":"NONE","active_lock_reason":null,"body":"### Description\r\n\r\nAdditional Blank window on compile\r\n### Steps to reproduce\r\n\r\n1. Start Sublime Text \r\n2. compile latex code\r\n3. code compiles in sumatra but also opens up an additional blank window\r\n\r\n### Expected behavior\r\n\r\ncode should compile without opening an additional blank window\r\n\r\n### Actual behavior\r\n\r\n code compiles in sumatra but also opens up an additional blank window\r\n\r\n### Environment\r\n\r\n* Build: [e.g. 4113]\r\n* Operating system and version: [Windows 10, desktop PC]\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":1161584,"login":"sublimehq","gravatar_id":"","url":"https://api.github.com/orgs/sublimehq","avatar_url":"https://avatars.githubusercontent.com/u/1161584?"}} +{"id":"17246339598","type":"PushEvent","actor":{"id":87747615,"login":"mitchelmarushh775","display_login":"mitchelmarushh775","gravatar_id":"","url":"https://api.github.com/users/mitchelmarushh775","avatar_url":"https://avatars.githubusercontent.com/u/87747615?"},"repo":{"id":388008180,"name":"mitchelmarushh775/cnlkfhw4","url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw4"},"payload":{"push_id":7562451155,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"bd823744c3bc65789b76a0321996e0099d85623d","before":"817453691c1c109b0e00795d20ae8953b363c9fa","commits":[{"sha":"bd823744c3bc65789b76a0321996e0099d85623d","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw4/commits/bd823744c3bc65789b76a0321996e0099d85623d"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339602","type":"DeleteEvent","actor":{"id":10810283,"login":"direwolf-github","display_login":"direwolf-github","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","avatar_url":"https://avatars.githubusercontent.com/u/10810283?"},"repo":{"id":183051410,"name":"direwolf-github/my-app","url":"https://api.github.com/repos/direwolf-github/my-app"},"payload":{"ref":"branch-104803df","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339603","type":"PushEvent","actor":{"id":48682370,"login":"rikanakai0823","display_login":"rikanakai0823","gravatar_id":"","url":"https://api.github.com/users/rikanakai0823","avatar_url":"https://avatars.githubusercontent.com/u/48682370?"},"repo":{"id":176296125,"name":"rikanakai0823/bangumi-data","url":"https://api.github.com/repos/rikanakai0823/bangumi-data"},"payload":{"push_id":7562451149,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"642b89709d668aed0f7e503cf73b5fd409d142b9","before":"db0daa6b091a9a8ff03e003c866809720d33ffc8","commits":[{"sha":"642b89709d668aed0f7e503cf73b5fd409d142b9","author":{"name":"rikanakai0823","email":"b58812a1de528b10e45c95eac7749d14dececc61@users.noreply.github.com"},"message":"data update Wed Jul 21 06:00:05 PM JST 2021","distinct":true,"url":"https://api.github.com/repos/rikanakai0823/bangumi-data/commits/642b89709d668aed0f7e503cf73b5fd409d142b9"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339606","type":"PushEvent","actor":{"id":70032509,"login":"GulnazBakirova","display_login":"GulnazBakirova","gravatar_id":"","url":"https://api.github.com/users/GulnazBakirova","avatar_url":"https://avatars.githubusercontent.com/u/70032509?"},"repo":{"id":371379395,"name":"GulnazBakirova/1479343-keksobooking-23","url":"https://api.github.com/repos/GulnazBakirova/1479343-keksobooking-23"},"payload":{"push_id":7562451161,"size":1,"distinct_size":1,"ref":"refs/heads/patch-1","head":"73190845265c333040ef7308a57f682fc0bb6783","before":"2a83834a1b092fb68f9c97a2a5ce230720bacd6a","commits":[{"sha":"73190845265c333040ef7308a57f682fc0bb6783","author":{"name":"GulnazBakirova","email":"d4e59c81813944ad2eea0d9450b394ba3e0c416d@users.noreply.github.com"},"message":"Delete throttle.js","distinct":true,"url":"https://api.github.com/repos/GulnazBakirova/1479343-keksobooking-23/commits/73190845265c333040ef7308a57f682fc0bb6783"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339621","type":"PullRequestReviewEvent","actor":{"id":35995629,"login":"DmitryChitalov","display_login":"DmitryChitalov","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","avatar_url":"https://avatars.githubusercontent.com/u/35995629?"},"repo":{"id":336795774,"name":"DmitryChitalov/-algorithms_2021","url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021"},"payload":{"action":"created","review":{"id":711422067,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDIyMDY3","user":{"login":"DmitryChitalov","id":35995629,"node_id":"MDQ6VXNlcjM1OTk1NjI5","avatar_url":"https://avatars.githubusercontent.com/u/35995629?v=4","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","html_url":"https://github.com/DmitryChitalov","followers_url":"https://api.github.com/users/DmitryChitalov/followers","following_url":"https://api.github.com/users/DmitryChitalov/following{/other_user}","gists_url":"https://api.github.com/users/DmitryChitalov/gists{/gist_id}","starred_url":"https://api.github.com/users/DmitryChitalov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DmitryChitalov/subscriptions","organizations_url":"https://api.github.com/users/DmitryChitalov/orgs","repos_url":"https://api.github.com/users/DmitryChitalov/repos","events_url":"https://api.github.com/users/DmitryChitalov/events{/privacy}","received_events_url":"https://api.github.com/users/DmitryChitalov/received_events","type":"User","site_admin":false},"body":null,"commit_id":"785d22a4245376f077ebfb457b5533af2ebfc023","submitted_at":"2021-07-21T09:00:01Z","state":"commented","html_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253#pullrequestreview-711422067","pull_request_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253","author_association":"OWNER","_links":{"html":{"href":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253#pullrequestreview-711422067"},"pull_request":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253"}}},"pull_request":{"url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253","id":693635172,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNjM1MTcy","html_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253","diff_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253.diff","patch_url":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253.patch","issue_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253","number":1253,"state":"open","locked":false,"title":"less-3: 1,2,3","user":{"login":"staphw","id":76226605,"node_id":"MDQ6VXNlcjc2MjI2NjA1","avatar_url":"https://avatars.githubusercontent.com/u/76226605?v=4","gravatar_id":"","url":"https://api.github.com/users/staphw","html_url":"https://github.com/staphw","followers_url":"https://api.github.com/users/staphw/followers","following_url":"https://api.github.com/users/staphw/following{/other_user}","gists_url":"https://api.github.com/users/staphw/gists{/gist_id}","starred_url":"https://api.github.com/users/staphw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/staphw/subscriptions","organizations_url":"https://api.github.com/users/staphw/orgs","repos_url":"https://api.github.com/users/staphw/repos","events_url":"https://api.github.com/users/staphw/events{/privacy}","received_events_url":"https://api.github.com/users/staphw/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T16:53:32Z","updated_at":"2021-07-21T09:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3cd5cf184f6ff3957fc061f8e90753cc516f0792","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/commits","review_comments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/comments","review_comment_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/comments{/number}","comments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253/comments","statuses_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/statuses/785d22a4245376f077ebfb457b5533af2ebfc023","head":{"label":"staphw:lesson-3","ref":"lesson-3","sha":"785d22a4245376f077ebfb457b5533af2ebfc023","user":{"login":"staphw","id":76226605,"node_id":"MDQ6VXNlcjc2MjI2NjA1","avatar_url":"https://avatars.githubusercontent.com/u/76226605?v=4","gravatar_id":"","url":"https://api.github.com/users/staphw","html_url":"https://github.com/staphw","followers_url":"https://api.github.com/users/staphw/followers","following_url":"https://api.github.com/users/staphw/following{/other_user}","gists_url":"https://api.github.com/users/staphw/gists{/gist_id}","starred_url":"https://api.github.com/users/staphw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/staphw/subscriptions","organizations_url":"https://api.github.com/users/staphw/orgs","repos_url":"https://api.github.com/users/staphw/repos","events_url":"https://api.github.com/users/staphw/events{/privacy}","received_events_url":"https://api.github.com/users/staphw/received_events","type":"User","site_admin":false},"repo":{"id":384888939,"node_id":"MDEwOlJlcG9zaXRvcnkzODQ4ODg5Mzk=","name":"-algorithms_2021","full_name":"staphw/-algorithms_2021","private":false,"owner":{"login":"staphw","id":76226605,"node_id":"MDQ6VXNlcjc2MjI2NjA1","avatar_url":"https://avatars.githubusercontent.com/u/76226605?v=4","gravatar_id":"","url":"https://api.github.com/users/staphw","html_url":"https://github.com/staphw","followers_url":"https://api.github.com/users/staphw/followers","following_url":"https://api.github.com/users/staphw/following{/other_user}","gists_url":"https://api.github.com/users/staphw/gists{/gist_id}","starred_url":"https://api.github.com/users/staphw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/staphw/subscriptions","organizations_url":"https://api.github.com/users/staphw/orgs","repos_url":"https://api.github.com/users/staphw/repos","events_url":"https://api.github.com/users/staphw/events{/privacy}","received_events_url":"https://api.github.com/users/staphw/received_events","type":"User","site_admin":false},"html_url":"https://github.com/staphw/-algorithms_2021","description":null,"fork":true,"url":"https://api.github.com/repos/staphw/-algorithms_2021","forks_url":"https://api.github.com/repos/staphw/-algorithms_2021/forks","keys_url":"https://api.github.com/repos/staphw/-algorithms_2021/keys{/key_id}","collaborators_url":"https://api.github.com/repos/staphw/-algorithms_2021/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/staphw/-algorithms_2021/teams","hooks_url":"https://api.github.com/repos/staphw/-algorithms_2021/hooks","issue_events_url":"https://api.github.com/repos/staphw/-algorithms_2021/issues/events{/number}","events_url":"https://api.github.com/repos/staphw/-algorithms_2021/events","assignees_url":"https://api.github.com/repos/staphw/-algorithms_2021/assignees{/user}","branches_url":"https://api.github.com/repos/staphw/-algorithms_2021/branches{/branch}","tags_url":"https://api.github.com/repos/staphw/-algorithms_2021/tags","blobs_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/refs{/sha}","trees_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/trees{/sha}","statuses_url":"https://api.github.com/repos/staphw/-algorithms_2021/statuses/{sha}","languages_url":"https://api.github.com/repos/staphw/-algorithms_2021/languages","stargazers_url":"https://api.github.com/repos/staphw/-algorithms_2021/stargazers","contributors_url":"https://api.github.com/repos/staphw/-algorithms_2021/contributors","subscribers_url":"https://api.github.com/repos/staphw/-algorithms_2021/subscribers","subscription_url":"https://api.github.com/repos/staphw/-algorithms_2021/subscription","commits_url":"https://api.github.com/repos/staphw/-algorithms_2021/commits{/sha}","git_commits_url":"https://api.github.com/repos/staphw/-algorithms_2021/git/commits{/sha}","comments_url":"https://api.github.com/repos/staphw/-algorithms_2021/comments{/number}","issue_comment_url":"https://api.github.com/repos/staphw/-algorithms_2021/issues/comments{/number}","contents_url":"https://api.github.com/repos/staphw/-algorithms_2021/contents/{+path}","compare_url":"https://api.github.com/repos/staphw/-algorithms_2021/compare/{base}...{head}","merges_url":"https://api.github.com/repos/staphw/-algorithms_2021/merges","archive_url":"https://api.github.com/repos/staphw/-algorithms_2021/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/staphw/-algorithms_2021/downloads","issues_url":"https://api.github.com/repos/staphw/-algorithms_2021/issues{/number}","pulls_url":"https://api.github.com/repos/staphw/-algorithms_2021/pulls{/number}","milestones_url":"https://api.github.com/repos/staphw/-algorithms_2021/milestones{/number}","notifications_url":"https://api.github.com/repos/staphw/-algorithms_2021/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/staphw/-algorithms_2021/labels{/name}","releases_url":"https://api.github.com/repos/staphw/-algorithms_2021/releases{/id}","deployments_url":"https://api.github.com/repos/staphw/-algorithms_2021/deployments","created_at":"2021-07-11T07:45:27Z","updated_at":"2021-07-11T07:45:28Z","pushed_at":"2021-07-20T16:52:45Z","git_url":"git://github.com/staphw/-algorithms_2021.git","ssh_url":"git@github.com:staphw/-algorithms_2021.git","clone_url":"https://github.com/staphw/-algorithms_2021.git","svn_url":"https://github.com/staphw/-algorithms_2021","homepage":null,"size":77,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"DmitryChitalov:master","ref":"master","sha":"93d9a166e5a961abf555ace5cb8f3a246510a403","user":{"login":"DmitryChitalov","id":35995629,"node_id":"MDQ6VXNlcjM1OTk1NjI5","avatar_url":"https://avatars.githubusercontent.com/u/35995629?v=4","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","html_url":"https://github.com/DmitryChitalov","followers_url":"https://api.github.com/users/DmitryChitalov/followers","following_url":"https://api.github.com/users/DmitryChitalov/following{/other_user}","gists_url":"https://api.github.com/users/DmitryChitalov/gists{/gist_id}","starred_url":"https://api.github.com/users/DmitryChitalov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DmitryChitalov/subscriptions","organizations_url":"https://api.github.com/users/DmitryChitalov/orgs","repos_url":"https://api.github.com/users/DmitryChitalov/repos","events_url":"https://api.github.com/users/DmitryChitalov/events{/privacy}","received_events_url":"https://api.github.com/users/DmitryChitalov/received_events","type":"User","site_admin":false},"repo":{"id":336795774,"node_id":"MDEwOlJlcG9zaXRvcnkzMzY3OTU3NzQ=","name":"-algorithms_2021","full_name":"DmitryChitalov/-algorithms_2021","private":false,"owner":{"login":"DmitryChitalov","id":35995629,"node_id":"MDQ6VXNlcjM1OTk1NjI5","avatar_url":"https://avatars.githubusercontent.com/u/35995629?v=4","gravatar_id":"","url":"https://api.github.com/users/DmitryChitalov","html_url":"https://github.com/DmitryChitalov","followers_url":"https://api.github.com/users/DmitryChitalov/followers","following_url":"https://api.github.com/users/DmitryChitalov/following{/other_user}","gists_url":"https://api.github.com/users/DmitryChitalov/gists{/gist_id}","starred_url":"https://api.github.com/users/DmitryChitalov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DmitryChitalov/subscriptions","organizations_url":"https://api.github.com/users/DmitryChitalov/orgs","repos_url":"https://api.github.com/users/DmitryChitalov/repos","events_url":"https://api.github.com/users/DmitryChitalov/events{/privacy}","received_events_url":"https://api.github.com/users/DmitryChitalov/received_events","type":"User","site_admin":false},"html_url":"https://github.com/DmitryChitalov/-algorithms_2021","description":null,"fork":false,"url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021","forks_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/forks","keys_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/teams","hooks_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/hooks","issue_events_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/events{/number}","events_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/events","assignees_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/assignees{/user}","branches_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/branches{/branch}","tags_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/tags","blobs_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/refs{/sha}","trees_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/statuses/{sha}","languages_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/languages","stargazers_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/stargazers","contributors_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/contributors","subscribers_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/subscribers","subscription_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/subscription","commits_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/commits{/sha}","git_commits_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/git/commits{/sha}","comments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/comments{/number}","issue_comment_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/comments{/number}","contents_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/contents/{+path}","compare_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/merges","archive_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/downloads","issues_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues{/number}","pulls_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls{/number}","milestones_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/milestones{/number}","notifications_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/labels{/name}","releases_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/releases{/id}","deployments_url":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/deployments","created_at":"2021-02-07T13:39:06Z","updated_at":"2021-07-13T18:43:54Z","pushed_at":"2021-07-21T07:44:18Z","git_url":"git://github.com/DmitryChitalov/-algorithms_2021.git","ssh_url":"git@github.com:DmitryChitalov/-algorithms_2021.git","clone_url":"https://github.com/DmitryChitalov/-algorithms_2021.git","svn_url":"https://github.com/DmitryChitalov/-algorithms_2021","homepage":null,"size":72,"stargazers_count":4,"watchers_count":4,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":271,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1152,"license":null,"forks":271,"open_issues":1152,"watchers":4,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253"},"html":{"href":"https://github.com/DmitryChitalov/-algorithms_2021/pull/1253"},"issue":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253"},"comments":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/issues/1253/comments"},"review_comments":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/comments"},"review_comment":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/pulls/1253/commits"},"statuses":{"href":"https://api.github.com/repos/DmitryChitalov/-algorithms_2021/statuses/785d22a4245376f077ebfb457b5533af2ebfc023"}},"author_association":"NONE","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339630","type":"PushEvent","actor":{"id":87747615,"login":"mitchelmarushh775","display_login":"mitchelmarushh775","gravatar_id":"","url":"https://api.github.com/users/mitchelmarushh775","avatar_url":"https://avatars.githubusercontent.com/u/87747615?"},"repo":{"id":388008139,"name":"mitchelmarushh775/cnlkfhw2","url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw2"},"payload":{"push_id":7562451158,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"3cfe8d49ea6b8ce1b69e133bde1e2f257ac7fb26","before":"335117119c201f70f160ca6b2839521798041ae9","commits":[{"sha":"3cfe8d49ea6b8ce1b69e133bde1e2f257ac7fb26","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw2/commits/3cfe8d49ea6b8ce1b69e133bde1e2f257ac7fb26"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339637","type":"CreateEvent","actor":{"id":87711390,"login":"NehaCholle117","display_login":"NehaCholle117","gravatar_id":"","url":"https://api.github.com/users/NehaCholle117","avatar_url":"https://avatars.githubusercontent.com/u/87711390?"},"repo":{"id":388056962,"name":"NehaCholle117/College-management-system","url":"https://api.github.com/repos/NehaCholle117/College-management-system"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339647","type":"PushEvent","actor":{"id":85825531,"login":"ydmd111","display_login":"ydmd111","gravatar_id":"","url":"https://api.github.com/users/ydmd111","avatar_url":"https://avatars.githubusercontent.com/u/85825531?"},"repo":{"id":385971045,"name":"ydmd111/Python-machine_learning","url":"https://api.github.com/repos/ydmd111/Python-machine_learning"},"payload":{"push_id":7562451191,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f9ea360f2c1ca9d3b2cb62ef4e9227002551415b","before":"e634a0f5adb9f5c043c8477064ae5870111833a3","commits":[{"sha":"f9ea360f2c1ca9d3b2cb62ef4e9227002551415b","author":{"name":"ydmd111","email":"09b704b909e6a16419c05103b635cc2240e3bfaa@users.noreply.github.com"},"message":"Rename Training & Testing with PolynomialFeatures.ipynb to Training & Testing with PolynomialFeatures and fitting with LinearRegression.ipynb","distinct":true,"url":"https://api.github.com/repos/ydmd111/Python-machine_learning/commits/f9ea360f2c1ca9d3b2cb62ef4e9227002551415b"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339631","type":"PushEvent","actor":{"id":87747615,"login":"mitchelmarushh775","display_login":"mitchelmarushh775","gravatar_id":"","url":"https://api.github.com/users/mitchelmarushh775","avatar_url":"https://avatars.githubusercontent.com/u/87747615?"},"repo":{"id":388008109,"name":"mitchelmarushh775/cnlkfhw1","url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw1"},"payload":{"push_id":7562451180,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f42efe44e2031390f914130543052fda92a35489","before":"e70893baf9d5c8c9c42155e96d059d5d6cc3931e","commits":[{"sha":"f42efe44e2031390f914130543052fda92a35489","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/mitchelmarushh775/cnlkfhw1/commits/f42efe44e2031390f914130543052fda92a35489"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339644","type":"PushEvent","actor":{"id":73314663,"login":"eniolajohnson","display_login":"eniolajohnson","gravatar_id":"","url":"https://api.github.com/users/eniolajohnson","avatar_url":"https://avatars.githubusercontent.com/u/73314663?"},"repo":{"id":385756371,"name":"eniolajohnson/todo-skapp","url":"https://api.github.com/repos/eniolajohnson/todo-skapp"},"payload":{"push_id":7562451177,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8c6fa1f0b9de55d0317febc761d0ddae7602ef15","before":"9cb1f2425c30966d236589670e7253af8bc4fdbc","commits":[{"sha":"8c6fa1f0b9de55d0317febc761d0ddae7602ef15","author":{"name":"Eniola JOHNSON","email":"3d23e5f1d781de99e33ec800292bd0390cd0040f@gmail.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/eniolajohnson/todo-skapp/commits/8c6fa1f0b9de55d0317febc761d0ddae7602ef15"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339645","type":"CreateEvent","actor":{"id":33559353,"login":"ZarakiLancelot","display_login":"ZarakiLancelot","gravatar_id":"","url":"https://api.github.com/users/ZarakiLancelot","avatar_url":"https://avatars.githubusercontent.com/u/33559353?"},"repo":{"id":388056557,"name":"ZarakiLancelot/empleados","url":"https://api.github.com/repos/ZarakiLancelot/empleados"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":"Sistema de empelados en Angular","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339648","type":"CreateEvent","actor":{"id":79913779,"login":"conda-forge-curator[bot]","display_login":"conda-forge-curator","gravatar_id":"","url":"https://api.github.com/users/conda-forge-curator[bot]","avatar_url":"https://avatars.githubusercontent.com/u/79913779?"},"repo":{"id":288431736,"name":"conda-forge/releases","url":"https://api.github.com/repos/conda-forge/releases"},"payload":{"ref":"win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2","ref_type":"tag","master_branch":"master","description":"releases of conda-forge builds","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":11897326,"login":"conda-forge","gravatar_id":"","url":"https://api.github.com/orgs/conda-forge","avatar_url":"https://avatars.githubusercontent.com/u/11897326?"}} +{"id":"17246339649","type":"PushEvent","actor":{"id":25180681,"login":"renovate-bot","display_login":"renovate-bot","gravatar_id":"","url":"https://api.github.com/users/renovate-bot","avatar_url":"https://avatars.githubusercontent.com/u/25180681?"},"repo":{"id":219561466,"name":"renovate-bot/java-compute","url":"https://api.github.com/repos/renovate-bot/java-compute"},"payload":{"push_id":7562451163,"size":0,"distinct_size":0,"ref":"refs/heads/master","head":"bd689646b0c802ff735f56c727734fb4124e1554","before":"bd689646b0c802ff735f56c727734fb4124e1554","commits":[]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339651","type":"PullRequestEvent","actor":{"id":29516565,"login":"drodriguezhdez","display_login":"drodriguezhdez","gravatar_id":"","url":"https://api.github.com/users/drodriguezhdez","avatar_url":"https://avatars.githubusercontent.com/u/29516565?"},"repo":{"id":41455269,"name":"jenkinsci/datadog-plugin","url":"https://api.github.com/repos/jenkinsci/datadog-plugin"},"payload":{"action":"opened","number":234,"pull_request":{"url":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/234","id":694185710,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzEw","html_url":"https://github.com/jenkinsci/datadog-plugin/pull/234","diff_url":"https://github.com/jenkinsci/datadog-plugin/pull/234.diff","patch_url":"https://github.com/jenkinsci/datadog-plugin/pull/234.patch","issue_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/234","number":234,"state":"open","locked":false,"title":"Remove APM Java Tracer dependency","user":{"login":"drodriguezhdez","id":29516565,"node_id":"MDQ6VXNlcjI5NTE2NTY1","avatar_url":"https://avatars.githubusercontent.com/u/29516565?v=4","gravatar_id":"","url":"https://api.github.com/users/drodriguezhdez","html_url":"https://github.com/drodriguezhdez","followers_url":"https://api.github.com/users/drodriguezhdez/followers","following_url":"https://api.github.com/users/drodriguezhdez/following{/other_user}","gists_url":"https://api.github.com/users/drodriguezhdez/gists{/gist_id}","starred_url":"https://api.github.com/users/drodriguezhdez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drodriguezhdez/subscriptions","organizations_url":"https://api.github.com/users/drodriguezhdez/orgs","repos_url":"https://api.github.com/users/drodriguezhdez/repos","events_url":"https://api.github.com/users/drodriguezhdez/events{/privacy}","received_events_url":"https://api.github.com/users/drodriguezhdez/received_events","type":"User","site_admin":false},"body":"### Requirements for Contributing to this repository\r\n\r\n* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.\r\n* The pull request must only fix one issue at the time.\r\n* The pull request must update the test suite to demonstrate the changed functionality.\r\n* After you create the pull request, all status checks must be pass before a maintainer reviews your contribution. For more details, please see [CONTRIBUTING](/CONTRIBUTING.md). \r\n\r\n### What does this PR do?\r\n\r\n\r\n\r\n### Description of the Change\r\n\r\n\r\n\r\n### Alternate Designs\r\n\r\n\r\n\r\n### Possible Drawbacks\r\n\r\n\r\n\r\n### Verification Process\r\n\r\n\r\n\r\n### Additional Notes\r\n\r\n\r\n\r\n### Release Notes\r\n\r\n\r\n\r\n### Review checklist (to be filled by reviewers)\r\n\r\n- [ ] Feature or bug fix MUST have appropriate tests (unit, integration, etc...)\r\n- [ ] PR title must be written as a CHANGELOG entry [(see why)](/CONTRIBUTING.md#pull-request-title)\r\n- [ ] Files changes must correspond to the primary purpose of the PR as described in the title (small unrelated changes should have their own PR)\r\n- [ ] PR must have one `changelog/` label attached. If applicable it should have the `backward-incompatible` label attached.\r\n- [ ] PR should not have `do-not-merge/` label attached.\r\n- [ ] If Applicable, issue must have `kind/` and `severity/` labels attached at least.\r\n\r\n","created_at":"2021-07-21T09:00:01Z","updated_at":"2021-07-21T09:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":true,"commits_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/234/commits","review_comments_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/234/comments","review_comment_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/234/comments","statuses_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/statuses/0a07fbe519d7115ba552d421c085118943045d0a","head":{"label":"jenkinsci:drodriguezhdez/remove_java_tracer","ref":"drodriguezhdez/remove_java_tracer","sha":"0a07fbe519d7115ba552d421c085118943045d0a","user":{"login":"jenkinsci","id":107424,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNzQyNA==","avatar_url":"https://avatars.githubusercontent.com/u/107424?v=4","gravatar_id":"","url":"https://api.github.com/users/jenkinsci","html_url":"https://github.com/jenkinsci","followers_url":"https://api.github.com/users/jenkinsci/followers","following_url":"https://api.github.com/users/jenkinsci/following{/other_user}","gists_url":"https://api.github.com/users/jenkinsci/gists{/gist_id}","starred_url":"https://api.github.com/users/jenkinsci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jenkinsci/subscriptions","organizations_url":"https://api.github.com/users/jenkinsci/orgs","repos_url":"https://api.github.com/users/jenkinsci/repos","events_url":"https://api.github.com/users/jenkinsci/events{/privacy}","received_events_url":"https://api.github.com/users/jenkinsci/received_events","type":"Organization","site_admin":false},"repo":{"id":41455269,"node_id":"MDEwOlJlcG9zaXRvcnk0MTQ1NTI2OQ==","name":"datadog-plugin","full_name":"jenkinsci/datadog-plugin","private":false,"owner":{"login":"jenkinsci","id":107424,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNzQyNA==","avatar_url":"https://avatars.githubusercontent.com/u/107424?v=4","gravatar_id":"","url":"https://api.github.com/users/jenkinsci","html_url":"https://github.com/jenkinsci","followers_url":"https://api.github.com/users/jenkinsci/followers","following_url":"https://api.github.com/users/jenkinsci/following{/other_user}","gists_url":"https://api.github.com/users/jenkinsci/gists{/gist_id}","starred_url":"https://api.github.com/users/jenkinsci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jenkinsci/subscriptions","organizations_url":"https://api.github.com/users/jenkinsci/orgs","repos_url":"https://api.github.com/users/jenkinsci/repos","events_url":"https://api.github.com/users/jenkinsci/events{/privacy}","received_events_url":"https://api.github.com/users/jenkinsci/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/jenkinsci/datadog-plugin","description":"A Jenkins plugin used to forward metrics, events, and service checks to an account at Datadog, automatically.","fork":false,"url":"https://api.github.com/repos/jenkinsci/datadog-plugin","forks_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/forks","keys_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/teams","hooks_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/hooks","issue_events_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/events{/number}","events_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/events","assignees_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/assignees{/user}","branches_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/branches{/branch}","tags_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/tags","blobs_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/refs{/sha}","trees_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/statuses/{sha}","languages_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/languages","stargazers_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/stargazers","contributors_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/contributors","subscribers_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/subscribers","subscription_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/subscription","commits_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/commits{/sha}","git_commits_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/commits{/sha}","comments_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/comments{/number}","issue_comment_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/comments{/number}","contents_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/contents/{+path}","compare_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/merges","archive_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/downloads","issues_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues{/number}","pulls_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls{/number}","milestones_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/milestones{/number}","notifications_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/labels{/name}","releases_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/releases{/id}","deployments_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/deployments","created_at":"2015-08-26T23:41:56Z","updated_at":"2021-07-19T13:56:30Z","pushed_at":"2021-07-21T09:00:01Z","git_url":"git://github.com/jenkinsci/datadog-plugin.git","ssh_url":"git@github.com:jenkinsci/datadog-plugin.git","clone_url":"https://github.com/jenkinsci/datadog-plugin.git","svn_url":"https://github.com/jenkinsci/datadog-plugin","homepage":null,"size":2148,"stargazers_count":19,"watchers_count":19,"language":"Java","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":31,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":31,"open_issues":4,"watchers":19,"default_branch":"master"}},"base":{"label":"jenkinsci:master","ref":"master","sha":"a52cfa1fc67525e7ebcf81fb98f63b036689cdd5","user":{"login":"jenkinsci","id":107424,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNzQyNA==","avatar_url":"https://avatars.githubusercontent.com/u/107424?v=4","gravatar_id":"","url":"https://api.github.com/users/jenkinsci","html_url":"https://github.com/jenkinsci","followers_url":"https://api.github.com/users/jenkinsci/followers","following_url":"https://api.github.com/users/jenkinsci/following{/other_user}","gists_url":"https://api.github.com/users/jenkinsci/gists{/gist_id}","starred_url":"https://api.github.com/users/jenkinsci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jenkinsci/subscriptions","organizations_url":"https://api.github.com/users/jenkinsci/orgs","repos_url":"https://api.github.com/users/jenkinsci/repos","events_url":"https://api.github.com/users/jenkinsci/events{/privacy}","received_events_url":"https://api.github.com/users/jenkinsci/received_events","type":"Organization","site_admin":false},"repo":{"id":41455269,"node_id":"MDEwOlJlcG9zaXRvcnk0MTQ1NTI2OQ==","name":"datadog-plugin","full_name":"jenkinsci/datadog-plugin","private":false,"owner":{"login":"jenkinsci","id":107424,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNzQyNA==","avatar_url":"https://avatars.githubusercontent.com/u/107424?v=4","gravatar_id":"","url":"https://api.github.com/users/jenkinsci","html_url":"https://github.com/jenkinsci","followers_url":"https://api.github.com/users/jenkinsci/followers","following_url":"https://api.github.com/users/jenkinsci/following{/other_user}","gists_url":"https://api.github.com/users/jenkinsci/gists{/gist_id}","starred_url":"https://api.github.com/users/jenkinsci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jenkinsci/subscriptions","organizations_url":"https://api.github.com/users/jenkinsci/orgs","repos_url":"https://api.github.com/users/jenkinsci/repos","events_url":"https://api.github.com/users/jenkinsci/events{/privacy}","received_events_url":"https://api.github.com/users/jenkinsci/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/jenkinsci/datadog-plugin","description":"A Jenkins plugin used to forward metrics, events, and service checks to an account at Datadog, automatically.","fork":false,"url":"https://api.github.com/repos/jenkinsci/datadog-plugin","forks_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/forks","keys_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/teams","hooks_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/hooks","issue_events_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/events{/number}","events_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/events","assignees_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/assignees{/user}","branches_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/branches{/branch}","tags_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/tags","blobs_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/refs{/sha}","trees_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/statuses/{sha}","languages_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/languages","stargazers_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/stargazers","contributors_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/contributors","subscribers_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/subscribers","subscription_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/subscription","commits_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/commits{/sha}","git_commits_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/git/commits{/sha}","comments_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/comments{/number}","issue_comment_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/comments{/number}","contents_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/contents/{+path}","compare_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/merges","archive_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/downloads","issues_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues{/number}","pulls_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls{/number}","milestones_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/milestones{/number}","notifications_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/labels{/name}","releases_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/releases{/id}","deployments_url":"https://api.github.com/repos/jenkinsci/datadog-plugin/deployments","created_at":"2015-08-26T23:41:56Z","updated_at":"2021-07-19T13:56:30Z","pushed_at":"2021-07-21T09:00:01Z","git_url":"git://github.com/jenkinsci/datadog-plugin.git","ssh_url":"git@github.com:jenkinsci/datadog-plugin.git","clone_url":"https://github.com/jenkinsci/datadog-plugin.git","svn_url":"https://github.com/jenkinsci/datadog-plugin","homepage":null,"size":2148,"stargazers_count":19,"watchers_count":19,"language":"Java","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":31,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":31,"open_issues":4,"watchers":19,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/234"},"html":{"href":"https://github.com/jenkinsci/datadog-plugin/pull/234"},"issue":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/234"},"comments":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/issues/234/comments"},"review_comments":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/234/comments"},"review_comment":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/pulls/234/commits"},"statuses":{"href":"https://api.github.com/repos/jenkinsci/datadog-plugin/statuses/0a07fbe519d7115ba552d421c085118943045d0a"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"draft","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":18,"additions":1221,"deletions":776,"changed_files":30}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":107424,"login":"jenkinsci","gravatar_id":"","url":"https://api.github.com/orgs/jenkinsci","avatar_url":"https://avatars.githubusercontent.com/u/107424?"}} +{"id":"17246339661","type":"PushEvent","actor":{"id":87718722,"login":"alezfergusonn9054","display_login":"alezfergusonn9054","gravatar_id":"","url":"https://api.github.com/users/alezfergusonn9054","avatar_url":"https://avatars.githubusercontent.com/u/87718722?"},"repo":{"id":387866845,"name":"alezfergusonn9054/hfdbx3","url":"https://api.github.com/repos/alezfergusonn9054/hfdbx3"},"payload":{"push_id":7562451200,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"7892bfe24f4ae020da7605bf89fd8faa20861758","before":"98aeafff9c8bf6a52bdde602803cb51df55b8a11","commits":[{"sha":"7892bfe24f4ae020da7605bf89fd8faa20861758","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/alezfergusonn9054/hfdbx3/commits/7892bfe24f4ae020da7605bf89fd8faa20861758"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339670","type":"WatchEvent","actor":{"id":1403893,"login":"kenshinji","display_login":"kenshinji","gravatar_id":"","url":"https://api.github.com/users/kenshinji","avatar_url":"https://avatars.githubusercontent.com/u/1403893?"},"repo":{"id":170248802,"name":"kuoruan/openwrt-v2ray","url":"https://api.github.com/repos/kuoruan/openwrt-v2ray"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339675","type":"PushEvent","actor":{"id":86923688,"login":"XiaoBai-12138","display_login":"XiaoBai-12138","gravatar_id":"","url":"https://api.github.com/users/XiaoBai-12138","avatar_url":"https://avatars.githubusercontent.com/u/86923688?"},"repo":{"id":388050505,"name":"XiaoBai-12138/ezOFFICE","url":"https://api.github.com/repos/XiaoBai-12138/ezOFFICE"},"payload":{"push_id":7562451201,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f15679b090f345f9621b1bec6f5bbf8e0fbf14bb","before":"6f65f2f6b1a7540a0d41c4bb537c450180576507","commits":[{"sha":"f15679b090f345f9621b1bec6f5bbf8e0fbf14bb","author":{"name":"XiaoBai","email":"95a80b1b37372c3f06fccd813cad8ca7003ead1c@users.noreply.github.com"},"message":"Create README.md","distinct":true,"url":"https://api.github.com/repos/XiaoBai-12138/ezOFFICE/commits/f15679b090f345f9621b1bec6f5bbf8e0fbf14bb"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339680","type":"PushEvent","actor":{"id":87741920,"login":"mikataysonn2053","display_login":"mikataysonn2053","gravatar_id":"","url":"https://api.github.com/users/mikataysonn2053","avatar_url":"https://avatars.githubusercontent.com/u/87741920?"},"repo":{"id":387970629,"name":"mikataysonn2053/dsvdvd4","url":"https://api.github.com/repos/mikataysonn2053/dsvdvd4"},"payload":{"push_id":7562451185,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"ae1ffe82282ff9f765c4c34545c1f662d608140c","before":"42f1d7ebc9ff626b02334beb1fa61ee292ea49f9","commits":[{"sha":"ae1ffe82282ff9f765c4c34545c1f662d608140c","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/mikataysonn2053/dsvdvd4/commits/ae1ffe82282ff9f765c4c34545c1f662d608140c"}]},"public":true,"created_at":"2021-07-21T09:00:01Z"} +{"id":"17246339684","type":"IssueCommentEvent","actor":{"id":3595932,"login":"rouke-broersma","display_login":"rouke-broersma","gravatar_id":"","url":"https://api.github.com/users/rouke-broersma","avatar_url":"https://avatars.githubusercontent.com/u/3595932?"},"repo":{"id":120896210,"name":"argoproj/argo-cd","url":"https://api.github.com/repos/argoproj/argo-cd"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/argoproj/argo-cd/issues/4691","repository_url":"https://api.github.com/repos/argoproj/argo-cd","labels_url":"https://api.github.com/repos/argoproj/argo-cd/issues/4691/labels{/name}","comments_url":"https://api.github.com/repos/argoproj/argo-cd/issues/4691/comments","events_url":"https://api.github.com/repos/argoproj/argo-cd/issues/4691/events","html_url":"https://github.com/argoproj/argo-cd/issues/4691","id":731395926,"node_id":"MDU6SXNzdWU3MzEzOTU5MjY=","number":4691,"title":"Cluster name should be supported on AppProject CRD","user":{"login":"paveq","id":130256,"node_id":"MDQ6VXNlcjEzMDI1Ng==","avatar_url":"https://avatars.githubusercontent.com/u/130256?v=4","gravatar_id":"","url":"https://api.github.com/users/paveq","html_url":"https://github.com/paveq","followers_url":"https://api.github.com/users/paveq/followers","following_url":"https://api.github.com/users/paveq/following{/other_user}","gists_url":"https://api.github.com/users/paveq/gists{/gist_id}","starred_url":"https://api.github.com/users/paveq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paveq/subscriptions","organizations_url":"https://api.github.com/users/paveq/orgs","repos_url":"https://api.github.com/users/paveq/repos","events_url":"https://api.github.com/users/paveq/events{/privacy}","received_events_url":"https://api.github.com/users/paveq/received_events","type":"User","site_admin":false},"labels":[{"id":832247448,"node_id":"MDU6TGFiZWw4MzIyNDc0NDg=","url":"https://api.github.com/repos/argoproj/argo-cd/labels/enhancement","name":"enhancement","color":"a2eeef","default":true,"description":"New feature or request"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2020-10-28T12:26:07Z","updated_at":"2021-07-21T09:00:01Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"# Summary\r\n\r\nCluster name feature introduced in v1.7 should be supported in AppProjects as well.\r\n\r\n# Motivation\r\n\r\nShould be able to give easy to remember name, instead of URL for the destination clusters in AppProject CRD.\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/argoproj/argo-cd/issues/comments/884018700","html_url":"https://github.com/argoproj/argo-cd/issues/4691#issuecomment-884018700","issue_url":"https://api.github.com/repos/argoproj/argo-cd/issues/4691","id":884018700,"node_id":"IC_kwDOBzS60s40sQ4M","user":{"login":"rouke-broersma","id":3595932,"node_id":"MDQ6VXNlcjM1OTU5MzI=","avatar_url":"https://avatars.githubusercontent.com/u/3595932?v=4","gravatar_id":"","url":"https://api.github.com/users/rouke-broersma","html_url":"https://github.com/rouke-broersma","followers_url":"https://api.github.com/users/rouke-broersma/followers","following_url":"https://api.github.com/users/rouke-broersma/following{/other_user}","gists_url":"https://api.github.com/users/rouke-broersma/gists{/gist_id}","starred_url":"https://api.github.com/users/rouke-broersma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rouke-broersma/subscriptions","organizations_url":"https://api.github.com/users/rouke-broersma/orgs","repos_url":"https://api.github.com/users/rouke-broersma/repos","events_url":"https://api.github.com/users/rouke-broersma/events{/privacy}","received_events_url":"https://api.github.com/users/rouke-broersma/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:01Z","updated_at":"2021-07-21T09:00:01Z","author_association":"NONE","body":"We are looking for this feature as well because we use app-of-apps with argo to provision other clusters with argo from automation. This automation does not know the the api url is going to be but it does know the cluster name.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:01Z","org":{"id":30269780,"login":"argoproj","gravatar_id":"","url":"https://api.github.com/orgs/argoproj","avatar_url":"https://avatars.githubusercontent.com/u/30269780?"}} +{"id":"17246339689","type":"PushEvent","actor":{"id":81258380,"login":"Helikopter-Bojowy","display_login":"Helikopter-Bojowy","gravatar_id":"","url":"https://api.github.com/users/Helikopter-Bojowy","avatar_url":"https://avatars.githubusercontent.com/u/81258380?"},"repo":{"id":352069365,"name":"Helikopter-Bojowy/Exp-na-helikopterze","url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze"},"payload":{"push_id":7562451196,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"8e776b85651aa9b1f13ae7982e1ad40e0f727484","before":"2003d5f663f56005804f900324a569f18e221913","commits":[{"sha":"8e776b85651aa9b1f13ae7982e1ad40e0f727484","author":{"name":"Helikopter-Bojowy","email":"733707e04c8bf7c874ce97516f8f637586fc79d7@users.noreply.github.com"},"message":"1626858000857:0","distinct":true,"url":"https://api.github.com/repos/Helikopter-Bojowy/Exp-na-helikopterze/commits/8e776b85651aa9b1f13ae7982e1ad40e0f727484"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339692","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":319743689,"name":"major/cronjobs","url":"https://api.github.com/repos/major/cronjobs"},"payload":{"push_id":7562451210,"size":1,"distinct_size":1,"ref":"refs/heads/cache-db","head":"c814c541a4ebe93f486a9c3b86646ef9056253ed","before":"adc9c4b6f06cf1c45753b4b9ca612810004e7cbe","commits":[{"sha":"c814c541a4ebe93f486a9c3b86646ef9056253ed","author":{"name":"major","email":"18c9d4621ef6c3113ce1f960346d7603e88406e8@users.noreply.github.com"},"message":"Update cache-db to output generated at 22f7f7d","distinct":true,"url":"https://api.github.com/repos/major/cronjobs/commits/c814c541a4ebe93f486a9c3b86646ef9056253ed"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339707","type":"PushEvent","actor":{"id":30502993,"login":"a-rsc","display_login":"a-rsc","gravatar_id":"","url":"https://api.github.com/users/a-rsc","avatar_url":"https://avatars.githubusercontent.com/u/30502993?"},"repo":{"id":387214145,"name":"a-rsc/AprenderVueJs","url":"https://api.github.com/repos/a-rsc/AprenderVueJs"},"payload":{"push_id":7562451216,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"1c2de13d6d9505838d647f3d7e8a606364911573","before":"7e4ca914b4e137c218a78900347c40c75a5070e1","commits":[{"sha":"1c2de13d6d9505838d647f3d7e8a606364911573","author":{"name":"Alvaro","email":"863cb5dd62b263f0d9ad6cc29dad3f1ac57c467d@hotmail.com"},"message":"starting lesson 015","distinct":true,"url":"https://api.github.com/repos/a-rsc/AprenderVueJs/commits/1c2de13d6d9505838d647f3d7e8a606364911573"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339714","type":"IssueCommentEvent","actor":{"id":8723280,"login":"OCA-git-bot","display_login":"OCA-git-bot","gravatar_id":"","url":"https://api.github.com/users/OCA-git-bot","avatar_url":"https://avatars.githubusercontent.com/u/8723280?"},"repo":{"id":20881720,"name":"OCA/account-budgeting","url":"https://api.github.com/repos/OCA/account-budgeting"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/OCA/account-budgeting/issues/37","repository_url":"https://api.github.com/repos/OCA/account-budgeting","labels_url":"https://api.github.com/repos/OCA/account-budgeting/issues/37/labels{/name}","comments_url":"https://api.github.com/repos/OCA/account-budgeting/issues/37/comments","events_url":"https://api.github.com/repos/OCA/account-budgeting/issues/37/events","html_url":"https://github.com/OCA/account-budgeting/pull/37","id":583839305,"node_id":"MDExOlB1bGxSZXF1ZXN0MzkwNTI4MjQ2","number":37,"title":"13.0 mig account budget oca","user":{"login":"amcor","id":11444048,"node_id":"MDQ6VXNlcjExNDQ0MDQ4","avatar_url":"https://avatars.githubusercontent.com/u/11444048?v=4","gravatar_id":"","url":"https://api.github.com/users/amcor","html_url":"https://github.com/amcor","followers_url":"https://api.github.com/users/amcor/followers","following_url":"https://api.github.com/users/amcor/following{/other_user}","gists_url":"https://api.github.com/users/amcor/gists{/gist_id}","starred_url":"https://api.github.com/users/amcor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/amcor/subscriptions","organizations_url":"https://api.github.com/users/amcor/orgs","repos_url":"https://api.github.com/users/amcor/repos","events_url":"https://api.github.com/users/amcor/events{/privacy}","received_events_url":"https://api.github.com/users/amcor/received_events","type":"User","site_admin":false},"labels":[{"id":577768738,"node_id":"MDU6TGFiZWw1Nzc3Njg3Mzg=","url":"https://api.github.com/repos/OCA/account-budgeting/labels/approved","name":"approved","color":"045509","default":false,"description":null},{"id":1122897055,"node_id":"MDU6TGFiZWwxMTIyODk3MDU1","url":"https://api.github.com/repos/OCA/account-budgeting/labels/ready%20to%20merge","name":"ready to merge","color":"bfdadc","default":false,"description":null}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2020-03-18T16:07:28Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"author_association":"MEMBER","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/OCA/account-budgeting/pulls/37","html_url":"https://github.com/OCA/account-budgeting/pull/37","diff_url":"https://github.com/OCA/account-budgeting/pull/37.diff","patch_url":"https://github.com/OCA/account-budgeting/pull/37.patch"},"body":"cc @guadaltech\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/OCA/account-budgeting/issues/comments/884018703","html_url":"https://github.com/OCA/account-budgeting/pull/37#issuecomment-884018703","issue_url":"https://api.github.com/repos/OCA/account-budgeting/issues/37","id":884018703,"node_id":"IC_kwDOAT6hOM40sQ4P","user":{"login":"OCA-git-bot","id":8723280,"node_id":"MDQ6VXNlcjg3MjMyODA=","avatar_url":"https://avatars.githubusercontent.com/u/8723280?v=4","gravatar_id":"","url":"https://api.github.com/users/OCA-git-bot","html_url":"https://github.com/OCA-git-bot","followers_url":"https://api.github.com/users/OCA-git-bot/followers","following_url":"https://api.github.com/users/OCA-git-bot/following{/other_user}","gists_url":"https://api.github.com/users/OCA-git-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/OCA-git-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OCA-git-bot/subscriptions","organizations_url":"https://api.github.com/users/OCA-git-bot/orgs","repos_url":"https://api.github.com/users/OCA-git-bot/repos","events_url":"https://api.github.com/users/OCA-git-bot/events{/privacy}","received_events_url":"https://api.github.com/users/OCA-git-bot/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:01Z","updated_at":"2021-07-21T09:00:01Z","author_association":"CONTRIBUTOR","body":"This PR has the `approved` label and has been created more than 5 days ago. It should therefore be ready to merge by a maintainer (or a PSC member if the concerned addon has no declared maintainer). 🤖","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":7600578,"login":"OCA","gravatar_id":"","url":"https://api.github.com/orgs/OCA","avatar_url":"https://avatars.githubusercontent.com/u/7600578?"}} +{"id":"17246339718","type":"CreateEvent","actor":{"id":87453487,"login":"niji486","display_login":"niji486","gravatar_id":"","url":"https://api.github.com/users/niji486","avatar_url":"https://avatars.githubusercontent.com/u/87453487?"},"repo":{"id":388056809,"name":"niji486/Algorithm","url":"https://api.github.com/repos/niji486/Algorithm"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339723","type":"PushEvent","actor":{"id":87360801,"login":"bhavaniashok13","display_login":"bhavaniashok13","gravatar_id":"","url":"https://api.github.com/users/bhavaniashok13","avatar_url":"https://avatars.githubusercontent.com/u/87360801?"},"repo":{"id":387716469,"name":"bhavaniashok13/pipeline","url":"https://api.github.com/repos/bhavaniashok13/pipeline"},"payload":{"push_id":7562451221,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"69e756c9839f86aa48f0e8cad61aab4996962b57","before":"6a69ada17dde3ca6c0768d7c5944cc9a134a4c85","commits":[{"sha":"69e756c9839f86aa48f0e8cad61aab4996962b57","author":{"name":"bhavaniashok13","email":"8f485a11d102eba834c7220c50ab36cd434f08c4@users.noreply.github.com"},"message":"Update Jenkinsfile","distinct":true,"url":"https://api.github.com/repos/bhavaniashok13/pipeline/commits/69e756c9839f86aa48f0e8cad61aab4996962b57"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339733","type":"CreateEvent","actor":{"id":3036176,"login":"Chaosus","display_login":"Chaosus","gravatar_id":"","url":"https://api.github.com/users/Chaosus","avatar_url":"https://avatars.githubusercontent.com/u/3036176?"},"repo":{"id":111555494,"name":"Chaosus/godot","url":"https://api.github.com/repos/Chaosus/godot"},"payload":{"ref":"vs_fix_triplanar","ref_type":"branch","master_branch":"master","description":"Godot Engine – Multi-platform 2D and 3D game engine","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339734","type":"WatchEvent","actor":{"id":38719088,"login":"Questdream","display_login":"Questdream","gravatar_id":"","url":"https://api.github.com/users/Questdream","avatar_url":"https://avatars.githubusercontent.com/u/38719088?"},"repo":{"id":152000215,"name":"jayboxyz/deeplearning_cv_notes","url":"https://api.github.com/repos/jayboxyz/deeplearning_cv_notes"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339735","type":"PushEvent","actor":{"id":58331543,"login":"rahilkapoor","display_login":"rahilkapoor","gravatar_id":"","url":"https://api.github.com/users/rahilkapoor","avatar_url":"https://avatars.githubusercontent.com/u/58331543?"},"repo":{"id":375583030,"name":"rahilkapoor/rahilkapoor","url":"https://api.github.com/repos/rahilkapoor/rahilkapoor"},"payload":{"push_id":7562451223,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"4a51cba0254ef6f00e7d8214f445ce5a64926a5a","before":"da66797bd8d2d689c27b318550815abcf0a6ba4b","commits":[{"sha":"4a51cba0254ef6f00e7d8214f445ce5a64926a5a","author":{"name":"Rahil Kapoor","email":"c4fa9e6a4b4167b3d6c939549f43c36e63b6893a@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/rahilkapoor/rahilkapoor/commits/4a51cba0254ef6f00e7d8214f445ce5a64926a5a"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339738","type":"WatchEvent","actor":{"id":562984,"login":"sanbroz","display_login":"sanbroz","gravatar_id":"","url":"https://api.github.com/users/sanbroz","avatar_url":"https://avatars.githubusercontent.com/u/562984?"},"repo":{"id":351510790,"name":"Flameeyes/unpaper","url":"https://api.github.com/repos/Flameeyes/unpaper"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339739","type":"PullRequestEvent","actor":{"id":11707729,"login":"gabrieldonadel","display_login":"gabrieldonadel","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","avatar_url":"https://avatars.githubusercontent.com/u/11707729?"},"repo":{"id":387916158,"name":"gabrieldonadel/pull-requests-limits","url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits"},"payload":{"action":"opened","number":5533,"pull_request":{"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5533","id":694185713,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzEz","html_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5533","diff_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5533.diff","patch_url":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5533.patch","issue_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5533","number":5533,"state":"open","locked":false,"title":"Add file 5555.txt","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"body":null,"created_at":"2021-07-21T09:00:01Z","updated_at":"2021-07-21T09:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5533/commits","review_comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5533/comments","review_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5533/comments","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/a20ac5023d462e437e2ea484528f10e10a8934f7","head":{"label":"gabrieldonadel:5555","ref":"5555","sha":"a20ac5023d462e437e2ea484528f10e10a8934f7","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"repo":{"id":387916158,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MTYxNTg=","name":"pull-requests-limits","full_name":"gabrieldonadel/pull-requests-limits","private":false,"owner":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gabrieldonadel/pull-requests-limits","description":"Testing Github Pull requests limits","fork":false,"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits","forks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/forks","keys_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/teams","hooks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/hooks","issue_events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/events{/number}","events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/events","assignees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/assignees{/user}","branches_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/branches{/branch}","tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/tags","blobs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/refs{/sha}","trees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/{sha}","languages_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/languages","stargazers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/stargazers","contributors_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contributors","subscribers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscribers","subscription_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscription","commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/commits{/sha}","git_commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/commits{/sha}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/comments{/number}","issue_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/comments{/number}","contents_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contents/{+path}","compare_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/merges","archive_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/downloads","issues_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues{/number}","pulls_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls{/number}","milestones_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/milestones{/number}","notifications_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/labels{/name}","releases_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/releases{/id}","deployments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/deployments","created_at":"2021-07-20T21:17:40Z","updated_at":"2021-07-21T01:36:14Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/gabrieldonadel/pull-requests-limits.git","ssh_url":"git@github.com:gabrieldonadel/pull-requests-limits.git","clone_url":"https://github.com/gabrieldonadel/pull-requests-limits.git","svn_url":"https://github.com/gabrieldonadel/pull-requests-limits","homepage":null,"size":1196,"stargazers_count":1,"watchers_count":1,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5531,"license":null,"forks":0,"open_issues":5531,"watchers":1,"default_branch":"master"}},"base":{"label":"gabrieldonadel:master","ref":"master","sha":"d82cf4fd9e97a60cfebab6b8f7f85f9f8380b29b","user":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"repo":{"id":387916158,"node_id":"MDEwOlJlcG9zaXRvcnkzODc5MTYxNTg=","name":"pull-requests-limits","full_name":"gabrieldonadel/pull-requests-limits","private":false,"owner":{"login":"gabrieldonadel","id":11707729,"node_id":"MDQ6VXNlcjExNzA3NzI5","avatar_url":"https://avatars.githubusercontent.com/u/11707729?v=4","gravatar_id":"","url":"https://api.github.com/users/gabrieldonadel","html_url":"https://github.com/gabrieldonadel","followers_url":"https://api.github.com/users/gabrieldonadel/followers","following_url":"https://api.github.com/users/gabrieldonadel/following{/other_user}","gists_url":"https://api.github.com/users/gabrieldonadel/gists{/gist_id}","starred_url":"https://api.github.com/users/gabrieldonadel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabrieldonadel/subscriptions","organizations_url":"https://api.github.com/users/gabrieldonadel/orgs","repos_url":"https://api.github.com/users/gabrieldonadel/repos","events_url":"https://api.github.com/users/gabrieldonadel/events{/privacy}","received_events_url":"https://api.github.com/users/gabrieldonadel/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gabrieldonadel/pull-requests-limits","description":"Testing Github Pull requests limits","fork":false,"url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits","forks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/forks","keys_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/teams","hooks_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/hooks","issue_events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/events{/number}","events_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/events","assignees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/assignees{/user}","branches_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/branches{/branch}","tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/tags","blobs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/refs{/sha}","trees_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/{sha}","languages_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/languages","stargazers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/stargazers","contributors_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contributors","subscribers_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscribers","subscription_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/subscription","commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/commits{/sha}","git_commits_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/git/commits{/sha}","comments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/comments{/number}","issue_comment_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/comments{/number}","contents_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/contents/{+path}","compare_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/merges","archive_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/downloads","issues_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues{/number}","pulls_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls{/number}","milestones_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/milestones{/number}","notifications_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/labels{/name}","releases_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/releases{/id}","deployments_url":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/deployments","created_at":"2021-07-20T21:17:40Z","updated_at":"2021-07-21T01:36:14Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/gabrieldonadel/pull-requests-limits.git","ssh_url":"git@github.com:gabrieldonadel/pull-requests-limits.git","clone_url":"https://github.com/gabrieldonadel/pull-requests-limits.git","svn_url":"https://github.com/gabrieldonadel/pull-requests-limits","homepage":null,"size":1196,"stargazers_count":1,"watchers_count":1,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5531,"license":null,"forks":0,"open_issues":5531,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5533"},"html":{"href":"https://github.com/gabrieldonadel/pull-requests-limits/pull/5533"},"issue":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5533"},"comments":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/issues/5533/comments"},"review_comments":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5533/comments"},"review_comment":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/pulls/5533/commits"},"statuses":{"href":"https://api.github.com/repos/gabrieldonadel/pull-requests-limits/statuses/a20ac5023d462e437e2ea484528f10e10a8934f7"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":0,"changed_files":1}},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339741","type":"PushEvent","actor":{"id":82897431,"login":"ConveroMursa","display_login":"ConveroMursa","gravatar_id":"","url":"https://api.github.com/users/ConveroMursa","avatar_url":"https://avatars.githubusercontent.com/u/82897431?"},"repo":{"id":363058430,"name":"ConveroMursa/pyda_homeworks","url":"https://api.github.com/repos/ConveroMursa/pyda_homeworks"},"payload":{"push_id":7562451225,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8d3701ed6bba0b82ddb3a64a09d5b7f207b35a52","before":"feffe89746f056eca1ee698e629e172062335dd8","commits":[{"sha":"8d3701ed6bba0b82ddb3a64a09d5b7f207b35a52","author":{"name":"ConveroMursa","email":"8af7dc8ad633ccac5f53b51f69b5238f98a54821@gmail.com"},"message":"add HW corr and regr","distinct":true,"url":"https://api.github.com/repos/ConveroMursa/pyda_homeworks/commits/8d3701ed6bba0b82ddb3a64a09d5b7f207b35a52"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339746","type":"PushEvent","actor":{"id":87330551,"login":"xz241","display_login":"xz241","gravatar_id":"","url":"https://api.github.com/users/xz241","avatar_url":"https://avatars.githubusercontent.com/u/87330551?"},"repo":{"id":385328687,"name":"xz241/wp-uploads","url":"https://api.github.com/repos/xz241/wp-uploads"},"payload":{"push_id":7562451247,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"1d84d885421cf1a0f94f8720579a53734457c27d","before":"ce7556e4c681ba0ee5334ee87ffa9cfd612a7916","commits":[{"sha":"1d84d885421cf1a0f94f8720579a53734457c27d","author":{"name":"xz241","email":"e17353502d0344fb9b4d10b6a471cc23c11850ec@users.noreply.github.com"},"message":"","distinct":true,"url":"https://api.github.com/repos/xz241/wp-uploads/commits/1d84d885421cf1a0f94f8720579a53734457c27d"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339756","type":"PullRequestEvent","actor":{"id":74347264,"login":"boost-e2e-tester-user","display_login":"boost-e2e-tester-user","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-tester-user","avatar_url":"https://avatars.githubusercontent.com/u/74347264?"},"repo":{"id":376948559,"name":"boost-e2e-stage-ci-buildkite/pr-comments","url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments"},"payload":{"action":"opened","number":3695,"pull_request":{"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3695","id":694185715,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzE1","html_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3695","diff_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3695.diff","patch_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3695.patch","issue_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3695","number":3695,"state":"open","locked":false,"title":"Test PR Comment","user":{"login":"boost-e2e-tester-user","id":74347264,"node_id":"MDQ6VXNlcjc0MzQ3MjY0","avatar_url":"https://avatars.githubusercontent.com/u/74347264?v=4","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-tester-user","html_url":"https://github.com/boost-e2e-tester-user","followers_url":"https://api.github.com/users/boost-e2e-tester-user/followers","following_url":"https://api.github.com/users/boost-e2e-tester-user/following{/other_user}","gists_url":"https://api.github.com/users/boost-e2e-tester-user/gists{/gist_id}","starred_url":"https://api.github.com/users/boost-e2e-tester-user/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boost-e2e-tester-user/subscriptions","organizations_url":"https://api.github.com/users/boost-e2e-tester-user/orgs","repos_url":"https://api.github.com/users/boost-e2e-tester-user/repos","events_url":"https://api.github.com/users/boost-e2e-tester-user/events{/privacy}","received_events_url":"https://api.github.com/users/boost-e2e-tester-user/received_events","type":"User","site_admin":false},"body":null,"created_at":"2021-07-21T09:00:01Z","updated_at":"2021-07-21T09:00:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3695/commits","review_comments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3695/comments","review_comment_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/comments{/number}","comments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3695/comments","statuses_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/statuses/cbfe19073ba710838d05858cdce5a23bc3c57894","head":{"label":"boost-e2e-stage-ci-buildkite:pr-comment","ref":"pr-comment","sha":"cbfe19073ba710838d05858cdce5a23bc3c57894","user":{"login":"boost-e2e-stage-ci-buildkite","id":81248900,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgxMjQ4OTAw","avatar_url":"https://avatars.githubusercontent.com/u/81248900?v=4","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite","html_url":"https://github.com/boost-e2e-stage-ci-buildkite","followers_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/followers","following_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/following{/other_user}","gists_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/gists{/gist_id}","starred_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/subscriptions","organizations_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/orgs","repos_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/repos","events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/events{/privacy}","received_events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/received_events","type":"Organization","site_admin":false},"repo":{"id":376948559,"node_id":"MDEwOlJlcG9zaXRvcnkzNzY5NDg1NTk=","name":"pr-comments","full_name":"boost-e2e-stage-ci-buildkite/pr-comments","private":false,"owner":{"login":"boost-e2e-stage-ci-buildkite","id":81248900,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgxMjQ4OTAw","avatar_url":"https://avatars.githubusercontent.com/u/81248900?v=4","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite","html_url":"https://github.com/boost-e2e-stage-ci-buildkite","followers_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/followers","following_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/following{/other_user}","gists_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/gists{/gist_id}","starred_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/subscriptions","organizations_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/orgs","repos_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/repos","events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/events{/privacy}","received_events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments","description":null,"fork":false,"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments","forks_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/forks","keys_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/keys{/key_id}","collaborators_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/teams","hooks_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/hooks","issue_events_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/events{/number}","events_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/events","assignees_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/assignees{/user}","branches_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/branches{/branch}","tags_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/tags","blobs_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/refs{/sha}","trees_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/trees{/sha}","statuses_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/statuses/{sha}","languages_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/languages","stargazers_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/stargazers","contributors_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/contributors","subscribers_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/subscribers","subscription_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/subscription","commits_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/commits{/sha}","git_commits_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/commits{/sha}","comments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/comments{/number}","issue_comment_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/comments{/number}","contents_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/contents/{+path}","compare_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/compare/{base}...{head}","merges_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/merges","archive_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/downloads","issues_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues{/number}","pulls_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls{/number}","milestones_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/milestones{/number}","notifications_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/labels{/name}","releases_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/releases{/id}","deployments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/deployments","created_at":"2021-06-14T20:31:07Z","updated_at":"2021-07-21T08:59:57Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/boost-e2e-stage-ci-buildkite/pr-comments.git","ssh_url":"git@github.com:boost-e2e-stage-ci-buildkite/pr-comments.git","clone_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments.git","svn_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments","homepage":null,"size":5,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"boost-e2e-stage-ci-buildkite:master","ref":"master","sha":"8402da60c61dd3efa44f3424268d09bbf44d850c","user":{"login":"boost-e2e-stage-ci-buildkite","id":81248900,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgxMjQ4OTAw","avatar_url":"https://avatars.githubusercontent.com/u/81248900?v=4","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite","html_url":"https://github.com/boost-e2e-stage-ci-buildkite","followers_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/followers","following_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/following{/other_user}","gists_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/gists{/gist_id}","starred_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/subscriptions","organizations_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/orgs","repos_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/repos","events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/events{/privacy}","received_events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/received_events","type":"Organization","site_admin":false},"repo":{"id":376948559,"node_id":"MDEwOlJlcG9zaXRvcnkzNzY5NDg1NTk=","name":"pr-comments","full_name":"boost-e2e-stage-ci-buildkite/pr-comments","private":false,"owner":{"login":"boost-e2e-stage-ci-buildkite","id":81248900,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgxMjQ4OTAw","avatar_url":"https://avatars.githubusercontent.com/u/81248900?v=4","gravatar_id":"","url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite","html_url":"https://github.com/boost-e2e-stage-ci-buildkite","followers_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/followers","following_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/following{/other_user}","gists_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/gists{/gist_id}","starred_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/subscriptions","organizations_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/orgs","repos_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/repos","events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/events{/privacy}","received_events_url":"https://api.github.com/users/boost-e2e-stage-ci-buildkite/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments","description":null,"fork":false,"url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments","forks_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/forks","keys_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/keys{/key_id}","collaborators_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/teams","hooks_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/hooks","issue_events_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/events{/number}","events_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/events","assignees_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/assignees{/user}","branches_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/branches{/branch}","tags_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/tags","blobs_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/refs{/sha}","trees_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/trees{/sha}","statuses_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/statuses/{sha}","languages_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/languages","stargazers_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/stargazers","contributors_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/contributors","subscribers_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/subscribers","subscription_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/subscription","commits_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/commits{/sha}","git_commits_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/git/commits{/sha}","comments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/comments{/number}","issue_comment_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/comments{/number}","contents_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/contents/{+path}","compare_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/compare/{base}...{head}","merges_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/merges","archive_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/downloads","issues_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues{/number}","pulls_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls{/number}","milestones_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/milestones{/number}","notifications_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/labels{/name}","releases_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/releases{/id}","deployments_url":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/deployments","created_at":"2021-06-14T20:31:07Z","updated_at":"2021-07-21T08:59:57Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/boost-e2e-stage-ci-buildkite/pr-comments.git","ssh_url":"git@github.com:boost-e2e-stage-ci-buildkite/pr-comments.git","clone_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments.git","svn_url":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments","homepage":null,"size":5,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3695"},"html":{"href":"https://github.com/boost-e2e-stage-ci-buildkite/pr-comments/pull/3695"},"issue":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3695"},"comments":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/issues/3695/comments"},"review_comments":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3695/comments"},"review_comment":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/pulls/3695/commits"},"statuses":{"href":"https://api.github.com/repos/boost-e2e-stage-ci-buildkite/pr-comments/statuses/cbfe19073ba710838d05858cdce5a23bc3c57894"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":4,"deletions":0,"changed_files":1}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":81248900,"login":"boost-e2e-stage-ci-buildkite","gravatar_id":"","url":"https://api.github.com/orgs/boost-e2e-stage-ci-buildkite","avatar_url":"https://avatars.githubusercontent.com/u/81248900?"}} +{"id":"17246339757","type":"PullRequestReviewEvent","actor":{"id":54418919,"login":"pascal-klesse","display_login":"pascal-klesse","gravatar_id":"","url":"https://api.github.com/users/pascal-klesse","avatar_url":"https://avatars.githubusercontent.com/u/54418919?"},"repo":{"id":379499395,"name":"lenneTech/academy","url":"https://api.github.com/repos/lenneTech/academy"},"payload":{"action":"created","review":{"id":711422083,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDIyMDgz","user":{"login":"pascal-klesse","id":54418919,"node_id":"MDQ6VXNlcjU0NDE4OTE5","avatar_url":"https://avatars.githubusercontent.com/u/54418919?v=4","gravatar_id":"","url":"https://api.github.com/users/pascal-klesse","html_url":"https://github.com/pascal-klesse","followers_url":"https://api.github.com/users/pascal-klesse/followers","following_url":"https://api.github.com/users/pascal-klesse/following{/other_user}","gists_url":"https://api.github.com/users/pascal-klesse/gists{/gist_id}","starred_url":"https://api.github.com/users/pascal-klesse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pascal-klesse/subscriptions","organizations_url":"https://api.github.com/users/pascal-klesse/orgs","repos_url":"https://api.github.com/users/pascal-klesse/repos","events_url":"https://api.github.com/users/pascal-klesse/events{/privacy}","received_events_url":"https://api.github.com/users/pascal-klesse/received_events","type":"User","site_admin":false},"body":"","commit_id":"6437e242af4138fec51c91062b5c5398f8593bbb","submitted_at":"2021-07-21T09:00:01Z","state":"approved","html_url":"https://github.com/lenneTech/academy/pull/63#pullrequestreview-711422083","pull_request_url":"https://api.github.com/repos/lenneTech/academy/pulls/63","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/lenneTech/academy/pull/63#pullrequestreview-711422083"},"pull_request":{"href":"https://api.github.com/repos/lenneTech/academy/pulls/63"}}},"pull_request":{"url":"https://api.github.com/repos/lenneTech/academy/pulls/63","id":693404082,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDA0MDgy","html_url":"https://github.com/lenneTech/academy/pull/63","diff_url":"https://github.com/lenneTech/academy/pull/63.diff","patch_url":"https://github.com/lenneTech/academy/pull/63.patch","issue_url":"https://api.github.com/repos/lenneTech/academy/issues/63","number":63,"state":"open","locked":false,"title":"added section-navigation","user":{"login":"DKoenig9","id":85439028,"node_id":"MDQ6VXNlcjg1NDM5MDI4","avatar_url":"https://avatars.githubusercontent.com/u/85439028?v=4","gravatar_id":"","url":"https://api.github.com/users/DKoenig9","html_url":"https://github.com/DKoenig9","followers_url":"https://api.github.com/users/DKoenig9/followers","following_url":"https://api.github.com/users/DKoenig9/following{/other_user}","gists_url":"https://api.github.com/users/DKoenig9/gists{/gist_id}","starred_url":"https://api.github.com/users/DKoenig9/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DKoenig9/subscriptions","organizations_url":"https://api.github.com/users/DKoenig9/orgs","repos_url":"https://api.github.com/users/DKoenig9/repos","events_url":"https://api.github.com/users/DKoenig9/events{/privacy}","received_events_url":"https://api.github.com/users/DKoenig9/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T11:56:55Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7fc2873c7c1e3bc27c6f3dfdfdd9d1dfdd15fef2","assignee":null,"assignees":[],"requested_reviewers":[{"login":"FSteinhansesLT","id":82445251,"node_id":"MDQ6VXNlcjgyNDQ1MjUx","avatar_url":"https://avatars.githubusercontent.com/u/82445251?v=4","gravatar_id":"","url":"https://api.github.com/users/FSteinhansesLT","html_url":"https://github.com/FSteinhansesLT","followers_url":"https://api.github.com/users/FSteinhansesLT/followers","following_url":"https://api.github.com/users/FSteinhansesLT/following{/other_user}","gists_url":"https://api.github.com/users/FSteinhansesLT/gists{/gist_id}","starred_url":"https://api.github.com/users/FSteinhansesLT/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/FSteinhansesLT/subscriptions","organizations_url":"https://api.github.com/users/FSteinhansesLT/orgs","repos_url":"https://api.github.com/users/FSteinhansesLT/repos","events_url":"https://api.github.com/users/FSteinhansesLT/events{/privacy}","received_events_url":"https://api.github.com/users/FSteinhansesLT/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/lenneTech/academy/pulls/63/commits","review_comments_url":"https://api.github.com/repos/lenneTech/academy/pulls/63/comments","review_comment_url":"https://api.github.com/repos/lenneTech/academy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lenneTech/academy/issues/63/comments","statuses_url":"https://api.github.com/repos/lenneTech/academy/statuses/6437e242af4138fec51c91062b5c5398f8593bbb","head":{"label":"lenneTech:academy/section-navigation","ref":"academy/section-navigation","sha":"6437e242af4138fec51c91062b5c5398f8593bbb","user":{"login":"lenneTech","id":51728218,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNzI4MjE4","avatar_url":"https://avatars.githubusercontent.com/u/51728218?v=4","gravatar_id":"","url":"https://api.github.com/users/lenneTech","html_url":"https://github.com/lenneTech","followers_url":"https://api.github.com/users/lenneTech/followers","following_url":"https://api.github.com/users/lenneTech/following{/other_user}","gists_url":"https://api.github.com/users/lenneTech/gists{/gist_id}","starred_url":"https://api.github.com/users/lenneTech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lenneTech/subscriptions","organizations_url":"https://api.github.com/users/lenneTech/orgs","repos_url":"https://api.github.com/users/lenneTech/repos","events_url":"https://api.github.com/users/lenneTech/events{/privacy}","received_events_url":"https://api.github.com/users/lenneTech/received_events","type":"Organization","site_admin":false},"repo":{"id":379499395,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk0OTkzOTU=","name":"academy","full_name":"lenneTech/academy","private":false,"owner":{"login":"lenneTech","id":51728218,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNzI4MjE4","avatar_url":"https://avatars.githubusercontent.com/u/51728218?v=4","gravatar_id":"","url":"https://api.github.com/users/lenneTech","html_url":"https://github.com/lenneTech","followers_url":"https://api.github.com/users/lenneTech/followers","following_url":"https://api.github.com/users/lenneTech/following{/other_user}","gists_url":"https://api.github.com/users/lenneTech/gists{/gist_id}","starred_url":"https://api.github.com/users/lenneTech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lenneTech/subscriptions","organizations_url":"https://api.github.com/users/lenneTech/orgs","repos_url":"https://api.github.com/users/lenneTech/repos","events_url":"https://api.github.com/users/lenneTech/events{/privacy}","received_events_url":"https://api.github.com/users/lenneTech/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/lenneTech/academy","description":"An online learning platform for learning web development. ","fork":false,"url":"https://api.github.com/repos/lenneTech/academy","forks_url":"https://api.github.com/repos/lenneTech/academy/forks","keys_url":"https://api.github.com/repos/lenneTech/academy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lenneTech/academy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lenneTech/academy/teams","hooks_url":"https://api.github.com/repos/lenneTech/academy/hooks","issue_events_url":"https://api.github.com/repos/lenneTech/academy/issues/events{/number}","events_url":"https://api.github.com/repos/lenneTech/academy/events","assignees_url":"https://api.github.com/repos/lenneTech/academy/assignees{/user}","branches_url":"https://api.github.com/repos/lenneTech/academy/branches{/branch}","tags_url":"https://api.github.com/repos/lenneTech/academy/tags","blobs_url":"https://api.github.com/repos/lenneTech/academy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lenneTech/academy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lenneTech/academy/git/refs{/sha}","trees_url":"https://api.github.com/repos/lenneTech/academy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lenneTech/academy/statuses/{sha}","languages_url":"https://api.github.com/repos/lenneTech/academy/languages","stargazers_url":"https://api.github.com/repos/lenneTech/academy/stargazers","contributors_url":"https://api.github.com/repos/lenneTech/academy/contributors","subscribers_url":"https://api.github.com/repos/lenneTech/academy/subscribers","subscription_url":"https://api.github.com/repos/lenneTech/academy/subscription","commits_url":"https://api.github.com/repos/lenneTech/academy/commits{/sha}","git_commits_url":"https://api.github.com/repos/lenneTech/academy/git/commits{/sha}","comments_url":"https://api.github.com/repos/lenneTech/academy/comments{/number}","issue_comment_url":"https://api.github.com/repos/lenneTech/academy/issues/comments{/number}","contents_url":"https://api.github.com/repos/lenneTech/academy/contents/{+path}","compare_url":"https://api.github.com/repos/lenneTech/academy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lenneTech/academy/merges","archive_url":"https://api.github.com/repos/lenneTech/academy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lenneTech/academy/downloads","issues_url":"https://api.github.com/repos/lenneTech/academy/issues{/number}","pulls_url":"https://api.github.com/repos/lenneTech/academy/pulls{/number}","milestones_url":"https://api.github.com/repos/lenneTech/academy/milestones{/number}","notifications_url":"https://api.github.com/repos/lenneTech/academy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lenneTech/academy/labels{/name}","releases_url":"https://api.github.com/repos/lenneTech/academy/releases{/id}","deployments_url":"https://api.github.com/repos/lenneTech/academy/deployments","created_at":"2021-06-23T06:17:06Z","updated_at":"2021-07-20T19:03:21Z","pushed_at":"2021-07-21T08:51:09Z","git_url":"git://github.com/lenneTech/academy.git","ssh_url":"git@github.com:lenneTech/academy.git","clone_url":"https://github.com/lenneTech/academy.git","svn_url":"https://github.com/lenneTech/academy","homepage":"https://akademie.lenne.tech","size":14842,"stargazers_count":1,"watchers_count":1,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":1,"open_issues":1,"watchers":1,"default_branch":"main"}},"base":{"label":"lenneTech:main","ref":"main","sha":"9aacde5ce6f5a580208e002283c22c3afd54135f","user":{"login":"lenneTech","id":51728218,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNzI4MjE4","avatar_url":"https://avatars.githubusercontent.com/u/51728218?v=4","gravatar_id":"","url":"https://api.github.com/users/lenneTech","html_url":"https://github.com/lenneTech","followers_url":"https://api.github.com/users/lenneTech/followers","following_url":"https://api.github.com/users/lenneTech/following{/other_user}","gists_url":"https://api.github.com/users/lenneTech/gists{/gist_id}","starred_url":"https://api.github.com/users/lenneTech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lenneTech/subscriptions","organizations_url":"https://api.github.com/users/lenneTech/orgs","repos_url":"https://api.github.com/users/lenneTech/repos","events_url":"https://api.github.com/users/lenneTech/events{/privacy}","received_events_url":"https://api.github.com/users/lenneTech/received_events","type":"Organization","site_admin":false},"repo":{"id":379499395,"node_id":"MDEwOlJlcG9zaXRvcnkzNzk0OTkzOTU=","name":"academy","full_name":"lenneTech/academy","private":false,"owner":{"login":"lenneTech","id":51728218,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUxNzI4MjE4","avatar_url":"https://avatars.githubusercontent.com/u/51728218?v=4","gravatar_id":"","url":"https://api.github.com/users/lenneTech","html_url":"https://github.com/lenneTech","followers_url":"https://api.github.com/users/lenneTech/followers","following_url":"https://api.github.com/users/lenneTech/following{/other_user}","gists_url":"https://api.github.com/users/lenneTech/gists{/gist_id}","starred_url":"https://api.github.com/users/lenneTech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lenneTech/subscriptions","organizations_url":"https://api.github.com/users/lenneTech/orgs","repos_url":"https://api.github.com/users/lenneTech/repos","events_url":"https://api.github.com/users/lenneTech/events{/privacy}","received_events_url":"https://api.github.com/users/lenneTech/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/lenneTech/academy","description":"An online learning platform for learning web development. ","fork":false,"url":"https://api.github.com/repos/lenneTech/academy","forks_url":"https://api.github.com/repos/lenneTech/academy/forks","keys_url":"https://api.github.com/repos/lenneTech/academy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lenneTech/academy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lenneTech/academy/teams","hooks_url":"https://api.github.com/repos/lenneTech/academy/hooks","issue_events_url":"https://api.github.com/repos/lenneTech/academy/issues/events{/number}","events_url":"https://api.github.com/repos/lenneTech/academy/events","assignees_url":"https://api.github.com/repos/lenneTech/academy/assignees{/user}","branches_url":"https://api.github.com/repos/lenneTech/academy/branches{/branch}","tags_url":"https://api.github.com/repos/lenneTech/academy/tags","blobs_url":"https://api.github.com/repos/lenneTech/academy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lenneTech/academy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lenneTech/academy/git/refs{/sha}","trees_url":"https://api.github.com/repos/lenneTech/academy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lenneTech/academy/statuses/{sha}","languages_url":"https://api.github.com/repos/lenneTech/academy/languages","stargazers_url":"https://api.github.com/repos/lenneTech/academy/stargazers","contributors_url":"https://api.github.com/repos/lenneTech/academy/contributors","subscribers_url":"https://api.github.com/repos/lenneTech/academy/subscribers","subscription_url":"https://api.github.com/repos/lenneTech/academy/subscription","commits_url":"https://api.github.com/repos/lenneTech/academy/commits{/sha}","git_commits_url":"https://api.github.com/repos/lenneTech/academy/git/commits{/sha}","comments_url":"https://api.github.com/repos/lenneTech/academy/comments{/number}","issue_comment_url":"https://api.github.com/repos/lenneTech/academy/issues/comments{/number}","contents_url":"https://api.github.com/repos/lenneTech/academy/contents/{+path}","compare_url":"https://api.github.com/repos/lenneTech/academy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lenneTech/academy/merges","archive_url":"https://api.github.com/repos/lenneTech/academy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lenneTech/academy/downloads","issues_url":"https://api.github.com/repos/lenneTech/academy/issues{/number}","pulls_url":"https://api.github.com/repos/lenneTech/academy/pulls{/number}","milestones_url":"https://api.github.com/repos/lenneTech/academy/milestones{/number}","notifications_url":"https://api.github.com/repos/lenneTech/academy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lenneTech/academy/labels{/name}","releases_url":"https://api.github.com/repos/lenneTech/academy/releases{/id}","deployments_url":"https://api.github.com/repos/lenneTech/academy/deployments","created_at":"2021-06-23T06:17:06Z","updated_at":"2021-07-20T19:03:21Z","pushed_at":"2021-07-21T08:51:09Z","git_url":"git://github.com/lenneTech/academy.git","ssh_url":"git@github.com:lenneTech/academy.git","clone_url":"https://github.com/lenneTech/academy.git","svn_url":"https://github.com/lenneTech/academy","homepage":"https://akademie.lenne.tech","size":14842,"stargazers_count":1,"watchers_count":1,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":1,"open_issues":1,"watchers":1,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/lenneTech/academy/pulls/63"},"html":{"href":"https://github.com/lenneTech/academy/pull/63"},"issue":{"href":"https://api.github.com/repos/lenneTech/academy/issues/63"},"comments":{"href":"https://api.github.com/repos/lenneTech/academy/issues/63/comments"},"review_comments":{"href":"https://api.github.com/repos/lenneTech/academy/pulls/63/comments"},"review_comment":{"href":"https://api.github.com/repos/lenneTech/academy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lenneTech/academy/pulls/63/commits"},"statuses":{"href":"https://api.github.com/repos/lenneTech/academy/statuses/6437e242af4138fec51c91062b5c5398f8593bbb"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":51728218,"login":"lenneTech","gravatar_id":"","url":"https://api.github.com/orgs/lenneTech","avatar_url":"https://avatars.githubusercontent.com/u/51728218?"}} +{"id":"17246339765","type":"WatchEvent","actor":{"id":87755141,"login":"Ehlod","display_login":"Ehlod","gravatar_id":"","url":"https://api.github.com/users/Ehlod","avatar_url":"https://avatars.githubusercontent.com/u/87755141?"},"repo":{"id":388056615,"name":"Ehlod/Ehlod","url":"https://api.github.com/repos/Ehlod/Ehlod"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339769","type":"CreateEvent","actor":{"id":42283162,"login":"aws-aemilia","display_login":"aws-aemilia","gravatar_id":"","url":"https://api.github.com/users/aws-aemilia","avatar_url":"https://avatars.githubusercontent.com/u/42283162?"},"repo":{"id":388056914,"name":"aws-aemilia/react-app44033922781758673","url":"https://api.github.com/repos/aws-aemilia/react-app44033922781758673"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339787","type":"PushEvent","actor":{"id":41898282,"login":"github-actions[bot]","display_login":"github-actions","gravatar_id":"","url":"https://api.github.com/users/github-actions[bot]","avatar_url":"https://avatars.githubusercontent.com/u/41898282?"},"repo":{"id":369713083,"name":"civictechsweden/JagVillHaVaccin","url":"https://api.github.com/repos/civictechsweden/JagVillHaVaccin"},"payload":{"push_id":7562451246,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"62fcc57c2330a6918cc1c546e3cecafe68efc983","before":"95c94556d43d856cf01e5f2eb9cffcdb38a4d46b","commits":[{"sha":"62fcc57c2330a6918cc1c546e3cecafe68efc983","author":{"name":"Pierre Mesure (Github Actions)","email":"ff019a5748a52b5641624af88a54a2f0e46a9fb5@mesu.re"},"message":"Updating the times for Region 01","distinct":true,"url":"https://api.github.com/repos/civictechsweden/JagVillHaVaccin/commits/62fcc57c2330a6918cc1c546e3cecafe68efc983"}]},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":44578964,"login":"civictechsweden","gravatar_id":"","url":"https://api.github.com/orgs/civictechsweden","avatar_url":"https://avatars.githubusercontent.com/u/44578964?"}} +{"id":"17246339789","type":"PushEvent","actor":{"id":82411728,"login":"szczepienia","display_login":"szczepienia","gravatar_id":"","url":"https://api.github.com/users/szczepienia","avatar_url":"https://avatars.githubusercontent.com/u/82411728?"},"repo":{"id":357316184,"name":"szczepienia/szczepienia.github.io","url":"https://api.github.com/repos/szczepienia/szczepienia.github.io"},"payload":{"push_id":7562451259,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"388c29474db703cd39c1d1a13481431e9d31c5ce","before":"281c3ea4b0c374188a13fffc2d6dba365d4025d0","commits":[{"sha":"388c29474db703cd39c1d1a13481431e9d31c5ce","author":{"name":"szczepienia","email":"0eed6e50a807a406b20f90bdf0913a600cbd5a7e@users.noreply.github.com"},"message":"remote update","distinct":true,"url":"https://api.github.com/repos/szczepienia/szczepienia.github.io/commits/388c29474db703cd39c1d1a13481431e9d31c5ce"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339792","type":"PushEvent","actor":{"id":20824859,"login":"Sean-Black","display_login":"Sean-Black","gravatar_id":"","url":"https://api.github.com/users/Sean-Black","avatar_url":"https://avatars.githubusercontent.com/u/20824859?"},"repo":{"id":384748402,"name":"Sean-Black/next-port","url":"https://api.github.com/repos/Sean-Black/next-port"},"payload":{"push_id":7562451261,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"8d71648db95edc48d91af7d3dc231af780888305","before":"f34c2a989cd455ea7dd8e359162f29d114b5fb94","commits":[{"sha":"8d71648db95edc48d91af7d3dc231af780888305","author":{"name":"Sean-Black","email":"e8b90297222601b265e2b2b914a02ae26cb94c79@Yahoo.com"},"message":"Added xypo landing page, and updated urls","distinct":true,"url":"https://api.github.com/repos/Sean-Black/next-port/commits/8d71648db95edc48d91af7d3dc231af780888305"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339795","type":"PushEvent","actor":{"id":1282736,"login":"asenac","display_login":"asenac","gravatar_id":"","url":"https://api.github.com/users/asenac","avatar_url":"https://avatars.githubusercontent.com/u/1282736?"},"repo":{"id":357223266,"name":"asenac/materialize","url":"https://api.github.com/repos/asenac/materialize"},"payload":{"push_id":7562451269,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"30409fef9f21cde13f2c24d9f054afeafbaf1463","before":"4a79961d35da6803dd57884049300b4c6cb10606","commits":[{"sha":"30409fef9f21cde13f2c24d9f054afeafbaf1463","author":{"name":"Andrés Senac","email":"883768b6dd2c42aea0031b24be8a2da40fef4b64@senac.es"},"message":"Throw an error for unsupported outer joins in comma join (#7474)\n\nThrow an error when the planner tries to push down the current\r\ncomma join to the leftmost side of a join tree involving either\r\nright or full outer join, since otherwise the planner would build\r\na wrong join tree leading to wrong results.\r\n\r\nLeft joins are allowed since, even though the resulting plan may\r\nnot be optimal, it is semantically equivalent, ie. leads to the\r\nsame query results.","distinct":true,"url":"https://api.github.com/repos/asenac/materialize/commits/30409fef9f21cde13f2c24d9f054afeafbaf1463"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339796","type":"PullRequestEvent","actor":{"id":81756376,"login":"seiya1911","display_login":"seiya1911","gravatar_id":"","url":"https://api.github.com/users/seiya1911","avatar_url":"https://avatars.githubusercontent.com/u/81756376?"},"repo":{"id":381889365,"name":"seiya1911/Hikidashi","url":"https://api.github.com/repos/seiya1911/Hikidashi"},"payload":{"action":"opened","number":32,"pull_request":{"url":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/32","id":694185720,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzIw","html_url":"https://github.com/seiya1911/Hikidashi/pull/32","diff_url":"https://github.com/seiya1911/Hikidashi/pull/32.diff","patch_url":"https://github.com/seiya1911/Hikidashi/pull/32.patch","issue_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues/32","number":32,"state":"open","locked":false,"title":"Develop","user":{"login":"seiya1911","id":81756376,"node_id":"MDQ6VXNlcjgxNzU2Mzc2","avatar_url":"https://avatars.githubusercontent.com/u/81756376?v=4","gravatar_id":"","url":"https://api.github.com/users/seiya1911","html_url":"https://github.com/seiya1911","followers_url":"https://api.github.com/users/seiya1911/followers","following_url":"https://api.github.com/users/seiya1911/following{/other_user}","gists_url":"https://api.github.com/users/seiya1911/gists{/gist_id}","starred_url":"https://api.github.com/users/seiya1911/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seiya1911/subscriptions","organizations_url":"https://api.github.com/users/seiya1911/orgs","repos_url":"https://api.github.com/users/seiya1911/repos","events_url":"https://api.github.com/users/seiya1911/events{/privacy}","received_events_url":"https://api.github.com/users/seiya1911/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/32/commits","review_comments_url":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/32/comments","review_comment_url":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/comments{/number}","comments_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues/32/comments","statuses_url":"https://api.github.com/repos/seiya1911/Hikidashi/statuses/7239086faa1d085c3937cd068049fa463f7ca955","head":{"label":"seiya1911:develop","ref":"develop","sha":"7239086faa1d085c3937cd068049fa463f7ca955","user":{"login":"seiya1911","id":81756376,"node_id":"MDQ6VXNlcjgxNzU2Mzc2","avatar_url":"https://avatars.githubusercontent.com/u/81756376?v=4","gravatar_id":"","url":"https://api.github.com/users/seiya1911","html_url":"https://github.com/seiya1911","followers_url":"https://api.github.com/users/seiya1911/followers","following_url":"https://api.github.com/users/seiya1911/following{/other_user}","gists_url":"https://api.github.com/users/seiya1911/gists{/gist_id}","starred_url":"https://api.github.com/users/seiya1911/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seiya1911/subscriptions","organizations_url":"https://api.github.com/users/seiya1911/orgs","repos_url":"https://api.github.com/users/seiya1911/repos","events_url":"https://api.github.com/users/seiya1911/events{/privacy}","received_events_url":"https://api.github.com/users/seiya1911/received_events","type":"User","site_admin":false},"repo":{"id":381889365,"node_id":"MDEwOlJlcG9zaXRvcnkzODE4ODkzNjU=","name":"Hikidashi","full_name":"seiya1911/Hikidashi","private":false,"owner":{"login":"seiya1911","id":81756376,"node_id":"MDQ6VXNlcjgxNzU2Mzc2","avatar_url":"https://avatars.githubusercontent.com/u/81756376?v=4","gravatar_id":"","url":"https://api.github.com/users/seiya1911","html_url":"https://github.com/seiya1911","followers_url":"https://api.github.com/users/seiya1911/followers","following_url":"https://api.github.com/users/seiya1911/following{/other_user}","gists_url":"https://api.github.com/users/seiya1911/gists{/gist_id}","starred_url":"https://api.github.com/users/seiya1911/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seiya1911/subscriptions","organizations_url":"https://api.github.com/users/seiya1911/orgs","repos_url":"https://api.github.com/users/seiya1911/repos","events_url":"https://api.github.com/users/seiya1911/events{/privacy}","received_events_url":"https://api.github.com/users/seiya1911/received_events","type":"User","site_admin":false},"html_url":"https://github.com/seiya1911/Hikidashi","description":null,"fork":false,"url":"https://api.github.com/repos/seiya1911/Hikidashi","forks_url":"https://api.github.com/repos/seiya1911/Hikidashi/forks","keys_url":"https://api.github.com/repos/seiya1911/Hikidashi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/seiya1911/Hikidashi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/seiya1911/Hikidashi/teams","hooks_url":"https://api.github.com/repos/seiya1911/Hikidashi/hooks","issue_events_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues/events{/number}","events_url":"https://api.github.com/repos/seiya1911/Hikidashi/events","assignees_url":"https://api.github.com/repos/seiya1911/Hikidashi/assignees{/user}","branches_url":"https://api.github.com/repos/seiya1911/Hikidashi/branches{/branch}","tags_url":"https://api.github.com/repos/seiya1911/Hikidashi/tags","blobs_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/refs{/sha}","trees_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/seiya1911/Hikidashi/statuses/{sha}","languages_url":"https://api.github.com/repos/seiya1911/Hikidashi/languages","stargazers_url":"https://api.github.com/repos/seiya1911/Hikidashi/stargazers","contributors_url":"https://api.github.com/repos/seiya1911/Hikidashi/contributors","subscribers_url":"https://api.github.com/repos/seiya1911/Hikidashi/subscribers","subscription_url":"https://api.github.com/repos/seiya1911/Hikidashi/subscription","commits_url":"https://api.github.com/repos/seiya1911/Hikidashi/commits{/sha}","git_commits_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/commits{/sha}","comments_url":"https://api.github.com/repos/seiya1911/Hikidashi/comments{/number}","issue_comment_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues/comments{/number}","contents_url":"https://api.github.com/repos/seiya1911/Hikidashi/contents/{+path}","compare_url":"https://api.github.com/repos/seiya1911/Hikidashi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/seiya1911/Hikidashi/merges","archive_url":"https://api.github.com/repos/seiya1911/Hikidashi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/seiya1911/Hikidashi/downloads","issues_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues{/number}","pulls_url":"https://api.github.com/repos/seiya1911/Hikidashi/pulls{/number}","milestones_url":"https://api.github.com/repos/seiya1911/Hikidashi/milestones{/number}","notifications_url":"https://api.github.com/repos/seiya1911/Hikidashi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/seiya1911/Hikidashi/labels{/name}","releases_url":"https://api.github.com/repos/seiya1911/Hikidashi/releases{/id}","deployments_url":"https://api.github.com/repos/seiya1911/Hikidashi/deployments","created_at":"2021-07-01T02:44:09Z","updated_at":"2021-07-20T01:50:10Z","pushed_at":"2021-07-21T08:59:46Z","git_url":"git://github.com/seiya1911/Hikidashi.git","ssh_url":"git@github.com:seiya1911/Hikidashi.git","clone_url":"https://github.com/seiya1911/Hikidashi.git","svn_url":"https://github.com/seiya1911/Hikidashi","homepage":null,"size":34668,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"base":{"label":"seiya1911:main","ref":"main","sha":"381b81b67b15b7430db5af9023bf91b8069c0e23","user":{"login":"seiya1911","id":81756376,"node_id":"MDQ6VXNlcjgxNzU2Mzc2","avatar_url":"https://avatars.githubusercontent.com/u/81756376?v=4","gravatar_id":"","url":"https://api.github.com/users/seiya1911","html_url":"https://github.com/seiya1911","followers_url":"https://api.github.com/users/seiya1911/followers","following_url":"https://api.github.com/users/seiya1911/following{/other_user}","gists_url":"https://api.github.com/users/seiya1911/gists{/gist_id}","starred_url":"https://api.github.com/users/seiya1911/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seiya1911/subscriptions","organizations_url":"https://api.github.com/users/seiya1911/orgs","repos_url":"https://api.github.com/users/seiya1911/repos","events_url":"https://api.github.com/users/seiya1911/events{/privacy}","received_events_url":"https://api.github.com/users/seiya1911/received_events","type":"User","site_admin":false},"repo":{"id":381889365,"node_id":"MDEwOlJlcG9zaXRvcnkzODE4ODkzNjU=","name":"Hikidashi","full_name":"seiya1911/Hikidashi","private":false,"owner":{"login":"seiya1911","id":81756376,"node_id":"MDQ6VXNlcjgxNzU2Mzc2","avatar_url":"https://avatars.githubusercontent.com/u/81756376?v=4","gravatar_id":"","url":"https://api.github.com/users/seiya1911","html_url":"https://github.com/seiya1911","followers_url":"https://api.github.com/users/seiya1911/followers","following_url":"https://api.github.com/users/seiya1911/following{/other_user}","gists_url":"https://api.github.com/users/seiya1911/gists{/gist_id}","starred_url":"https://api.github.com/users/seiya1911/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seiya1911/subscriptions","organizations_url":"https://api.github.com/users/seiya1911/orgs","repos_url":"https://api.github.com/users/seiya1911/repos","events_url":"https://api.github.com/users/seiya1911/events{/privacy}","received_events_url":"https://api.github.com/users/seiya1911/received_events","type":"User","site_admin":false},"html_url":"https://github.com/seiya1911/Hikidashi","description":null,"fork":false,"url":"https://api.github.com/repos/seiya1911/Hikidashi","forks_url":"https://api.github.com/repos/seiya1911/Hikidashi/forks","keys_url":"https://api.github.com/repos/seiya1911/Hikidashi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/seiya1911/Hikidashi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/seiya1911/Hikidashi/teams","hooks_url":"https://api.github.com/repos/seiya1911/Hikidashi/hooks","issue_events_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues/events{/number}","events_url":"https://api.github.com/repos/seiya1911/Hikidashi/events","assignees_url":"https://api.github.com/repos/seiya1911/Hikidashi/assignees{/user}","branches_url":"https://api.github.com/repos/seiya1911/Hikidashi/branches{/branch}","tags_url":"https://api.github.com/repos/seiya1911/Hikidashi/tags","blobs_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/refs{/sha}","trees_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/seiya1911/Hikidashi/statuses/{sha}","languages_url":"https://api.github.com/repos/seiya1911/Hikidashi/languages","stargazers_url":"https://api.github.com/repos/seiya1911/Hikidashi/stargazers","contributors_url":"https://api.github.com/repos/seiya1911/Hikidashi/contributors","subscribers_url":"https://api.github.com/repos/seiya1911/Hikidashi/subscribers","subscription_url":"https://api.github.com/repos/seiya1911/Hikidashi/subscription","commits_url":"https://api.github.com/repos/seiya1911/Hikidashi/commits{/sha}","git_commits_url":"https://api.github.com/repos/seiya1911/Hikidashi/git/commits{/sha}","comments_url":"https://api.github.com/repos/seiya1911/Hikidashi/comments{/number}","issue_comment_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues/comments{/number}","contents_url":"https://api.github.com/repos/seiya1911/Hikidashi/contents/{+path}","compare_url":"https://api.github.com/repos/seiya1911/Hikidashi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/seiya1911/Hikidashi/merges","archive_url":"https://api.github.com/repos/seiya1911/Hikidashi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/seiya1911/Hikidashi/downloads","issues_url":"https://api.github.com/repos/seiya1911/Hikidashi/issues{/number}","pulls_url":"https://api.github.com/repos/seiya1911/Hikidashi/pulls{/number}","milestones_url":"https://api.github.com/repos/seiya1911/Hikidashi/milestones{/number}","notifications_url":"https://api.github.com/repos/seiya1911/Hikidashi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/seiya1911/Hikidashi/labels{/name}","releases_url":"https://api.github.com/repos/seiya1911/Hikidashi/releases{/id}","deployments_url":"https://api.github.com/repos/seiya1911/Hikidashi/deployments","created_at":"2021-07-01T02:44:09Z","updated_at":"2021-07-20T01:50:10Z","pushed_at":"2021-07-21T08:59:46Z","git_url":"git://github.com/seiya1911/Hikidashi.git","ssh_url":"git@github.com:seiya1911/Hikidashi.git","clone_url":"https://github.com/seiya1911/Hikidashi.git","svn_url":"https://github.com/seiya1911/Hikidashi","homepage":null,"size":34668,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/32"},"html":{"href":"https://github.com/seiya1911/Hikidashi/pull/32"},"issue":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/issues/32"},"comments":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/issues/32/comments"},"review_comments":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/32/comments"},"review_comment":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/pulls/32/commits"},"statuses":{"href":"https://api.github.com/repos/seiya1911/Hikidashi/statuses/7239086faa1d085c3937cd068049fa463f7ca955"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":4,"additions":242,"deletions":133,"changed_files":19}},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339804","type":"IssueCommentEvent","actor":{"id":16579427,"login":"cziegenhain","display_login":"cziegenhain","gravatar_id":"","url":"https://api.github.com/users/cziegenhain","avatar_url":"https://avatars.githubusercontent.com/u/16579427?"},"repo":{"id":94886407,"name":"sdparekh/zUMIs","url":"https://api.github.com/repos/sdparekh/zUMIs"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272","repository_url":"https://api.github.com/repos/sdparekh/zUMIs","labels_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272/labels{/name}","comments_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272/comments","events_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272/events","html_url":"https://github.com/sdparekh/zUMIs/issues/272","id":949217001,"node_id":"MDU6SXNzdWU5NDkyMTcwMDE=","number":272,"title":"Reproducing HCA data using zUMI","user":{"login":"HaniJieunKim","id":62005528,"node_id":"MDQ6VXNlcjYyMDA1NTI4","avatar_url":"https://avatars.githubusercontent.com/u/62005528?v=4","gravatar_id":"","url":"https://api.github.com/users/HaniJieunKim","html_url":"https://github.com/HaniJieunKim","followers_url":"https://api.github.com/users/HaniJieunKim/followers","following_url":"https://api.github.com/users/HaniJieunKim/following{/other_user}","gists_url":"https://api.github.com/users/HaniJieunKim/gists{/gist_id}","starred_url":"https://api.github.com/users/HaniJieunKim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/HaniJieunKim/subscriptions","organizations_url":"https://api.github.com/users/HaniJieunKim/orgs","repos_url":"https://api.github.com/users/HaniJieunKim/repos","events_url":"https://api.github.com/users/HaniJieunKim/events{/privacy}","received_events_url":"https://api.github.com/users/HaniJieunKim/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2021-07-21T00:36:15Z","updated_at":"2021-07-21T09:00:02Z","closed_at":"2021-07-21T09:00:02Z","author_association":"NONE","active_lock_reason":null,"body":"Hi!\r\n\r\nThank you very much for creating and maintaining zUMIs. I have been successful at running the test data and also the fibroblast data from the original Smart-seq3 paper. However when I try running HCA, I get an error (see below for YAML file and log). I would be much grateful for you help!\r\n\r\n```\r\nproject: HCA_test\r\nsequence_files:\r\n file1:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.R1.fastq.gz\r\n base_definition:\r\n - cDNA(23-150)\r\n - UMI(12-19)\r\n find_pattern: ATTGCGCAATG\r\n file2:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.R2.fastq.gz\r\n base_definition:\r\n - cDNA(1-150)\r\n file3:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.I1.fastq.gz\r\n base_definition:\r\n - BC(1-8)\r\n file4:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.I2.fastq.gz\r\n base_definition:\r\n - BC(1-8)\r\nreference:\r\n STAR_index: /home/data/data2/genomeDir/STARgenomes/human2\r\n GTF_file: /home/data/data2/genomeDir/refdata-gex-GRCh38-2020-A/genes/genes.gtf\r\n exon_extension: no\r\n extension_length: 0\r\n scaffold_length_min: 0\r\n additional_STAR_params: '--clip3pAdapterSeq CTGTCTCTTATACACATCT --limitSjdbInsertNsj 2000000 --outFilterIntronMotifs --RemoveNoncanonicalUnannotated'\r\n additional_files: ~\r\nout_dir: /home/data/data2/normalisation/zUMI/test4\r\nnum_threads: 50\r\nmem_limit: 200\r\nfilter_cutoffs:\r\n BC_filter:\r\n num_bases: 3\r\n phred: 20\r\n UMI_filter:\r\n num_bases: 3\r\n phred: 20\r\nbarcodes:\r\n barcode_num: ~\r\n barcode_file: ~\r\n barcode_sharing: ~\r\n automatic: yes\r\n BarcodeBinning: 1\r\n nReadsperCell: 100\r\ncounting_opts:\r\n introns: yes\r\n intronProb: no\r\n downsampling: '0'\r\n strand: 0\r\n Ham_Dist: 1\r\n velocyto: no\r\n primaryHit: yes\r\n multi_overlap: no\r\n twoPass: no\r\n demultiplex: no\r\nmake_stats: yes\r\nwhich_Stage: Filtering\r\nsamtools_exec: samtools\r\npigz_exec: pigz\r\n#STAR_exec: /home/ubuntu/STAR/bin/Linux_x86_64/STAR\r\nSTAR_exec: STAR\r\nRscript_exec: Rscript\r\nzUMIs_directory: /home/ubuntu/zUMIs\r\n```\r\n\r\n```\r\nUsing miniconda environment for zUMIs!\r\n note: internal executables will be used instead of those specified in the YAML file!\r\n\r\n\r\n You provided these parameters:\r\n YAML file: script/HCA_test_v1.yaml\r\n zUMIs directory: /home/ubuntu/zUMIs\r\n STAR executable STAR\r\n samtools executable samtools\r\n pigz executable pigz\r\n Rscript executable Rscript\r\n RAM limit: 200\r\n zUMIs version 2.9.6 \r\n\r\n\r\nFri Jul 16 04:03:11 UTC 2021\r\nWARNING: The STAR version used for mapping is 2.7.3a and the STAR index was created using the version 2.7.1a. This may lead to an error while mapping. If you encounte\r\nr any errors at the mapping stage, please make sure to create the STAR index using STAR 2.7.3a.\r\nFiltering...\r\npigz: skipping: /home/data/data2/Smart-seq3/raw/HCA.R2.fastq.gz: corrupted -- incomplete deflate data\r\npigz: abort: internal threads error\r\npigz: skipping: /home/data/data2/Smart-seq3/raw/HCA.R1.fastq.gz: corrupted -- incomplete deflate data\r\npigz: abort: internal threads error\r\npigz: skipping: /home/data/data2/normalisation/zUMI/test4/zUMIs_output/.tmpMerge/HCA.R2.fastqHCA_testbt.gz does not exist\r\n@A00187:188:HM3HKDSXX:4:2657:24532:15076\r\n\r\nERROR! Fastq files are not in the same order.\r\n Make sure to provide reads in the same order.\r\n\r\n@A00187:188:HM3HKDSXX:4:2657:24551:15076\r\n\r\nERROR! Fastq files are not in the same order.\r\n Make sure to provide reads in the same order.\r\n```\r\n\r\nThese are the FASTQ files downloaded.\r\n```\r\n-rw-rw-r-- 1 ubuntu ubuntu 29660183738 May 15 2020 HCA.I1.fastq.gz\r\n-rw-rw-r-- 1 ubuntu ubuntu 30477852902 May 15 2020 HCA.I2.fastq.gz\r\n-rw-rw-r-- 1 ubuntu ubuntu 227299256599 Jul 15 07:49 HCA.R1.fastq.gz\r\n-rw-rw-r-- 1 ubuntu ubuntu 227987249699 Jul 15 07:49 HCA.R2.fastq.gz\r\n```\r\n\r\nMany thanks for you time and help!\r\n\r\nBest regards,\r\n\r\nHani\r\n\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/sdparekh/zUMIs/issues/comments/884018704","html_url":"https://github.com/sdparekh/zUMIs/issues/272#issuecomment-884018704","issue_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272","id":884018704,"node_id":"IC_kwDOBafaB840sQ4Q","user":{"login":"cziegenhain","id":16579427,"node_id":"MDQ6VXNlcjE2NTc5NDI3","avatar_url":"https://avatars.githubusercontent.com/u/16579427?v=4","gravatar_id":"","url":"https://api.github.com/users/cziegenhain","html_url":"https://github.com/cziegenhain","followers_url":"https://api.github.com/users/cziegenhain/followers","following_url":"https://api.github.com/users/cziegenhain/following{/other_user}","gists_url":"https://api.github.com/users/cziegenhain/gists{/gist_id}","starred_url":"https://api.github.com/users/cziegenhain/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cziegenhain/subscriptions","organizations_url":"https://api.github.com/users/cziegenhain/orgs","repos_url":"https://api.github.com/users/cziegenhain/repos","events_url":"https://api.github.com/users/cziegenhain/events{/privacy}","received_events_url":"https://api.github.com/users/cziegenhain/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","author_association":"COLLABORATOR","body":"for example, you can count the number of reads in each of the files that you download to confirm they all match.\r\nI'm sure ArrayExpress also has md5 checksums to verify your download.\r\n\r\nClosing this issue as it is not related to zUMIs.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339805","type":"PullRequestReviewEvent","actor":{"id":158871,"login":"machisuji","display_login":"machisuji","gravatar_id":"","url":"https://api.github.com/users/machisuji","avatar_url":"https://avatars.githubusercontent.com/u/158871?"},"repo":{"id":6899875,"name":"opf/openproject","url":"https://api.github.com/repos/opf/openproject"},"payload":{"action":"created","review":{"id":711422084,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDIyMDg0","user":{"login":"machisuji","id":158871,"node_id":"MDQ6VXNlcjE1ODg3MQ==","avatar_url":"https://avatars.githubusercontent.com/u/158871?v=4","gravatar_id":"","url":"https://api.github.com/users/machisuji","html_url":"https://github.com/machisuji","followers_url":"https://api.github.com/users/machisuji/followers","following_url":"https://api.github.com/users/machisuji/following{/other_user}","gists_url":"https://api.github.com/users/machisuji/gists{/gist_id}","starred_url":"https://api.github.com/users/machisuji/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/machisuji/subscriptions","organizations_url":"https://api.github.com/users/machisuji/orgs","repos_url":"https://api.github.com/users/machisuji/repos","events_url":"https://api.github.com/users/machisuji/events{/privacy}","received_events_url":"https://api.github.com/users/machisuji/received_events","type":"User","site_admin":false},"body":"👍 ","commit_id":"5c1718ec5e84886cb18b5972ab4de28cbba4c3d6","submitted_at":"2021-07-21T09:00:01Z","state":"approved","html_url":"https://github.com/opf/openproject/pull/9495#pullrequestreview-711422084","pull_request_url":"https://api.github.com/repos/opf/openproject/pulls/9495","author_association":"MEMBER","_links":{"html":{"href":"https://github.com/opf/openproject/pull/9495#pullrequestreview-711422084"},"pull_request":{"href":"https://api.github.com/repos/opf/openproject/pulls/9495"}}},"pull_request":{"url":"https://api.github.com/repos/opf/openproject/pulls/9495","id":693710783,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNzEwNzgz","html_url":"https://github.com/opf/openproject/pull/9495","diff_url":"https://github.com/opf/openproject/pull/9495.diff","patch_url":"https://github.com/opf/openproject/pull/9495.patch","issue_url":"https://api.github.com/repos/opf/openproject/issues/9495","number":9495,"state":"open","locked":false,"title":"Add API documentation for notifications","user":{"login":"oliverguenther","id":459462,"node_id":"MDQ6VXNlcjQ1OTQ2Mg==","avatar_url":"https://avatars.githubusercontent.com/u/459462?v=4","gravatar_id":"","url":"https://api.github.com/users/oliverguenther","html_url":"https://github.com/oliverguenther","followers_url":"https://api.github.com/users/oliverguenther/followers","following_url":"https://api.github.com/users/oliverguenther/following{/other_user}","gists_url":"https://api.github.com/users/oliverguenther/gists{/gist_id}","starred_url":"https://api.github.com/users/oliverguenther/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oliverguenther/subscriptions","organizations_url":"https://api.github.com/users/oliverguenther/orgs","repos_url":"https://api.github.com/users/oliverguenther/repos","events_url":"https://api.github.com/users/oliverguenther/events{/privacy}","received_events_url":"https://api.github.com/users/oliverguenther/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T18:43:15Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"db02bf918c70d6ecfc37047bc8c127a05d35f54e","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/opf/openproject/pulls/9495/commits","review_comments_url":"https://api.github.com/repos/opf/openproject/pulls/9495/comments","review_comment_url":"https://api.github.com/repos/opf/openproject/pulls/comments{/number}","comments_url":"https://api.github.com/repos/opf/openproject/issues/9495/comments","statuses_url":"https://api.github.com/repos/opf/openproject/statuses/5c1718ec5e84886cb18b5972ab4de28cbba4c3d6","head":{"label":"opf:notifications-api-doc","ref":"notifications-api-doc","sha":"5c1718ec5e84886cb18b5972ab4de28cbba4c3d6","user":{"login":"opf","id":1756674,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE3NTY2NzQ=","avatar_url":"https://avatars.githubusercontent.com/u/1756674?v=4","gravatar_id":"","url":"https://api.github.com/users/opf","html_url":"https://github.com/opf","followers_url":"https://api.github.com/users/opf/followers","following_url":"https://api.github.com/users/opf/following{/other_user}","gists_url":"https://api.github.com/users/opf/gists{/gist_id}","starred_url":"https://api.github.com/users/opf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opf/subscriptions","organizations_url":"https://api.github.com/users/opf/orgs","repos_url":"https://api.github.com/users/opf/repos","events_url":"https://api.github.com/users/opf/events{/privacy}","received_events_url":"https://api.github.com/users/opf/received_events","type":"Organization","site_admin":false},"repo":{"id":6899875,"node_id":"MDEwOlJlcG9zaXRvcnk2ODk5ODc1","name":"openproject","full_name":"opf/openproject","private":false,"owner":{"login":"opf","id":1756674,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE3NTY2NzQ=","avatar_url":"https://avatars.githubusercontent.com/u/1756674?v=4","gravatar_id":"","url":"https://api.github.com/users/opf","html_url":"https://github.com/opf","followers_url":"https://api.github.com/users/opf/followers","following_url":"https://api.github.com/users/opf/following{/other_user}","gists_url":"https://api.github.com/users/opf/gists{/gist_id}","starred_url":"https://api.github.com/users/opf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opf/subscriptions","organizations_url":"https://api.github.com/users/opf/orgs","repos_url":"https://api.github.com/users/opf/repos","events_url":"https://api.github.com/users/opf/events{/privacy}","received_events_url":"https://api.github.com/users/opf/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opf/openproject","description":"OpenProject is the leading open source project management software.","fork":false,"url":"https://api.github.com/repos/opf/openproject","forks_url":"https://api.github.com/repos/opf/openproject/forks","keys_url":"https://api.github.com/repos/opf/openproject/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opf/openproject/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opf/openproject/teams","hooks_url":"https://api.github.com/repos/opf/openproject/hooks","issue_events_url":"https://api.github.com/repos/opf/openproject/issues/events{/number}","events_url":"https://api.github.com/repos/opf/openproject/events","assignees_url":"https://api.github.com/repos/opf/openproject/assignees{/user}","branches_url":"https://api.github.com/repos/opf/openproject/branches{/branch}","tags_url":"https://api.github.com/repos/opf/openproject/tags","blobs_url":"https://api.github.com/repos/opf/openproject/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opf/openproject/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opf/openproject/git/refs{/sha}","trees_url":"https://api.github.com/repos/opf/openproject/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opf/openproject/statuses/{sha}","languages_url":"https://api.github.com/repos/opf/openproject/languages","stargazers_url":"https://api.github.com/repos/opf/openproject/stargazers","contributors_url":"https://api.github.com/repos/opf/openproject/contributors","subscribers_url":"https://api.github.com/repos/opf/openproject/subscribers","subscription_url":"https://api.github.com/repos/opf/openproject/subscription","commits_url":"https://api.github.com/repos/opf/openproject/commits{/sha}","git_commits_url":"https://api.github.com/repos/opf/openproject/git/commits{/sha}","comments_url":"https://api.github.com/repos/opf/openproject/comments{/number}","issue_comment_url":"https://api.github.com/repos/opf/openproject/issues/comments{/number}","contents_url":"https://api.github.com/repos/opf/openproject/contents/{+path}","compare_url":"https://api.github.com/repos/opf/openproject/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opf/openproject/merges","archive_url":"https://api.github.com/repos/opf/openproject/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opf/openproject/downloads","issues_url":"https://api.github.com/repos/opf/openproject/issues{/number}","pulls_url":"https://api.github.com/repos/opf/openproject/pulls{/number}","milestones_url":"https://api.github.com/repos/opf/openproject/milestones{/number}","notifications_url":"https://api.github.com/repos/opf/openproject/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opf/openproject/labels{/name}","releases_url":"https://api.github.com/repos/opf/openproject/releases{/id}","deployments_url":"https://api.github.com/repos/opf/openproject/deployments","created_at":"2012-11-28T09:59:11Z","updated_at":"2021-07-21T03:19:12Z","pushed_at":"2021-07-21T08:59:45Z","git_url":"git://github.com/opf/openproject.git","ssh_url":"git@github.com:opf/openproject.git","clone_url":"https://github.com/opf/openproject.git","svn_url":"https://github.com/opf/openproject","homepage":"https://www.openproject.org","size":514621,"stargazers_count":4993,"watchers_count":4993,"language":"Ruby","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1462,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":20,"license":null,"forks":1462,"open_issues":20,"watchers":4993,"default_branch":"dev"}},"base":{"label":"opf:release/11.4","ref":"release/11.4","sha":"938c6e56197ab1eb6c542a48cbfe92d0831f6c4e","user":{"login":"opf","id":1756674,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE3NTY2NzQ=","avatar_url":"https://avatars.githubusercontent.com/u/1756674?v=4","gravatar_id":"","url":"https://api.github.com/users/opf","html_url":"https://github.com/opf","followers_url":"https://api.github.com/users/opf/followers","following_url":"https://api.github.com/users/opf/following{/other_user}","gists_url":"https://api.github.com/users/opf/gists{/gist_id}","starred_url":"https://api.github.com/users/opf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opf/subscriptions","organizations_url":"https://api.github.com/users/opf/orgs","repos_url":"https://api.github.com/users/opf/repos","events_url":"https://api.github.com/users/opf/events{/privacy}","received_events_url":"https://api.github.com/users/opf/received_events","type":"Organization","site_admin":false},"repo":{"id":6899875,"node_id":"MDEwOlJlcG9zaXRvcnk2ODk5ODc1","name":"openproject","full_name":"opf/openproject","private":false,"owner":{"login":"opf","id":1756674,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE3NTY2NzQ=","avatar_url":"https://avatars.githubusercontent.com/u/1756674?v=4","gravatar_id":"","url":"https://api.github.com/users/opf","html_url":"https://github.com/opf","followers_url":"https://api.github.com/users/opf/followers","following_url":"https://api.github.com/users/opf/following{/other_user}","gists_url":"https://api.github.com/users/opf/gists{/gist_id}","starred_url":"https://api.github.com/users/opf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opf/subscriptions","organizations_url":"https://api.github.com/users/opf/orgs","repos_url":"https://api.github.com/users/opf/repos","events_url":"https://api.github.com/users/opf/events{/privacy}","received_events_url":"https://api.github.com/users/opf/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opf/openproject","description":"OpenProject is the leading open source project management software.","fork":false,"url":"https://api.github.com/repos/opf/openproject","forks_url":"https://api.github.com/repos/opf/openproject/forks","keys_url":"https://api.github.com/repos/opf/openproject/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opf/openproject/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opf/openproject/teams","hooks_url":"https://api.github.com/repos/opf/openproject/hooks","issue_events_url":"https://api.github.com/repos/opf/openproject/issues/events{/number}","events_url":"https://api.github.com/repos/opf/openproject/events","assignees_url":"https://api.github.com/repos/opf/openproject/assignees{/user}","branches_url":"https://api.github.com/repos/opf/openproject/branches{/branch}","tags_url":"https://api.github.com/repos/opf/openproject/tags","blobs_url":"https://api.github.com/repos/opf/openproject/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opf/openproject/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opf/openproject/git/refs{/sha}","trees_url":"https://api.github.com/repos/opf/openproject/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opf/openproject/statuses/{sha}","languages_url":"https://api.github.com/repos/opf/openproject/languages","stargazers_url":"https://api.github.com/repos/opf/openproject/stargazers","contributors_url":"https://api.github.com/repos/opf/openproject/contributors","subscribers_url":"https://api.github.com/repos/opf/openproject/subscribers","subscription_url":"https://api.github.com/repos/opf/openproject/subscription","commits_url":"https://api.github.com/repos/opf/openproject/commits{/sha}","git_commits_url":"https://api.github.com/repos/opf/openproject/git/commits{/sha}","comments_url":"https://api.github.com/repos/opf/openproject/comments{/number}","issue_comment_url":"https://api.github.com/repos/opf/openproject/issues/comments{/number}","contents_url":"https://api.github.com/repos/opf/openproject/contents/{+path}","compare_url":"https://api.github.com/repos/opf/openproject/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opf/openproject/merges","archive_url":"https://api.github.com/repos/opf/openproject/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opf/openproject/downloads","issues_url":"https://api.github.com/repos/opf/openproject/issues{/number}","pulls_url":"https://api.github.com/repos/opf/openproject/pulls{/number}","milestones_url":"https://api.github.com/repos/opf/openproject/milestones{/number}","notifications_url":"https://api.github.com/repos/opf/openproject/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opf/openproject/labels{/name}","releases_url":"https://api.github.com/repos/opf/openproject/releases{/id}","deployments_url":"https://api.github.com/repos/opf/openproject/deployments","created_at":"2012-11-28T09:59:11Z","updated_at":"2021-07-21T03:19:12Z","pushed_at":"2021-07-21T08:59:45Z","git_url":"git://github.com/opf/openproject.git","ssh_url":"git@github.com:opf/openproject.git","clone_url":"https://github.com/opf/openproject.git","svn_url":"https://github.com/opf/openproject","homepage":"https://www.openproject.org","size":514621,"stargazers_count":4993,"watchers_count":4993,"language":"Ruby","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1462,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":20,"license":null,"forks":1462,"open_issues":20,"watchers":4993,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/opf/openproject/pulls/9495"},"html":{"href":"https://github.com/opf/openproject/pull/9495"},"issue":{"href":"https://api.github.com/repos/opf/openproject/issues/9495"},"comments":{"href":"https://api.github.com/repos/opf/openproject/issues/9495/comments"},"review_comments":{"href":"https://api.github.com/repos/opf/openproject/pulls/9495/comments"},"review_comment":{"href":"https://api.github.com/repos/opf/openproject/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/opf/openproject/pulls/9495/commits"},"statuses":{"href":"https://api.github.com/repos/opf/openproject/statuses/5c1718ec5e84886cb18b5972ab4de28cbba4c3d6"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":1756674,"login":"opf","gravatar_id":"","url":"https://api.github.com/orgs/opf","avatar_url":"https://avatars.githubusercontent.com/u/1756674?"}} +{"id":"17246339811","type":"PushEvent","actor":{"id":64682801,"login":"shashankx86","display_login":"shashankx86","gravatar_id":"","url":"https://api.github.com/users/shashankx86","avatar_url":"https://avatars.githubusercontent.com/u/64682801?"},"repo":{"id":384875664,"name":"shashankx86/DebloaterX","url":"https://api.github.com/repos/shashankx86/DebloaterX"},"payload":{"push_id":7562451263,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"7a3b6aeec3d2561f70d81c47cbba5313fccc7011","before":"21f642f68f3ef407f35b44ffd58a91d1fdc6b348","commits":[{"sha":"7a3b6aeec3d2561f70d81c47cbba5313fccc7011","author":{"name":"Shashank Pandey","email":"030cafcc69c51f316a4290f17be0b5231fd75dcc@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/shashankx86/DebloaterX/commits/7a3b6aeec3d2561f70d81c47cbba5313fccc7011"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339814","type":"PushEvent","actor":{"id":87743028,"login":"lukaromes895","display_login":"lukaromes895","gravatar_id":"","url":"https://api.github.com/users/lukaromes895","avatar_url":"https://avatars.githubusercontent.com/u/87743028?"},"repo":{"id":387979195,"name":"lukaromes895/verlng2","url":"https://api.github.com/repos/lukaromes895/verlng2"},"payload":{"push_id":7562451268,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"8c5ca8b02c95477d81e8bb847fa5a181a0552474","before":"d1b0aea8cf16aa545f4c97757426a4d97d155549","commits":[{"sha":"8c5ca8b02c95477d81e8bb847fa5a181a0552474","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/lukaromes895/verlng2/commits/8c5ca8b02c95477d81e8bb847fa5a181a0552474"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339816","type":"WatchEvent","actor":{"id":30831045,"login":"amanchopra95","display_login":"amanchopra95","gravatar_id":"","url":"https://api.github.com/users/amanchopra95","avatar_url":"https://avatars.githubusercontent.com/u/30831045?"},"repo":{"id":173529836,"name":"hnasr/javascript_playground","url":"https://api.github.com/repos/hnasr/javascript_playground"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339818","type":"PushEvent","actor":{"id":69341855,"login":"kimu-naoki","display_login":"kimu-naoki","gravatar_id":"","url":"https://api.github.com/users/kimu-naoki","avatar_url":"https://avatars.githubusercontent.com/u/69341855?"},"repo":{"id":293456744,"name":"kimu-naoki/kimu-naoki.github.io","url":"https://api.github.com/repos/kimu-naoki/kimu-naoki.github.io"},"payload":{"push_id":7562451266,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"bbf39e7cc23e9e6ea238bd2b25002697164bac5f","before":"a76ee7457242d10e48c2e6e97a15c8d053f25521","commits":[{"sha":"bbf39e7cc23e9e6ea238bd2b25002697164bac5f","author":{"name":"kimu-naoki","email":"fdede8c5b30e6059490360c89428ab86a0bc0357@users.noreply.github.com"},"message":"Update index.html","distinct":true,"url":"https://api.github.com/repos/kimu-naoki/kimu-naoki.github.io/commits/bbf39e7cc23e9e6ea238bd2b25002697164bac5f"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339820","type":"PushEvent","actor":{"id":10191894,"login":"khanglq","display_login":"khanglq","gravatar_id":"","url":"https://api.github.com/users/khanglq","avatar_url":"https://avatars.githubusercontent.com/u/10191894?"},"repo":{"id":388034241,"name":"khanglq/support-java-doan","url":"https://api.github.com/repos/khanglq/support-java-doan"},"payload":{"push_id":7562451281,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"2f996572df67d2fcc915e1a32d323527ec8d2bf7","before":"c221a812ae32b6fed18bc9dd5497c0b2a38449cf","commits":[{"sha":"2f996572df67d2fcc915e1a32d323527ec8d2bf7","author":{"name":"khanglq","email":"caacf6bc6c783cafabc934d326886ea4f06d6d09@gmail.com"},"message":"add db resized","distinct":true,"url":"https://api.github.com/repos/khanglq/support-java-doan/commits/2f996572df67d2fcc915e1a32d323527ec8d2bf7"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339822","type":"PushEvent","actor":{"id":87330551,"login":"xz241","display_login":"xz241","gravatar_id":"","url":"https://api.github.com/users/xz241","avatar_url":"https://avatars.githubusercontent.com/u/87330551?"},"repo":{"id":385328687,"name":"xz241/wp-uploads","url":"https://api.github.com/repos/xz241/wp-uploads"},"payload":{"push_id":7562451293,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"94d80572a2b9faafef90bebf50c0c6fcdd4b91b8","before":"1d84d885421cf1a0f94f8720579a53734457c27d","commits":[{"sha":"94d80572a2b9faafef90bebf50c0c6fcdd4b91b8","author":{"name":"xz241","email":"e17353502d0344fb9b4d10b6a471cc23c11850ec@users.noreply.github.com"},"message":"","distinct":true,"url":"https://api.github.com/repos/xz241/wp-uploads/commits/94d80572a2b9faafef90bebf50c0c6fcdd4b91b8"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339832","type":"IssuesEvent","actor":{"id":16579427,"login":"cziegenhain","display_login":"cziegenhain","gravatar_id":"","url":"https://api.github.com/users/cziegenhain","avatar_url":"https://avatars.githubusercontent.com/u/16579427?"},"repo":{"id":94886407,"name":"sdparekh/zUMIs","url":"https://api.github.com/repos/sdparekh/zUMIs"},"payload":{"action":"closed","issue":{"url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272","repository_url":"https://api.github.com/repos/sdparekh/zUMIs","labels_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272/labels{/name}","comments_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272/comments","events_url":"https://api.github.com/repos/sdparekh/zUMIs/issues/272/events","html_url":"https://github.com/sdparekh/zUMIs/issues/272","id":949217001,"node_id":"MDU6SXNzdWU5NDkyMTcwMDE=","number":272,"title":"Reproducing HCA data using zUMI","user":{"login":"HaniJieunKim","id":62005528,"node_id":"MDQ6VXNlcjYyMDA1NTI4","avatar_url":"https://avatars.githubusercontent.com/u/62005528?v=4","gravatar_id":"","url":"https://api.github.com/users/HaniJieunKim","html_url":"https://github.com/HaniJieunKim","followers_url":"https://api.github.com/users/HaniJieunKim/followers","following_url":"https://api.github.com/users/HaniJieunKim/following{/other_user}","gists_url":"https://api.github.com/users/HaniJieunKim/gists{/gist_id}","starred_url":"https://api.github.com/users/HaniJieunKim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/HaniJieunKim/subscriptions","organizations_url":"https://api.github.com/users/HaniJieunKim/orgs","repos_url":"https://api.github.com/users/HaniJieunKim/repos","events_url":"https://api.github.com/users/HaniJieunKim/events{/privacy}","received_events_url":"https://api.github.com/users/HaniJieunKim/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2021-07-21T00:36:15Z","updated_at":"2021-07-21T09:00:02Z","closed_at":"2021-07-21T09:00:02Z","author_association":"NONE","active_lock_reason":null,"body":"Hi!\r\n\r\nThank you very much for creating and maintaining zUMIs. I have been successful at running the test data and also the fibroblast data from the original Smart-seq3 paper. However when I try running HCA, I get an error (see below for YAML file and log). I would be much grateful for you help!\r\n\r\n```\r\nproject: HCA_test\r\nsequence_files:\r\n file1:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.R1.fastq.gz\r\n base_definition:\r\n - cDNA(23-150)\r\n - UMI(12-19)\r\n find_pattern: ATTGCGCAATG\r\n file2:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.R2.fastq.gz\r\n base_definition:\r\n - cDNA(1-150)\r\n file3:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.I1.fastq.gz\r\n base_definition:\r\n - BC(1-8)\r\n file4:\r\n name: /home/data/data2/Smart-seq3/raw/HCA.I2.fastq.gz\r\n base_definition:\r\n - BC(1-8)\r\nreference:\r\n STAR_index: /home/data/data2/genomeDir/STARgenomes/human2\r\n GTF_file: /home/data/data2/genomeDir/refdata-gex-GRCh38-2020-A/genes/genes.gtf\r\n exon_extension: no\r\n extension_length: 0\r\n scaffold_length_min: 0\r\n additional_STAR_params: '--clip3pAdapterSeq CTGTCTCTTATACACATCT --limitSjdbInsertNsj 2000000 --outFilterIntronMotifs --RemoveNoncanonicalUnannotated'\r\n additional_files: ~\r\nout_dir: /home/data/data2/normalisation/zUMI/test4\r\nnum_threads: 50\r\nmem_limit: 200\r\nfilter_cutoffs:\r\n BC_filter:\r\n num_bases: 3\r\n phred: 20\r\n UMI_filter:\r\n num_bases: 3\r\n phred: 20\r\nbarcodes:\r\n barcode_num: ~\r\n barcode_file: ~\r\n barcode_sharing: ~\r\n automatic: yes\r\n BarcodeBinning: 1\r\n nReadsperCell: 100\r\ncounting_opts:\r\n introns: yes\r\n intronProb: no\r\n downsampling: '0'\r\n strand: 0\r\n Ham_Dist: 1\r\n velocyto: no\r\n primaryHit: yes\r\n multi_overlap: no\r\n twoPass: no\r\n demultiplex: no\r\nmake_stats: yes\r\nwhich_Stage: Filtering\r\nsamtools_exec: samtools\r\npigz_exec: pigz\r\n#STAR_exec: /home/ubuntu/STAR/bin/Linux_x86_64/STAR\r\nSTAR_exec: STAR\r\nRscript_exec: Rscript\r\nzUMIs_directory: /home/ubuntu/zUMIs\r\n```\r\n\r\n```\r\nUsing miniconda environment for zUMIs!\r\n note: internal executables will be used instead of those specified in the YAML file!\r\n\r\n\r\n You provided these parameters:\r\n YAML file: script/HCA_test_v1.yaml\r\n zUMIs directory: /home/ubuntu/zUMIs\r\n STAR executable STAR\r\n samtools executable samtools\r\n pigz executable pigz\r\n Rscript executable Rscript\r\n RAM limit: 200\r\n zUMIs version 2.9.6 \r\n\r\n\r\nFri Jul 16 04:03:11 UTC 2021\r\nWARNING: The STAR version used for mapping is 2.7.3a and the STAR index was created using the version 2.7.1a. This may lead to an error while mapping. If you encounte\r\nr any errors at the mapping stage, please make sure to create the STAR index using STAR 2.7.3a.\r\nFiltering...\r\npigz: skipping: /home/data/data2/Smart-seq3/raw/HCA.R2.fastq.gz: corrupted -- incomplete deflate data\r\npigz: abort: internal threads error\r\npigz: skipping: /home/data/data2/Smart-seq3/raw/HCA.R1.fastq.gz: corrupted -- incomplete deflate data\r\npigz: abort: internal threads error\r\npigz: skipping: /home/data/data2/normalisation/zUMI/test4/zUMIs_output/.tmpMerge/HCA.R2.fastqHCA_testbt.gz does not exist\r\n@A00187:188:HM3HKDSXX:4:2657:24532:15076\r\n\r\nERROR! Fastq files are not in the same order.\r\n Make sure to provide reads in the same order.\r\n\r\n@A00187:188:HM3HKDSXX:4:2657:24551:15076\r\n\r\nERROR! Fastq files are not in the same order.\r\n Make sure to provide reads in the same order.\r\n```\r\n\r\nThese are the FASTQ files downloaded.\r\n```\r\n-rw-rw-r-- 1 ubuntu ubuntu 29660183738 May 15 2020 HCA.I1.fastq.gz\r\n-rw-rw-r-- 1 ubuntu ubuntu 30477852902 May 15 2020 HCA.I2.fastq.gz\r\n-rw-rw-r-- 1 ubuntu ubuntu 227299256599 Jul 15 07:49 HCA.R1.fastq.gz\r\n-rw-rw-r-- 1 ubuntu ubuntu 227987249699 Jul 15 07:49 HCA.R2.fastq.gz\r\n```\r\n\r\nMany thanks for you time and help!\r\n\r\nBest regards,\r\n\r\nHani\r\n\r\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339833","type":"CreateEvent","actor":{"id":87711390,"login":"NehaCholle117","display_login":"NehaCholle117","gravatar_id":"","url":"https://api.github.com/users/NehaCholle117","avatar_url":"https://avatars.githubusercontent.com/u/87711390?"},"repo":{"id":388056962,"name":"NehaCholle117/College-management-system","url":"https://api.github.com/repos/NehaCholle117/College-management-system"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339836","type":"CreateEvent","actor":{"id":10532611,"login":"felixfbecker","display_login":"felixfbecker","gravatar_id":"","url":"https://api.github.com/users/felixfbecker","avatar_url":"https://avatars.githubusercontent.com/u/10532611?"},"repo":{"id":41288708,"name":"sourcegraph/sourcegraph","url":"https://api.github.com/repos/sourcegraph/sourcegraph"},"payload":{"ref":"insights-backend-codenotify","ref_type":"branch","master_branch":"main","description":"Universal code search (self-hosted)","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":3979584,"login":"sourcegraph","gravatar_id":"","url":"https://api.github.com/orgs/sourcegraph","avatar_url":"https://avatars.githubusercontent.com/u/3979584?"}} +{"id":"17246339844","type":"CreateEvent","actor":{"id":63582211,"login":"imarco20","display_login":"imarco20","gravatar_id":"","url":"https://api.github.com/users/imarco20","avatar_url":"https://avatars.githubusercontent.com/u/63582211?"},"repo":{"id":388055757,"name":"imarco20/Lets-Go","url":"https://api.github.com/repos/imarco20/Lets-Go"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":"This repository contains the code I am writing based on reading \"Let's Go\" Book by Alex Edwards","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339853","type":"PushEvent","actor":{"id":1991673,"login":"DanySK","display_login":"DanySK","gravatar_id":"","url":"https://api.github.com/users/DanySK","avatar_url":"https://avatars.githubusercontent.com/u/1991673?"},"repo":{"id":235406820,"name":"DanySK/alchemist-intellij-plugin","url":"https://api.github.com/repos/DanySK/alchemist-intellij-plugin"},"payload":{"push_id":7562451285,"size":1,"distinct_size":1,"ref":"refs/heads/bump-git-sensitive-semantic-versioning-to-0.3.0","head":"c59f22b383ca28be1b82dd6b4b1a1ce98e22fb40","before":"1b30adf1385930b72aec988870c29cc12d72ced1","commits":[{"sha":"c59f22b383ca28be1b82dd6b4b1a1ce98e22fb40","author":{"name":"Danilo Pianini","email":"606269d560238419e4b69aaa991b3a01c7df4fc1@unibo.it"},"message":"Update build.gradle.kts","distinct":true,"url":"https://api.github.com/repos/DanySK/alchemist-intellij-plugin/commits/c59f22b383ca28be1b82dd6b4b1a1ce98e22fb40"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339857","type":"CreateEvent","actor":{"id":58910463,"login":"davendu","display_login":"davendu","gravatar_id":"","url":"https://api.github.com/users/davendu","avatar_url":"https://avatars.githubusercontent.com/u/58910463?"},"repo":{"id":388054998,"name":"davendu/dnsmasq-china-list","url":"https://api.github.com/repos/davendu/dnsmasq-china-list"},"payload":{"ref":"feat/binaryai","ref_type":"branch","master_branch":"master","description":"Chinese-specific configuration to improve your favorite DNS server. Best partner for chnroutes.","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339869","type":"PullRequestEvent","actor":{"id":45790494,"login":"merlynjocol","display_login":"merlynjocol","gravatar_id":"","url":"https://api.github.com/users/merlynjocol","avatar_url":"https://avatars.githubusercontent.com/u/45790494?"},"repo":{"id":387825491,"name":"merlynjocol/merlynjocol.github.io","url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io"},"payload":{"action":"opened","number":1,"pull_request":{"url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/1","id":694185723,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzIz","html_url":"https://github.com/merlynjocol/merlynjocol.github.io/pull/1","diff_url":"https://github.com/merlynjocol/merlynjocol.github.io/pull/1.diff","patch_url":"https://github.com/merlynjocol/merlynjocol.github.io/pull/1.patch","issue_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/1","number":1,"state":"open","locked":false,"title":"Update _config.yml","user":{"login":"merlynjocol","id":45790494,"node_id":"MDQ6VXNlcjQ1NzkwNDk0","avatar_url":"https://avatars.githubusercontent.com/u/45790494?v=4","gravatar_id":"","url":"https://api.github.com/users/merlynjocol","html_url":"https://github.com/merlynjocol","followers_url":"https://api.github.com/users/merlynjocol/followers","following_url":"https://api.github.com/users/merlynjocol/following{/other_user}","gists_url":"https://api.github.com/users/merlynjocol/gists{/gist_id}","starred_url":"https://api.github.com/users/merlynjocol/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/merlynjocol/subscriptions","organizations_url":"https://api.github.com/users/merlynjocol/orgs","repos_url":"https://api.github.com/users/merlynjocol/repos","events_url":"https://api.github.com/users/merlynjocol/events{/privacy}","received_events_url":"https://api.github.com/users/merlynjocol/received_events","type":"User","site_admin":false},"body":"configuration Theme_Memoirs \r\nhttps://bootstrapstarter.com/jekyll-theme-memoirs/","created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/1/commits","review_comments_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/1/comments","review_comment_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/1/comments","statuses_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/statuses/72657a9062f8c9a69523078225b83ed01ae790ac","head":{"label":"merlynjocol:merlynjocol-patch-1","ref":"merlynjocol-patch-1","sha":"72657a9062f8c9a69523078225b83ed01ae790ac","user":{"login":"merlynjocol","id":45790494,"node_id":"MDQ6VXNlcjQ1NzkwNDk0","avatar_url":"https://avatars.githubusercontent.com/u/45790494?v=4","gravatar_id":"","url":"https://api.github.com/users/merlynjocol","html_url":"https://github.com/merlynjocol","followers_url":"https://api.github.com/users/merlynjocol/followers","following_url":"https://api.github.com/users/merlynjocol/following{/other_user}","gists_url":"https://api.github.com/users/merlynjocol/gists{/gist_id}","starred_url":"https://api.github.com/users/merlynjocol/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/merlynjocol/subscriptions","organizations_url":"https://api.github.com/users/merlynjocol/orgs","repos_url":"https://api.github.com/users/merlynjocol/repos","events_url":"https://api.github.com/users/merlynjocol/events{/privacy}","received_events_url":"https://api.github.com/users/merlynjocol/received_events","type":"User","site_admin":false},"repo":{"id":387825491,"node_id":"MDEwOlJlcG9zaXRvcnkzODc4MjU0OTE=","name":"merlynjocol.github.io","full_name":"merlynjocol/merlynjocol.github.io","private":false,"owner":{"login":"merlynjocol","id":45790494,"node_id":"MDQ6VXNlcjQ1NzkwNDk0","avatar_url":"https://avatars.githubusercontent.com/u/45790494?v=4","gravatar_id":"","url":"https://api.github.com/users/merlynjocol","html_url":"https://github.com/merlynjocol","followers_url":"https://api.github.com/users/merlynjocol/followers","following_url":"https://api.github.com/users/merlynjocol/following{/other_user}","gists_url":"https://api.github.com/users/merlynjocol/gists{/gist_id}","starred_url":"https://api.github.com/users/merlynjocol/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/merlynjocol/subscriptions","organizations_url":"https://api.github.com/users/merlynjocol/orgs","repos_url":"https://api.github.com/users/merlynjocol/repos","events_url":"https://api.github.com/users/merlynjocol/events{/privacy}","received_events_url":"https://api.github.com/users/merlynjocol/received_events","type":"User","site_admin":false},"html_url":"https://github.com/merlynjocol/merlynjocol.github.io","description":"Portfolio of a Professional in the International Cooperation sector and Digital transformation towards SDG's","fork":false,"url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io","forks_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/forks","keys_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/teams","hooks_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/hooks","issue_events_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/events","assignees_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/tags","blobs_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/languages","stargazers_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/stargazers","contributors_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/contributors","subscribers_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/subscribers","subscription_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/subscription","commits_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/merges","archive_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/downloads","issues_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/labels{/name}","releases_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/deployments","created_at":"2021-07-20T14:53:27Z","updated_at":"2021-07-21T08:51:06Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/merlynjocol/merlynjocol.github.io.git","ssh_url":"git@github.com:merlynjocol/merlynjocol.github.io.git","clone_url":"https://github.com/merlynjocol/merlynjocol.github.io.git","svn_url":"https://github.com/merlynjocol/merlynjocol.github.io","homepage":"","size":22,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"base":{"label":"merlynjocol:main","ref":"main","sha":"e607c9b78640103bb1f65d2a48cb8afe4c1bac88","user":{"login":"merlynjocol","id":45790494,"node_id":"MDQ6VXNlcjQ1NzkwNDk0","avatar_url":"https://avatars.githubusercontent.com/u/45790494?v=4","gravatar_id":"","url":"https://api.github.com/users/merlynjocol","html_url":"https://github.com/merlynjocol","followers_url":"https://api.github.com/users/merlynjocol/followers","following_url":"https://api.github.com/users/merlynjocol/following{/other_user}","gists_url":"https://api.github.com/users/merlynjocol/gists{/gist_id}","starred_url":"https://api.github.com/users/merlynjocol/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/merlynjocol/subscriptions","organizations_url":"https://api.github.com/users/merlynjocol/orgs","repos_url":"https://api.github.com/users/merlynjocol/repos","events_url":"https://api.github.com/users/merlynjocol/events{/privacy}","received_events_url":"https://api.github.com/users/merlynjocol/received_events","type":"User","site_admin":false},"repo":{"id":387825491,"node_id":"MDEwOlJlcG9zaXRvcnkzODc4MjU0OTE=","name":"merlynjocol.github.io","full_name":"merlynjocol/merlynjocol.github.io","private":false,"owner":{"login":"merlynjocol","id":45790494,"node_id":"MDQ6VXNlcjQ1NzkwNDk0","avatar_url":"https://avatars.githubusercontent.com/u/45790494?v=4","gravatar_id":"","url":"https://api.github.com/users/merlynjocol","html_url":"https://github.com/merlynjocol","followers_url":"https://api.github.com/users/merlynjocol/followers","following_url":"https://api.github.com/users/merlynjocol/following{/other_user}","gists_url":"https://api.github.com/users/merlynjocol/gists{/gist_id}","starred_url":"https://api.github.com/users/merlynjocol/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/merlynjocol/subscriptions","organizations_url":"https://api.github.com/users/merlynjocol/orgs","repos_url":"https://api.github.com/users/merlynjocol/repos","events_url":"https://api.github.com/users/merlynjocol/events{/privacy}","received_events_url":"https://api.github.com/users/merlynjocol/received_events","type":"User","site_admin":false},"html_url":"https://github.com/merlynjocol/merlynjocol.github.io","description":"Portfolio of a Professional in the International Cooperation sector and Digital transformation towards SDG's","fork":false,"url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io","forks_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/forks","keys_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/teams","hooks_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/hooks","issue_events_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/events","assignees_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/tags","blobs_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/languages","stargazers_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/stargazers","contributors_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/contributors","subscribers_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/subscribers","subscription_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/subscription","commits_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/merges","archive_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/downloads","issues_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/labels{/name}","releases_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/deployments","created_at":"2021-07-20T14:53:27Z","updated_at":"2021-07-21T08:51:06Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/merlynjocol/merlynjocol.github.io.git","ssh_url":"git@github.com:merlynjocol/merlynjocol.github.io.git","clone_url":"https://github.com/merlynjocol/merlynjocol.github.io.git","svn_url":"https://github.com/merlynjocol/merlynjocol.github.io","homepage":"","size":22,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/1"},"html":{"href":"https://github.com/merlynjocol/merlynjocol.github.io/pull/1"},"issue":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/1"},"comments":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/merlynjocol/merlynjocol.github.io/statuses/72657a9062f8c9a69523078225b83ed01ae790ac"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":1,"deletions":1,"changed_files":1}},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339892","type":"PullRequestEvent","actor":{"id":10532611,"login":"felixfbecker","display_login":"felixfbecker","gravatar_id":"","url":"https://api.github.com/users/felixfbecker","avatar_url":"https://avatars.githubusercontent.com/u/10532611?"},"repo":{"id":41288708,"name":"sourcegraph/sourcegraph","url":"https://api.github.com/repos/sourcegraph/sourcegraph"},"payload":{"action":"opened","number":23063,"pull_request":{"url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/23063","id":694185726,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzI2","html_url":"https://github.com/sourcegraph/sourcegraph/pull/23063","diff_url":"https://github.com/sourcegraph/sourcegraph/pull/23063.diff","patch_url":"https://github.com/sourcegraph/sourcegraph/pull/23063.patch","issue_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/23063","number":23063,"state":"open","locked":false,"title":"Remove myself from CODENOTIFY for insights","user":{"login":"felixfbecker","id":10532611,"node_id":"MDQ6VXNlcjEwNTMyNjEx","avatar_url":"https://avatars.githubusercontent.com/u/10532611?v=4","gravatar_id":"","url":"https://api.github.com/users/felixfbecker","html_url":"https://github.com/felixfbecker","followers_url":"https://api.github.com/users/felixfbecker/followers","following_url":"https://api.github.com/users/felixfbecker/following{/other_user}","gists_url":"https://api.github.com/users/felixfbecker/gists{/gist_id}","starred_url":"https://api.github.com/users/felixfbecker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/felixfbecker/subscriptions","organizations_url":"https://api.github.com/users/felixfbecker/orgs","repos_url":"https://api.github.com/users/felixfbecker/repos","events_url":"https://api.github.com/users/felixfbecker/events{/privacy}","received_events_url":"https://api.github.com/users/felixfbecker/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/23063/commits","review_comments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/23063/comments","review_comment_url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/23063/comments","statuses_url":"https://api.github.com/repos/sourcegraph/sourcegraph/statuses/1739139b8c442270d76df2900ca266f1e0e6d675","head":{"label":"sourcegraph:insights-backend-codenotify","ref":"insights-backend-codenotify","sha":"1739139b8c442270d76df2900ca266f1e0e6d675","user":{"login":"sourcegraph","id":3979584,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5Nzk1ODQ=","avatar_url":"https://avatars.githubusercontent.com/u/3979584?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcegraph","html_url":"https://github.com/sourcegraph","followers_url":"https://api.github.com/users/sourcegraph/followers","following_url":"https://api.github.com/users/sourcegraph/following{/other_user}","gists_url":"https://api.github.com/users/sourcegraph/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcegraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcegraph/subscriptions","organizations_url":"https://api.github.com/users/sourcegraph/orgs","repos_url":"https://api.github.com/users/sourcegraph/repos","events_url":"https://api.github.com/users/sourcegraph/events{/privacy}","received_events_url":"https://api.github.com/users/sourcegraph/received_events","type":"Organization","site_admin":false},"repo":{"id":41288708,"node_id":"MDEwOlJlcG9zaXRvcnk0MTI4ODcwOA==","name":"sourcegraph","full_name":"sourcegraph/sourcegraph","private":false,"owner":{"login":"sourcegraph","id":3979584,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5Nzk1ODQ=","avatar_url":"https://avatars.githubusercontent.com/u/3979584?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcegraph","html_url":"https://github.com/sourcegraph","followers_url":"https://api.github.com/users/sourcegraph/followers","following_url":"https://api.github.com/users/sourcegraph/following{/other_user}","gists_url":"https://api.github.com/users/sourcegraph/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcegraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcegraph/subscriptions","organizations_url":"https://api.github.com/users/sourcegraph/orgs","repos_url":"https://api.github.com/users/sourcegraph/repos","events_url":"https://api.github.com/users/sourcegraph/events{/privacy}","received_events_url":"https://api.github.com/users/sourcegraph/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sourcegraph/sourcegraph","description":"Universal code search (self-hosted)","fork":false,"url":"https://api.github.com/repos/sourcegraph/sourcegraph","forks_url":"https://api.github.com/repos/sourcegraph/sourcegraph/forks","keys_url":"https://api.github.com/repos/sourcegraph/sourcegraph/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sourcegraph/sourcegraph/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sourcegraph/sourcegraph/teams","hooks_url":"https://api.github.com/repos/sourcegraph/sourcegraph/hooks","issue_events_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/events{/number}","events_url":"https://api.github.com/repos/sourcegraph/sourcegraph/events","assignees_url":"https://api.github.com/repos/sourcegraph/sourcegraph/assignees{/user}","branches_url":"https://api.github.com/repos/sourcegraph/sourcegraph/branches{/branch}","tags_url":"https://api.github.com/repos/sourcegraph/sourcegraph/tags","blobs_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/refs{/sha}","trees_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sourcegraph/sourcegraph/statuses/{sha}","languages_url":"https://api.github.com/repos/sourcegraph/sourcegraph/languages","stargazers_url":"https://api.github.com/repos/sourcegraph/sourcegraph/stargazers","contributors_url":"https://api.github.com/repos/sourcegraph/sourcegraph/contributors","subscribers_url":"https://api.github.com/repos/sourcegraph/sourcegraph/subscribers","subscription_url":"https://api.github.com/repos/sourcegraph/sourcegraph/subscription","commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/commits{/sha}","git_commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/commits{/sha}","comments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/comments{/number}","issue_comment_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/comments{/number}","contents_url":"https://api.github.com/repos/sourcegraph/sourcegraph/contents/{+path}","compare_url":"https://api.github.com/repos/sourcegraph/sourcegraph/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sourcegraph/sourcegraph/merges","archive_url":"https://api.github.com/repos/sourcegraph/sourcegraph/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sourcegraph/sourcegraph/downloads","issues_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues{/number}","pulls_url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls{/number}","milestones_url":"https://api.github.com/repos/sourcegraph/sourcegraph/milestones{/number}","notifications_url":"https://api.github.com/repos/sourcegraph/sourcegraph/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sourcegraph/sourcegraph/labels{/name}","releases_url":"https://api.github.com/repos/sourcegraph/sourcegraph/releases{/id}","deployments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/deployments","created_at":"2015-08-24T07:27:28Z","updated_at":"2021-07-21T08:57:54Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/sourcegraph/sourcegraph.git","ssh_url":"git@github.com:sourcegraph/sourcegraph.git","clone_url":"https://github.com/sourcegraph/sourcegraph.git","svn_url":"https://github.com/sourcegraph/sourcegraph","homepage":"https://sourcegraph.com","size":512321,"stargazers_count":4894,"watchers_count":4894,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":579,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2534,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":579,"open_issues":2534,"watchers":4894,"default_branch":"main"}},"base":{"label":"sourcegraph:main","ref":"main","sha":"aaec1239e46ef2e7899542472e35dcac68342769","user":{"login":"sourcegraph","id":3979584,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5Nzk1ODQ=","avatar_url":"https://avatars.githubusercontent.com/u/3979584?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcegraph","html_url":"https://github.com/sourcegraph","followers_url":"https://api.github.com/users/sourcegraph/followers","following_url":"https://api.github.com/users/sourcegraph/following{/other_user}","gists_url":"https://api.github.com/users/sourcegraph/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcegraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcegraph/subscriptions","organizations_url":"https://api.github.com/users/sourcegraph/orgs","repos_url":"https://api.github.com/users/sourcegraph/repos","events_url":"https://api.github.com/users/sourcegraph/events{/privacy}","received_events_url":"https://api.github.com/users/sourcegraph/received_events","type":"Organization","site_admin":false},"repo":{"id":41288708,"node_id":"MDEwOlJlcG9zaXRvcnk0MTI4ODcwOA==","name":"sourcegraph","full_name":"sourcegraph/sourcegraph","private":false,"owner":{"login":"sourcegraph","id":3979584,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5Nzk1ODQ=","avatar_url":"https://avatars.githubusercontent.com/u/3979584?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcegraph","html_url":"https://github.com/sourcegraph","followers_url":"https://api.github.com/users/sourcegraph/followers","following_url":"https://api.github.com/users/sourcegraph/following{/other_user}","gists_url":"https://api.github.com/users/sourcegraph/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcegraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcegraph/subscriptions","organizations_url":"https://api.github.com/users/sourcegraph/orgs","repos_url":"https://api.github.com/users/sourcegraph/repos","events_url":"https://api.github.com/users/sourcegraph/events{/privacy}","received_events_url":"https://api.github.com/users/sourcegraph/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sourcegraph/sourcegraph","description":"Universal code search (self-hosted)","fork":false,"url":"https://api.github.com/repos/sourcegraph/sourcegraph","forks_url":"https://api.github.com/repos/sourcegraph/sourcegraph/forks","keys_url":"https://api.github.com/repos/sourcegraph/sourcegraph/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sourcegraph/sourcegraph/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sourcegraph/sourcegraph/teams","hooks_url":"https://api.github.com/repos/sourcegraph/sourcegraph/hooks","issue_events_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/events{/number}","events_url":"https://api.github.com/repos/sourcegraph/sourcegraph/events","assignees_url":"https://api.github.com/repos/sourcegraph/sourcegraph/assignees{/user}","branches_url":"https://api.github.com/repos/sourcegraph/sourcegraph/branches{/branch}","tags_url":"https://api.github.com/repos/sourcegraph/sourcegraph/tags","blobs_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/refs{/sha}","trees_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sourcegraph/sourcegraph/statuses/{sha}","languages_url":"https://api.github.com/repos/sourcegraph/sourcegraph/languages","stargazers_url":"https://api.github.com/repos/sourcegraph/sourcegraph/stargazers","contributors_url":"https://api.github.com/repos/sourcegraph/sourcegraph/contributors","subscribers_url":"https://api.github.com/repos/sourcegraph/sourcegraph/subscribers","subscription_url":"https://api.github.com/repos/sourcegraph/sourcegraph/subscription","commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/commits{/sha}","git_commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/commits{/sha}","comments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/comments{/number}","issue_comment_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/comments{/number}","contents_url":"https://api.github.com/repos/sourcegraph/sourcegraph/contents/{+path}","compare_url":"https://api.github.com/repos/sourcegraph/sourcegraph/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sourcegraph/sourcegraph/merges","archive_url":"https://api.github.com/repos/sourcegraph/sourcegraph/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sourcegraph/sourcegraph/downloads","issues_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues{/number}","pulls_url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls{/number}","milestones_url":"https://api.github.com/repos/sourcegraph/sourcegraph/milestones{/number}","notifications_url":"https://api.github.com/repos/sourcegraph/sourcegraph/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sourcegraph/sourcegraph/labels{/name}","releases_url":"https://api.github.com/repos/sourcegraph/sourcegraph/releases{/id}","deployments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/deployments","created_at":"2015-08-24T07:27:28Z","updated_at":"2021-07-21T08:57:54Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/sourcegraph/sourcegraph.git","ssh_url":"git@github.com:sourcegraph/sourcegraph.git","clone_url":"https://github.com/sourcegraph/sourcegraph.git","svn_url":"https://github.com/sourcegraph/sourcegraph","homepage":"https://sourcegraph.com","size":512321,"stargazers_count":4894,"watchers_count":4894,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":579,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2534,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":579,"open_issues":2534,"watchers":4894,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/23063"},"html":{"href":"https://github.com/sourcegraph/sourcegraph/pull/23063"},"issue":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/23063"},"comments":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/23063/comments"},"review_comments":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/23063/comments"},"review_comment":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls/23063/commits"},"statuses":{"href":"https://api.github.com/repos/sourcegraph/sourcegraph/statuses/1739139b8c442270d76df2900ca266f1e0e6d675"}},"author_association":"MEMBER","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":2,"deletions":2,"changed_files":2}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":3979584,"login":"sourcegraph","gravatar_id":"","url":"https://api.github.com/orgs/sourcegraph","avatar_url":"https://avatars.githubusercontent.com/u/3979584?"}} +{"id":"17246339861","type":"WatchEvent","actor":{"id":664076,"login":"jonaskello","display_login":"jonaskello","gravatar_id":"","url":"https://api.github.com/users/jonaskello","avatar_url":"https://avatars.githubusercontent.com/u/664076?"},"repo":{"id":178991158,"name":"starship/starship","url":"https://api.github.com/repos/starship/starship"},"payload":{"action":"started"},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":49654870,"login":"starship","gravatar_id":"","url":"https://api.github.com/orgs/starship","avatar_url":"https://avatars.githubusercontent.com/u/49654870?"}} +{"id":"17246339863","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7562451286,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"b6cb047bb302033ffc190e6274c47cd04a5b7f0d","before":"31adc436527008f4fbd031f1b2d64e18bb6184c5","commits":[{"sha":"b6cb047bb302033ffc190e6274c47cd04a5b7f0d","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/b6cb047bb302033ffc190e6274c47cd04a5b7f0d"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339864","type":"DeleteEvent","actor":{"id":10810283,"login":"direwolf-github","display_login":"direwolf-github","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","avatar_url":"https://avatars.githubusercontent.com/u/10810283?"},"repo":{"id":183051410,"name":"direwolf-github/my-app","url":"https://api.github.com/repos/direwolf-github/my-app"},"payload":{"ref":"branch-a6ccd853","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339877","type":"PushEvent","actor":{"id":8517910,"login":"LombiqBot","display_login":"LombiqBot","gravatar_id":"","url":"https://api.github.com/users/LombiqBot","avatar_url":"https://avatars.githubusercontent.com/u/8517910?"},"repo":{"id":264168728,"name":"Lombiq/OrchardCMS.Poll","url":"https://api.github.com/repos/Lombiq/OrchardCMS.Poll"},"payload":{"push_id":7562451296,"size":0,"distinct_size":0,"ref":"refs/heads/dev","head":"0ad7be63fcce942d979bb024f403dc08f789e593","before":"0ad7be63fcce942d979bb024f403dc08f789e593","commits":[]},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":8158177,"login":"Lombiq","gravatar_id":"","url":"https://api.github.com/orgs/Lombiq","avatar_url":"https://avatars.githubusercontent.com/u/8158177?"}} +{"id":"17246339882","type":"PushEvent","actor":{"id":67819815,"login":"PRASAD-DANGARE","display_login":"PRASAD-DANGARE","gravatar_id":"","url":"https://api.github.com/users/PRASAD-DANGARE","avatar_url":"https://avatars.githubusercontent.com/u/67819815?"},"repo":{"id":358221901,"name":"PRASAD-DANGARE/Machine_Learning-Applications","url":"https://api.github.com/repos/PRASAD-DANGARE/Machine_Learning-Applications"},"payload":{"push_id":7562451317,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"283fac602e08db879ce0a589afee949a609e5a06","before":"59f29fc9287f8d8dc16ff41a32e68c5dfae4299e","commits":[{"sha":"283fac602e08db879ce0a589afee949a609e5a06","author":{"name":"Prasad_Dangare","email":"b0d2f1d57f52050017900d03cca92a142a984e26@users.noreply.github.com"},"message":"About This Repository....","distinct":true,"url":"https://api.github.com/repos/PRASAD-DANGARE/Machine_Learning-Applications/commits/283fac602e08db879ce0a589afee949a609e5a06"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339884","type":"PushEvent","actor":{"id":68853062,"login":"lkunjumon","display_login":"lkunjumon","gravatar_id":"","url":"https://api.github.com/users/lkunjumon","avatar_url":"https://avatars.githubusercontent.com/u/68853062?"},"repo":{"id":333340959,"name":"lkunjumon/sonic-buildimage-1","url":"https://api.github.com/repos/lkunjumon/sonic-buildimage-1"},"payload":{"push_id":7562451305,"size":1,"distinct_size":1,"ref":"refs/heads/PMON","head":"8047bbf4d06f077d11a843d9a5fb100d929a491c","before":"d1ff6ae85e0429def58b91dad4948eef5c023b83","commits":[{"sha":"8047bbf4d06f077d11a843d9a5fb100d929a491c","author":{"name":"lkunjumon","email":"48c45adbe7f7c490f6a6115cfca94c89937402fb@marvell.com"},"message":"Adding platform changes for intel","distinct":true,"url":"https://api.github.com/repos/lkunjumon/sonic-buildimage-1/commits/8047bbf4d06f077d11a843d9a5fb100d929a491c"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339885","type":"PushEvent","actor":{"id":79913779,"login":"conda-forge-curator[bot]","display_login":"conda-forge-curator","gravatar_id":"","url":"https://api.github.com/users/conda-forge-curator[bot]","avatar_url":"https://avatars.githubusercontent.com/u/79913779?"},"repo":{"id":286991282,"name":"conda-forge/repodata-shards","url":"https://api.github.com/repos/conda-forge/repodata-shards"},"payload":{"push_id":7562451315,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"f075d97679af69fa2099a519d5d9aa8f65483e67","before":"b621128ef3719675e3398376b07e3de84f00c1f1","commits":[{"sha":"f075d97679af69fa2099a519d5d9aa8f65483e67","author":{"name":"conda-forge-curator[bot]","email":"33ed12a5a1eeb5dbf95e50afdeb87971316ce7da@users.noreply.github.com"},"message":"added win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2 [ci skip] [cf admin skip] ***NO_CI***","distinct":true,"url":"https://api.github.com/repos/conda-forge/repodata-shards/commits/f075d97679af69fa2099a519d5d9aa8f65483e67"}]},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":11897326,"login":"conda-forge","gravatar_id":"","url":"https://api.github.com/orgs/conda-forge","avatar_url":"https://avatars.githubusercontent.com/u/11897326?"}} +{"id":"17246339896","type":"PushEvent","actor":{"id":44903710,"login":"Mj-Techs","display_login":"Mj-Techs","gravatar_id":"","url":"https://api.github.com/users/Mj-Techs","avatar_url":"https://avatars.githubusercontent.com/u/44903710?"},"repo":{"id":388055598,"name":"Mj-Techs/memories-api","url":"https://api.github.com/repos/Mj-Techs/memories-api"},"payload":{"push_id":7562451313,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"f52220369c2643b672096666f57c0c7f564a8276","before":"f2b2a06140d61c9b58c097b708e685031aa94bb7","commits":[{"sha":"f52220369c2643b672096666f57c0c7f564a8276","author":{"name":"Mrityunjay","email":"47fbbf25c13870316dd8aa8a59dea16366eabc82@users.noreply.github.com"},"message":"'update'","distinct":true,"url":"https://api.github.com/repos/Mj-Techs/memories-api/commits/f52220369c2643b672096666f57c0c7f564a8276"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339899","type":"CreateEvent","actor":{"id":45231395,"login":"appleRtsan","display_login":"appleRtsan","gravatar_id":"","url":"https://api.github.com/users/appleRtsan","avatar_url":"https://avatars.githubusercontent.com/u/45231395?"},"repo":{"id":388055402,"name":"appleRtsan/testdlib","url":"https://api.github.com/repos/appleRtsan/testdlib"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339900","type":"PullRequestEvent","actor":{"id":6995524,"login":"yurabakhtin","display_login":"yurabakhtin","gravatar_id":"","url":"https://api.github.com/users/yurabakhtin","avatar_url":"https://avatars.githubusercontent.com/u/6995524?"},"repo":{"id":16685462,"name":"humhub/humhub","url":"https://api.github.com/repos/humhub/humhub"},"payload":{"action":"opened","number":5181,"pull_request":{"url":"https://api.github.com/repos/humhub/humhub/pulls/5181","id":694185722,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTg1NzIy","html_url":"https://github.com/humhub/humhub/pull/5181","diff_url":"https://github.com/humhub/humhub/pull/5181.diff","patch_url":"https://github.com/humhub/humhub/pull/5181.patch","issue_url":"https://api.github.com/repos/humhub/humhub/issues/5181","number":5181,"state":"open","locked":false,"title":"Allow to disable comments per content","user":{"login":"yurabakhtin","id":6995524,"node_id":"MDQ6VXNlcjY5OTU1MjQ=","avatar_url":"https://avatars.githubusercontent.com/u/6995524?v=4","gravatar_id":"","url":"https://api.github.com/users/yurabakhtin","html_url":"https://github.com/yurabakhtin","followers_url":"https://api.github.com/users/yurabakhtin/followers","following_url":"https://api.github.com/users/yurabakhtin/following{/other_user}","gists_url":"https://api.github.com/users/yurabakhtin/gists{/gist_id}","starred_url":"https://api.github.com/users/yurabakhtin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yurabakhtin/subscriptions","organizations_url":"https://api.github.com/users/yurabakhtin/orgs","repos_url":"https://api.github.com/users/yurabakhtin/repos","events_url":"https://api.github.com/users/yurabakhtin/events{/privacy}","received_events_url":"https://api.github.com/users/yurabakhtin/received_events","type":"User","site_admin":false},"body":"\r\n\r\n\r\n**What kind of change does this PR introduce?** (check at least one)\r\n\r\n- [x] Feature\r\n- [x] Code style update\r\n- [x] Refactor\r\n\r\n**The PR fulfills these requirements:**\r\n\r\n- [z] It's submitted to the `develop` branch, _not_ the `master` branch if no hotfix\r\n- [ ] All tests are passing\r\n- [ ] Changelog was modified\r\n\r\n**Other information:**\r\nIssue: https://github.com/humhub/humhub/issues/4495","created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[{"login":"luke-","id":4736168,"node_id":"MDQ6VXNlcjQ3MzYxNjg=","avatar_url":"https://avatars.githubusercontent.com/u/4736168?v=4","gravatar_id":"","url":"https://api.github.com/users/luke-","html_url":"https://github.com/luke-","followers_url":"https://api.github.com/users/luke-/followers","following_url":"https://api.github.com/users/luke-/following{/other_user}","gists_url":"https://api.github.com/users/luke-/gists{/gist_id}","starred_url":"https://api.github.com/users/luke-/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/luke-/subscriptions","organizations_url":"https://api.github.com/users/luke-/orgs","repos_url":"https://api.github.com/users/luke-/repos","events_url":"https://api.github.com/users/luke-/events{/privacy}","received_events_url":"https://api.github.com/users/luke-/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/humhub/humhub/pulls/5181/commits","review_comments_url":"https://api.github.com/repos/humhub/humhub/pulls/5181/comments","review_comment_url":"https://api.github.com/repos/humhub/humhub/pulls/comments{/number}","comments_url":"https://api.github.com/repos/humhub/humhub/issues/5181/comments","statuses_url":"https://api.github.com/repos/humhub/humhub/statuses/f228afb744865702496c50266dba0eda5fe02f31","head":{"label":"humhub:enh/4495-disable-comments","ref":"enh/4495-disable-comments","sha":"f228afb744865702496c50266dba0eda5fe02f31","user":{"login":"humhub","id":6262639,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNjI2Mzk=","avatar_url":"https://avatars.githubusercontent.com/u/6262639?v=4","gravatar_id":"","url":"https://api.github.com/users/humhub","html_url":"https://github.com/humhub","followers_url":"https://api.github.com/users/humhub/followers","following_url":"https://api.github.com/users/humhub/following{/other_user}","gists_url":"https://api.github.com/users/humhub/gists{/gist_id}","starred_url":"https://api.github.com/users/humhub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/humhub/subscriptions","organizations_url":"https://api.github.com/users/humhub/orgs","repos_url":"https://api.github.com/users/humhub/repos","events_url":"https://api.github.com/users/humhub/events{/privacy}","received_events_url":"https://api.github.com/users/humhub/received_events","type":"Organization","site_admin":false},"repo":{"id":16685462,"node_id":"MDEwOlJlcG9zaXRvcnkxNjY4NTQ2Mg==","name":"humhub","full_name":"humhub/humhub","private":false,"owner":{"login":"humhub","id":6262639,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNjI2Mzk=","avatar_url":"https://avatars.githubusercontent.com/u/6262639?v=4","gravatar_id":"","url":"https://api.github.com/users/humhub","html_url":"https://github.com/humhub","followers_url":"https://api.github.com/users/humhub/followers","following_url":"https://api.github.com/users/humhub/following{/other_user}","gists_url":"https://api.github.com/users/humhub/gists{/gist_id}","starred_url":"https://api.github.com/users/humhub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/humhub/subscriptions","organizations_url":"https://api.github.com/users/humhub/orgs","repos_url":"https://api.github.com/users/humhub/repos","events_url":"https://api.github.com/users/humhub/events{/privacy}","received_events_url":"https://api.github.com/users/humhub/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/humhub/humhub","description":"HumHub is an Open Source Enterprise Social Network. Easy to install, intuitive to use and extendable with countless freely available modules.","fork":false,"url":"https://api.github.com/repos/humhub/humhub","forks_url":"https://api.github.com/repos/humhub/humhub/forks","keys_url":"https://api.github.com/repos/humhub/humhub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/humhub/humhub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/humhub/humhub/teams","hooks_url":"https://api.github.com/repos/humhub/humhub/hooks","issue_events_url":"https://api.github.com/repos/humhub/humhub/issues/events{/number}","events_url":"https://api.github.com/repos/humhub/humhub/events","assignees_url":"https://api.github.com/repos/humhub/humhub/assignees{/user}","branches_url":"https://api.github.com/repos/humhub/humhub/branches{/branch}","tags_url":"https://api.github.com/repos/humhub/humhub/tags","blobs_url":"https://api.github.com/repos/humhub/humhub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/humhub/humhub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/humhub/humhub/git/refs{/sha}","trees_url":"https://api.github.com/repos/humhub/humhub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/humhub/humhub/statuses/{sha}","languages_url":"https://api.github.com/repos/humhub/humhub/languages","stargazers_url":"https://api.github.com/repos/humhub/humhub/stargazers","contributors_url":"https://api.github.com/repos/humhub/humhub/contributors","subscribers_url":"https://api.github.com/repos/humhub/humhub/subscribers","subscription_url":"https://api.github.com/repos/humhub/humhub/subscription","commits_url":"https://api.github.com/repos/humhub/humhub/commits{/sha}","git_commits_url":"https://api.github.com/repos/humhub/humhub/git/commits{/sha}","comments_url":"https://api.github.com/repos/humhub/humhub/comments{/number}","issue_comment_url":"https://api.github.com/repos/humhub/humhub/issues/comments{/number}","contents_url":"https://api.github.com/repos/humhub/humhub/contents/{+path}","compare_url":"https://api.github.com/repos/humhub/humhub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/humhub/humhub/merges","archive_url":"https://api.github.com/repos/humhub/humhub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/humhub/humhub/downloads","issues_url":"https://api.github.com/repos/humhub/humhub/issues{/number}","pulls_url":"https://api.github.com/repos/humhub/humhub/pulls{/number}","milestones_url":"https://api.github.com/repos/humhub/humhub/milestones{/number}","notifications_url":"https://api.github.com/repos/humhub/humhub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/humhub/humhub/labels{/name}","releases_url":"https://api.github.com/repos/humhub/humhub/releases{/id}","deployments_url":"https://api.github.com/repos/humhub/humhub/deployments","created_at":"2014-02-10T05:31:03Z","updated_at":"2021-07-20T17:30:39Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/humhub/humhub.git","ssh_url":"git@github.com:humhub/humhub.git","clone_url":"https://github.com/humhub/humhub.git","svn_url":"https://github.com/humhub/humhub","homepage":"http://www.humhub.com","size":93616,"stargazers_count":5462,"watchers_count":5462,"language":"PHP","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1545,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":513,"license":null,"forks":1545,"open_issues":513,"watchers":5462,"default_branch":"master"}},"base":{"label":"humhub:develop","ref":"develop","sha":"91d4b03b7a87eff546c4bc4dfa6a0b06559572b4","user":{"login":"humhub","id":6262639,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNjI2Mzk=","avatar_url":"https://avatars.githubusercontent.com/u/6262639?v=4","gravatar_id":"","url":"https://api.github.com/users/humhub","html_url":"https://github.com/humhub","followers_url":"https://api.github.com/users/humhub/followers","following_url":"https://api.github.com/users/humhub/following{/other_user}","gists_url":"https://api.github.com/users/humhub/gists{/gist_id}","starred_url":"https://api.github.com/users/humhub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/humhub/subscriptions","organizations_url":"https://api.github.com/users/humhub/orgs","repos_url":"https://api.github.com/users/humhub/repos","events_url":"https://api.github.com/users/humhub/events{/privacy}","received_events_url":"https://api.github.com/users/humhub/received_events","type":"Organization","site_admin":false},"repo":{"id":16685462,"node_id":"MDEwOlJlcG9zaXRvcnkxNjY4NTQ2Mg==","name":"humhub","full_name":"humhub/humhub","private":false,"owner":{"login":"humhub","id":6262639,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNjI2Mzk=","avatar_url":"https://avatars.githubusercontent.com/u/6262639?v=4","gravatar_id":"","url":"https://api.github.com/users/humhub","html_url":"https://github.com/humhub","followers_url":"https://api.github.com/users/humhub/followers","following_url":"https://api.github.com/users/humhub/following{/other_user}","gists_url":"https://api.github.com/users/humhub/gists{/gist_id}","starred_url":"https://api.github.com/users/humhub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/humhub/subscriptions","organizations_url":"https://api.github.com/users/humhub/orgs","repos_url":"https://api.github.com/users/humhub/repos","events_url":"https://api.github.com/users/humhub/events{/privacy}","received_events_url":"https://api.github.com/users/humhub/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/humhub/humhub","description":"HumHub is an Open Source Enterprise Social Network. Easy to install, intuitive to use and extendable with countless freely available modules.","fork":false,"url":"https://api.github.com/repos/humhub/humhub","forks_url":"https://api.github.com/repos/humhub/humhub/forks","keys_url":"https://api.github.com/repos/humhub/humhub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/humhub/humhub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/humhub/humhub/teams","hooks_url":"https://api.github.com/repos/humhub/humhub/hooks","issue_events_url":"https://api.github.com/repos/humhub/humhub/issues/events{/number}","events_url":"https://api.github.com/repos/humhub/humhub/events","assignees_url":"https://api.github.com/repos/humhub/humhub/assignees{/user}","branches_url":"https://api.github.com/repos/humhub/humhub/branches{/branch}","tags_url":"https://api.github.com/repos/humhub/humhub/tags","blobs_url":"https://api.github.com/repos/humhub/humhub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/humhub/humhub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/humhub/humhub/git/refs{/sha}","trees_url":"https://api.github.com/repos/humhub/humhub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/humhub/humhub/statuses/{sha}","languages_url":"https://api.github.com/repos/humhub/humhub/languages","stargazers_url":"https://api.github.com/repos/humhub/humhub/stargazers","contributors_url":"https://api.github.com/repos/humhub/humhub/contributors","subscribers_url":"https://api.github.com/repos/humhub/humhub/subscribers","subscription_url":"https://api.github.com/repos/humhub/humhub/subscription","commits_url":"https://api.github.com/repos/humhub/humhub/commits{/sha}","git_commits_url":"https://api.github.com/repos/humhub/humhub/git/commits{/sha}","comments_url":"https://api.github.com/repos/humhub/humhub/comments{/number}","issue_comment_url":"https://api.github.com/repos/humhub/humhub/issues/comments{/number}","contents_url":"https://api.github.com/repos/humhub/humhub/contents/{+path}","compare_url":"https://api.github.com/repos/humhub/humhub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/humhub/humhub/merges","archive_url":"https://api.github.com/repos/humhub/humhub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/humhub/humhub/downloads","issues_url":"https://api.github.com/repos/humhub/humhub/issues{/number}","pulls_url":"https://api.github.com/repos/humhub/humhub/pulls{/number}","milestones_url":"https://api.github.com/repos/humhub/humhub/milestones{/number}","notifications_url":"https://api.github.com/repos/humhub/humhub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/humhub/humhub/labels{/name}","releases_url":"https://api.github.com/repos/humhub/humhub/releases{/id}","deployments_url":"https://api.github.com/repos/humhub/humhub/deployments","created_at":"2014-02-10T05:31:03Z","updated_at":"2021-07-20T17:30:39Z","pushed_at":"2021-07-21T09:00:02Z","git_url":"git://github.com/humhub/humhub.git","ssh_url":"git@github.com:humhub/humhub.git","clone_url":"https://github.com/humhub/humhub.git","svn_url":"https://github.com/humhub/humhub","homepage":"http://www.humhub.com","size":93616,"stargazers_count":5462,"watchers_count":5462,"language":"PHP","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1545,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":513,"license":null,"forks":1545,"open_issues":513,"watchers":5462,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/humhub/humhub/pulls/5181"},"html":{"href":"https://github.com/humhub/humhub/pull/5181"},"issue":{"href":"https://api.github.com/repos/humhub/humhub/issues/5181"},"comments":{"href":"https://api.github.com/repos/humhub/humhub/issues/5181/comments"},"review_comments":{"href":"https://api.github.com/repos/humhub/humhub/pulls/5181/comments"},"review_comment":{"href":"https://api.github.com/repos/humhub/humhub/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/humhub/humhub/pulls/5181/commits"},"statuses":{"href":"https://api.github.com/repos/humhub/humhub/statuses/f228afb744865702496c50266dba0eda5fe02f31"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":272,"deletions":7,"changed_files":10}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":6262639,"login":"humhub","gravatar_id":"","url":"https://api.github.com/orgs/humhub","avatar_url":"https://avatars.githubusercontent.com/u/6262639?"}} +{"id":"17246339902","type":"IssueCommentEvent","actor":{"id":3491412,"login":"miguelgfierro","display_login":"miguelgfierro","gravatar_id":"","url":"https://api.github.com/users/miguelgfierro","avatar_url":"https://avatars.githubusercontent.com/u/3491412?"},"repo":{"id":149430917,"name":"microsoft/recommenders","url":"https://api.github.com/repos/microsoft/recommenders"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/microsoft/recommenders/issues/1398","repository_url":"https://api.github.com/repos/microsoft/recommenders","labels_url":"https://api.github.com/repos/microsoft/recommenders/issues/1398/labels{/name}","comments_url":"https://api.github.com/repos/microsoft/recommenders/issues/1398/comments","events_url":"https://api.github.com/repos/microsoft/recommenders/issues/1398/events","html_url":"https://github.com/microsoft/recommenders/pull/1398","id":884329895,"node_id":"MDExOlB1bGxSZXF1ZXN0NjM3NzIxMTAz","number":1398,"title":"Create azure-cg-pipelines.yml","user":{"login":"dciborow","id":9027725,"node_id":"MDQ6VXNlcjkwMjc3MjU=","avatar_url":"https://avatars.githubusercontent.com/u/9027725?v=4","gravatar_id":"","url":"https://api.github.com/users/dciborow","html_url":"https://github.com/dciborow","followers_url":"https://api.github.com/users/dciborow/followers","following_url":"https://api.github.com/users/dciborow/following{/other_user}","gists_url":"https://api.github.com/users/dciborow/gists{/gist_id}","starred_url":"https://api.github.com/users/dciborow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dciborow/subscriptions","organizations_url":"https://api.github.com/users/dciborow/orgs","repos_url":"https://api.github.com/users/dciborow/repos","events_url":"https://api.github.com/users/dciborow/events{/privacy}","received_events_url":"https://api.github.com/users/dciborow/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-05-10T14:19:06Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"author_association":"COLLABORATOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/microsoft/recommenders/pulls/1398","html_url":"https://github.com/microsoft/recommenders/pull/1398","diff_url":"https://github.com/microsoft/recommenders/pull/1398.diff","patch_url":"https://github.com/microsoft/recommenders/pull/1398.patch"},"body":"### Description\r\n\r\n\r\n\r\n\r\n### Related Issues\r\n\r\n\r\n\r\n### Checklist:\r\n\r\n\r\n- [ ] I have followed the [contribution guidelines](../CONTRIBUTING.md) and code style for this project.\r\n- [ ] I have added tests covering my contributions.\r\n- [ ] I have updated the documentation accordingly.\r\n- [ ] This PR is being made to `staging branch` and not to `main branch`.\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/microsoft/recommenders/issues/comments/884018710","html_url":"https://github.com/microsoft/recommenders/pull/1398#issuecomment-884018710","issue_url":"https://api.github.com/repos/microsoft/recommenders/issues/1398","id":884018710,"node_id":"IC_kwDOCOgihc40sQ4W","user":{"login":"miguelgfierro","id":3491412,"node_id":"MDQ6VXNlcjM0OTE0MTI=","avatar_url":"https://avatars.githubusercontent.com/u/3491412?v=4","gravatar_id":"","url":"https://api.github.com/users/miguelgfierro","html_url":"https://github.com/miguelgfierro","followers_url":"https://api.github.com/users/miguelgfierro/followers","following_url":"https://api.github.com/users/miguelgfierro/following{/other_user}","gists_url":"https://api.github.com/users/miguelgfierro/gists{/gist_id}","starred_url":"https://api.github.com/users/miguelgfierro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/miguelgfierro/subscriptions","organizations_url":"https://api.github.com/users/miguelgfierro/orgs","repos_url":"https://api.github.com/users/miguelgfierro/repos","events_url":"https://api.github.com/users/miguelgfierro/events{/privacy}","received_events_url":"https://api.github.com/users/miguelgfierro/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","author_association":"MEMBER","body":"@dciborow is this still needed?","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":6154722,"login":"microsoft","gravatar_id":"","url":"https://api.github.com/orgs/microsoft","avatar_url":"https://avatars.githubusercontent.com/u/6154722?"}} +{"id":"17246339904","type":"PushEvent","actor":{"id":58833107,"login":"hrhwve3553","display_login":"hrhwve3553","gravatar_id":"","url":"https://api.github.com/users/hrhwve3553","avatar_url":"https://avatars.githubusercontent.com/u/58833107?"},"repo":{"id":227724995,"name":"hrhwve3553/djy","url":"https://api.github.com/repos/hrhwve3553/djy"},"payload":{"push_id":7562451298,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"0807a70faf7740d66bcea2d4b49051465acf24af","before":"31ac9a45007e5c34dc9d3375d6b31ce590c9ad50","commits":[{"sha":"0807a70faf7740d66bcea2d4b49051465acf24af","author":{"name":"hrhwve3553","email":"10135ac1af9bed4c0bec7c29db44050e24a097b2@users.noreply.github.com"},"message":"Update nscrw413.md","distinct":true,"url":"https://api.github.com/repos/hrhwve3553/djy/commits/0807a70faf7740d66bcea2d4b49051465acf24af"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339907","type":"IssueCommentEvent","actor":{"id":43336822,"login":"openjdk[bot]","display_login":"openjdk","gravatar_id":"","url":"https://api.github.com/users/openjdk[bot]","avatar_url":"https://avatars.githubusercontent.com/u/43336822?"},"repo":{"id":183030217,"name":"openjdk/jdk11u-dev","url":"https://api.github.com/repos/openjdk/jdk11u-dev"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/openjdk/jdk11u-dev/issues/147","repository_url":"https://api.github.com/repos/openjdk/jdk11u-dev","labels_url":"https://api.github.com/repos/openjdk/jdk11u-dev/issues/147/labels{/name}","comments_url":"https://api.github.com/repos/openjdk/jdk11u-dev/issues/147/comments","events_url":"https://api.github.com/repos/openjdk/jdk11u-dev/issues/147/events","html_url":"https://github.com/openjdk/jdk11u-dev/pull/147","id":948786370,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNTc3MDEz","number":147,"title":"8240983: Incorrect copyright header in Apache Santuario 2.1.3 files","user":{"login":"TheRealMDoerr","id":44297768,"node_id":"MDQ6VXNlcjQ0Mjk3NzY4","avatar_url":"https://avatars.githubusercontent.com/u/44297768?v=4","gravatar_id":"","url":"https://api.github.com/users/TheRealMDoerr","html_url":"https://github.com/TheRealMDoerr","followers_url":"https://api.github.com/users/TheRealMDoerr/followers","following_url":"https://api.github.com/users/TheRealMDoerr/following{/other_user}","gists_url":"https://api.github.com/users/TheRealMDoerr/gists{/gist_id}","starred_url":"https://api.github.com/users/TheRealMDoerr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TheRealMDoerr/subscriptions","organizations_url":"https://api.github.com/users/TheRealMDoerr/orgs","repos_url":"https://api.github.com/users/TheRealMDoerr/repos","events_url":"https://api.github.com/users/TheRealMDoerr/events{/privacy}","received_events_url":"https://api.github.com/users/TheRealMDoerr/received_events","type":"User","site_admin":false},"labels":[{"id":3050563200,"node_id":"MDU6TGFiZWwzMDUwNTYzMjAw","url":"https://api.github.com/repos/openjdk/jdk11u-dev/labels/backport","name":"backport","color":"ededed","default":false,"description":null},{"id":3053112164,"node_id":"MDU6TGFiZWwzMDUzMTEyMTY0","url":"https://api.github.com/repos/openjdk/jdk11u-dev/labels/clean","name":"clean","color":"ededed","default":false,"description":null},{"id":3052342185,"node_id":"MDU6TGFiZWwzMDUyMzQyMTg1","url":"https://api.github.com/repos/openjdk/jdk11u-dev/labels/ready","name":"ready","color":"23d900","default":false,"description":"Pull request is ready to be integrated"},{"id":3050564827,"node_id":"MDU6TGFiZWwzMDUwNTY0ODI3","url":"https://api.github.com/repos/openjdk/jdk11u-dev/labels/rfr","name":"rfr","color":"ff8400","default":false,"description":"Pull request is ready for review"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2021-07-20T15:40:43Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/openjdk/jdk11u-dev/pulls/147","html_url":"https://github.com/openjdk/jdk11u-dev/pull/147","diff_url":"https://github.com/openjdk/jdk11u-dev/pull/147.diff","patch_url":"https://github.com/openjdk/jdk11u-dev/pull/147.patch"},"body":"Clean backport of JDK-8240983. Required for other changes to apply.\n\n\n---------\n### Progress\n- [x] Change must not contain extraneous whitespace\n- [x] Commit message must refer to an issue\n\n### Issue\n * [JDK-8240983](https://bugs.openjdk.java.net/browse/JDK-8240983): Incorrect copyright header in Apache Santuario 2.1.3 files\n\n\n### Reviewing\n
    Using git\n\nCheckout this PR locally: \\\n`$ git fetch https://git.openjdk.java.net/jdk11u-dev pull/147/head:pull/147` \\\n`$ git checkout pull/147`\n\nUpdate a local copy of the PR: \\\n`$ git checkout pull/147` \\\n`$ git pull https://git.openjdk.java.net/jdk11u-dev pull/147/head`\n\n
    \n
    Using Skara CLI tools\n\nCheckout this PR locally: \\\n`$ git pr checkout 147`\n\nView PR using the GUI difftool: \\\n`$ git pr show -t 147`\n\n
    \n
    Using diff file\n\nDownload this PR as a diff file: \\\nhttps://git.openjdk.java.net/jdk11u-dev/pull/147.diff\n\n
    \n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/openjdk/jdk11u-dev/issues/comments/884018712","html_url":"https://github.com/openjdk/jdk11u-dev/pull/147#issuecomment-884018712","issue_url":"https://api.github.com/repos/openjdk/jdk11u-dev/issues/147","id":884018712,"node_id":"IC_kwDOCujRyc40sQ4Y","user":{"login":"openjdk[bot]","id":43336822,"node_id":"MDM6Qm90NDMzMzY4MjI=","avatar_url":"https://avatars.githubusercontent.com/u/41768318?v=4","gravatar_id":"","url":"https://api.github.com/users/openjdk%5Bbot%5D","html_url":"https://github.com/apps/openjdk","followers_url":"https://api.github.com/users/openjdk%5Bbot%5D/followers","following_url":"https://api.github.com/users/openjdk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/openjdk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/openjdk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openjdk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/openjdk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/openjdk%5Bbot%5D/repos","events_url":"https://api.github.com/users/openjdk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/openjdk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","author_association":"NONE","body":"\nGoing to push as commit fd2ec8ad1124188b5f39005b098924f684132450.\nSince your change was applied there have been 2 commits pushed to the `master` branch:\n\n * 44ef6fef19531de102089ed21c8e6fe7b80cae03: 8236671: NullPointerException in JKS keystore\n * 240ef4496291ce25737f0012da260b834afdd37a: 8267042: bug in monitor locking/unlocking on ARM32 C1 due to uninitialized BasicObjectLock::_displaced_header\n\n\nYour commit was automatically rebased without conflicts.\n\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:02Z","org":{"id":41768318,"login":"openjdk","gravatar_id":"","url":"https://api.github.com/orgs/openjdk","avatar_url":"https://avatars.githubusercontent.com/u/41768318?"}} +{"id":"17246339908","type":"PushEvent","actor":{"id":87330551,"login":"xz241","display_login":"xz241","gravatar_id":"","url":"https://api.github.com/users/xz241","avatar_url":"https://avatars.githubusercontent.com/u/87330551?"},"repo":{"id":385328687,"name":"xz241/wp-uploads","url":"https://api.github.com/repos/xz241/wp-uploads"},"payload":{"push_id":7562451330,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"d2ef866944436499cc87340aed2c248e5c5cf793","before":"94d80572a2b9faafef90bebf50c0c6fcdd4b91b8","commits":[{"sha":"d2ef866944436499cc87340aed2c248e5c5cf793","author":{"name":"xz241","email":"e17353502d0344fb9b4d10b6a471cc23c11850ec@users.noreply.github.com"},"message":"","distinct":true,"url":"https://api.github.com/repos/xz241/wp-uploads/commits/d2ef866944436499cc87340aed2c248e5c5cf793"}]},"public":true,"created_at":"2021-07-21T09:00:02Z"} +{"id":"17246339913","type":"PushEvent","actor":{"id":65212910,"login":"Hall-1910","display_login":"Hall-1910","gravatar_id":"","url":"https://api.github.com/users/Hall-1910","avatar_url":"https://avatars.githubusercontent.com/u/65212910?"},"repo":{"id":222505696,"name":"brand22/d3","url":"https://api.github.com/repos/brand22/d3"},"payload":{"push_id":7562451339,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"af741ccd65855d20fc1dbb2960ff8fe16621f450","before":"b0b6d64988c1bc1a407224e72ee2b703970d7ecc","commits":[{"sha":"af741ccd65855d20fc1dbb2960ff8fe16621f450","author":{"name":"Hall-1910","email":"82b2a317210219cb74c49a50f60ce4b4ef49066b@users.noreply.github.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/brand22/d3/commits/af741ccd65855d20fc1dbb2960ff8fe16621f450"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339914","type":"PushEvent","actor":{"id":18316027,"login":"xn3cr0nx","display_login":"xn3cr0nx","gravatar_id":"","url":"https://api.github.com/users/xn3cr0nx","avatar_url":"https://avatars.githubusercontent.com/u/18316027?"},"repo":{"id":368538085,"name":"elysiumbridge/email-service","url":"https://api.github.com/repos/elysiumbridge/email-service"},"payload":{"push_id":7562451338,"size":1,"distinct_size":1,"ref":"refs/heads/staging","head":"989142affe8674925af9c54c6d56c9ad28ac954e","before":"5da6510fd0b51e23becbc105f136573be396eb59","commits":[{"sha":"989142affe8674925af9c54c6d56c9ad28ac954e","author":{"name":"Patrick Jusic","email":"564961049dee2a941e65b77838bc1f9b60f0e5b6@gmail.com"},"message":"Implement auction offer email","distinct":true,"url":"https://api.github.com/repos/elysiumbridge/email-service/commits/989142affe8674925af9c54c6d56c9ad28ac954e"}]},"public":true,"created_at":"2021-07-21T09:00:03Z","org":{"id":83607158,"login":"elysiumbridge","gravatar_id":"","url":"https://api.github.com/orgs/elysiumbridge","avatar_url":"https://avatars.githubusercontent.com/u/83607158?"}} +{"id":"17246339922","type":"PushEvent","actor":{"id":87753729,"login":"GabrielASC21","display_login":"GabrielASC21","gravatar_id":"","url":"https://api.github.com/users/GabrielASC21","avatar_url":"https://avatars.githubusercontent.com/u/87753729?"},"repo":{"id":388049279,"name":"GabrielASC21/myFirstRespository","url":"https://api.github.com/repos/GabrielASC21/myFirstRespository"},"payload":{"push_id":7562451321,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"7551bdc486660c117a0faa738c98554b0b440d53","before":"3e7f85565670b6a30d7b5219efeee708d108cf85","commits":[{"sha":"7551bdc486660c117a0faa738c98554b0b440d53","author":{"name":"GabrielASC21","email":"51c203f273118b7f0f2126b6ebe512537bafd420@gmail.com"},"message":"First commit","distinct":true,"url":"https://api.github.com/repos/GabrielASC21/myFirstRespository/commits/7551bdc486660c117a0faa738c98554b0b440d53"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339924","type":"PushEvent","actor":{"id":58833107,"login":"hrhwve3553","display_login":"hrhwve3553","gravatar_id":"","url":"https://api.github.com/users/hrhwve3553","avatar_url":"https://avatars.githubusercontent.com/u/58833107?"},"repo":{"id":227725053,"name":"hrhwve3553/ntdtv","url":"https://api.github.com/repos/hrhwve3553/ntdtv"},"payload":{"push_id":7562451336,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"dbad1d02038cecc3b7bb097d739f2d42b63994d8","before":"7a4554a96e3e4f8a92b3179615a8ae26af3fcfc5","commits":[{"sha":"dbad1d02038cecc3b7bb097d739f2d42b63994d8","author":{"name":"hrhwve3553","email":"10135ac1af9bed4c0bec7c29db44050e24a097b2@users.noreply.github.com"},"message":"Update culture-world_1.md","distinct":true,"url":"https://api.github.com/repos/hrhwve3553/ntdtv/commits/dbad1d02038cecc3b7bb097d739f2d42b63994d8"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339927","type":"CreateEvent","actor":{"id":85146151,"login":"Jey0205","display_login":"Jey0205","gravatar_id":"","url":"https://api.github.com/users/Jey0205","avatar_url":"https://avatars.githubusercontent.com/u/85146151?"},"repo":{"id":388056614,"name":"Jey0205/BreadJson","url":"https://api.github.com/repos/Jey0205/BreadJson"},"payload":{"ref":"main","ref_type":"branch","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339929","type":"PushEvent","actor":{"id":87743028,"login":"lukaromes895","display_login":"lukaromes895","gravatar_id":"","url":"https://api.github.com/users/lukaromes895","avatar_url":"https://avatars.githubusercontent.com/u/87743028?"},"repo":{"id":387979216,"name":"lukaromes895/verlng3","url":"https://api.github.com/repos/lukaromes895/verlng3"},"payload":{"push_id":7562451334,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"bcf7d3a89885f9ab3b3d77c576cb0194bcc3b0a7","before":"67a5c38fedd4b7f2e312ac1064e0daa32b3a57d7","commits":[{"sha":"bcf7d3a89885f9ab3b3d77c576cb0194bcc3b0a7","author":{"name":"O","email":"08a914cde05039694ef0194d9ee79ff9a79dde33@a.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/lukaromes895/verlng3/commits/bcf7d3a89885f9ab3b3d77c576cb0194bcc3b0a7"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339933","type":"PushEvent","actor":{"id":85441621,"login":"thatjohn01","display_login":"thatjohn01","gravatar_id":"","url":"https://api.github.com/users/thatjohn01","avatar_url":"https://avatars.githubusercontent.com/u/85441621?"},"repo":{"id":385329061,"name":"thatjohn01/753104284","url":"https://api.github.com/repos/thatjohn01/753104284"},"payload":{"push_id":7562451354,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"e5a712c43dd7da39f0e15cacce87e3f0c30631ee","before":"b59bb83346f2bdae15bca98990e7e101e938f800","commits":[{"sha":"e5a712c43dd7da39f0e15cacce87e3f0c30631ee","author":{"name":"thatjohn01","email":"72eb81e66410b3da65da4cca287ce0578825ce64@users.noreply.github.com"},"message":"change README.md","distinct":true,"url":"https://api.github.com/repos/thatjohn01/753104284/commits/e5a712c43dd7da39f0e15cacce87e3f0c30631ee"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339938","type":"PushEvent","actor":{"id":46065349,"login":"xhayper","display_login":"xhayper","gravatar_id":"","url":"https://api.github.com/users/xhayper","avatar_url":"https://avatars.githubusercontent.com/u/46065349?"},"repo":{"id":381891279,"name":"xhayper/Animator","url":"https://api.github.com/repos/xhayper/Animator"},"payload":{"push_id":7562451343,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"6243e063401e76a918219de22f243a0fd142e473","before":"d8ffc34451b18ddeb2fad88dcc21c5a89200fc96","commits":[{"sha":"6243e063401e76a918219de22f243a0fd142e473","author":{"name":"hayper","email":"ce1b58c20f3e4c517d1bfa5f0f98e207ce58b174@gmail.com"},"message":"a","distinct":true,"url":"https://api.github.com/repos/xhayper/Animator/commits/6243e063401e76a918219de22f243a0fd142e473"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339952","type":"PushEvent","actor":{"id":40921989,"login":"AdewaleAdeniji","display_login":"AdewaleAdeniji","gravatar_id":"","url":"https://api.github.com/users/AdewaleAdeniji","avatar_url":"https://avatars.githubusercontent.com/u/40921989?"},"repo":{"id":383033718,"name":"AdewaleAdeniji/phalconwise","url":"https://api.github.com/repos/AdewaleAdeniji/phalconwise"},"payload":{"push_id":7562451352,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"94c8410549b4731b41f683c50b628cb86ffa27b8","before":"924f7e787297ae45ce28eeba02cb33c600aa48aa","commits":[{"sha":"94c8410549b4731b41f683c50b628cb86ffa27b8","author":{"name":"Adeniji Oluwaferanmi","email":"14310bb6d5e60d4c33c4af4517b212c2fbb94f30@student.lautech.edu.ng"},"message":"commit","distinct":true,"url":"https://api.github.com/repos/AdewaleAdeniji/phalconwise/commits/94c8410549b4731b41f683c50b628cb86ffa27b8"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339953","type":"PushEvent","actor":{"id":42319300,"login":"thalals","display_login":"thalals","gravatar_id":"","url":"https://api.github.com/users/thalals","avatar_url":"https://avatars.githubusercontent.com/u/42319300?"},"repo":{"id":331019217,"name":"thalals/Algorithm_Study","url":"https://api.github.com/repos/thalals/Algorithm_Study"},"payload":{"push_id":7562451350,"size":17,"distinct_size":17,"ref":"refs/heads/main","head":"ceb29b8a84554b55edc0c4250f41761eb556ca6a","before":"cb4c3c05dc982ccf94d90f9ee6099cf1842a993b","commits":[{"sha":"8733b779d8b441f5a2d112ce052b6986aaeaffa1","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15649[해결]","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/8733b779d8b441f5a2d112ce052b6986aaeaffa1"},{"sha":"0755c70eedf1b6c12a25b70b934d9561458ddfc6","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15650(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/0755c70eedf1b6c12a25b70b934d9561458ddfc6"},{"sha":"53ccd1badc2f85cac549ffafb622c1366cf5690d","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15651(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/53ccd1badc2f85cac549ffafb622c1366cf5690d"},{"sha":"c4dd95a48908aa14e602a213c9ef3a0d9a014c07","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[UPD] BOJ15651(수정)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/c4dd95a48908aa14e602a213c9ef3a0d9a014c07"},{"sha":"7d66d5e2ce6811a14e89d4b155f9d6371ff4c13a","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15652(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/7d66d5e2ce6811a14e89d4b155f9d6371ff4c13a"},{"sha":"2e7da26646c6b17ee927daaa21c1480d392965f1","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[UPD] BOJ15652(수정)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/2e7da26646c6b17ee927daaa21c1480d392965f1"},{"sha":"4f9090e05d850f48cdafafba6a378ad73a50619b","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15654(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/4f9090e05d850f48cdafafba6a378ad73a50619b"},{"sha":"ee776f9f08539cfac46a04f8f4c27e211d1d01eb","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15655(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/ee776f9f08539cfac46a04f8f4c27e211d1d01eb"},{"sha":"d439a0396d6b2b4c51323a9cff9649fae6ecdea6","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15656(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/d439a0396d6b2b4c51323a9cff9649fae6ecdea6"},{"sha":"1c4d0dc0a29d8c085fdbeced402c1a1454ecf156","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15657(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/1c4d0dc0a29d8c085fdbeced402c1a1454ecf156"},{"sha":"f7f0bdd16df848f947835b866c61aa6cdcdc2a93","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15663(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/f7f0bdd16df848f947835b866c61aa6cdcdc2a93"},{"sha":"69279d6466789220ed8ca2de03bccdb2e0f67c3c","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15664(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/69279d6466789220ed8ca2de03bccdb2e0f67c3c"},{"sha":"65279ea9b20d8eeb8717a99970758603cc58fdbd","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15665(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/65279ea9b20d8eeb8717a99970758603cc58fdbd"},{"sha":"1c3ad6915ed47b98b1ab7b1e067e6cb36eaf4b8b","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ15666(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/1c3ad6915ed47b98b1ab7b1e067e6cb36eaf4b8b"},{"sha":"4710ea642c111d2fb4a4080ec5a87d53a7c04dd1","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ1182(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/4710ea642c111d2fb4a4080ec5a87d53a7c04dd1"},{"sha":"6bddc948f7a12cfa96dda55250c0a8b034864c1b","author":{"name":"Gun1Yun","email":"e9e18c820c59411cdbfb5a07fccd792d8b0bfb45@gmail.com"},"message":"[ADD] BOJ10971(해결)","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/6bddc948f7a12cfa96dda55250c0a8b034864c1b"},{"sha":"ceb29b8a84554b55edc0c4250f41761eb556ca6a","author":{"name":"Yun Geonil","email":"ac1688b8889497d5475a07ba16a0a8cc45fb660a@users.noreply.github.com"},"message":"Merge pull request #216 from Gun1Yun/geonil\n\nGeonil","distinct":true,"url":"https://api.github.com/repos/thalals/Algorithm_Study/commits/ceb29b8a84554b55edc0c4250f41761eb556ca6a"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339955","type":"PushEvent","actor":{"id":28945700,"login":"DX4D","display_login":"DX4D","gravatar_id":"","url":"https://api.github.com/users/DX4D","avatar_url":"https://avatars.githubusercontent.com/u/28945700?"},"repo":{"id":237986525,"name":"wovencode/OpenMMO","url":"https://api.github.com/repos/wovencode/OpenMMO"},"payload":{"push_id":7562451257,"size":2,"distinct_size":2,"ref":"refs/heads/master","head":"3efe348412815d3f1aa7fbc6c82dbfd90615c3d2","before":"8e61a08612f536319bdf0f81813819ad2f9d56ca","commits":[{"sha":"28b0b456990b1e71c7ee29d7bed6699d6b9e14bb","author":{"name":"Dave Mandrella","email":"2a887f95d0a6fa507c37ab1958e2a2e90b1f7323@gmail.com"},"message":"UPDATE MIRROR v43.3.1","distinct":true,"url":"https://api.github.com/repos/wovencode/OpenMMO/commits/28b0b456990b1e71c7ee29d7bed6699d6b9e14bb"},{"sha":"3efe348412815d3f1aa7fbc6c82dbfd90615c3d2","author":{"name":"Dave Mandrella","email":"2a887f95d0a6fa507c37ab1958e2a2e90b1f7323@gmail.com"},"message":"FIX: MIRROR UPDATE","distinct":true,"url":"https://api.github.com/repos/wovencode/OpenMMO/commits/3efe348412815d3f1aa7fbc6c82dbfd90615c3d2"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339957","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7562451348,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"9d8a22e0234d4a85de68a32e7861ffa610d57edd","before":"b6cb047bb302033ffc190e6274c47cd04a5b7f0d","commits":[{"sha":"9d8a22e0234d4a85de68a32e7861ffa610d57edd","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/9d8a22e0234d4a85de68a32e7861ffa610d57edd"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339976","type":"PushEvent","actor":{"id":1317792,"login":"xndcn","display_login":"xndcn","gravatar_id":"","url":"https://api.github.com/users/xndcn","avatar_url":"https://avatars.githubusercontent.com/u/1317792?"},"repo":{"id":10446275,"name":"xndcn/smzdm.com","url":"https://api.github.com/repos/xndcn/smzdm.com"},"payload":{"push_id":7562451358,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"74e538a609d77e731fb68444527a6e1f65cfbccb","before":"d45dc2953c0fd3e41da64373b3674c1d95b1ed98","commits":[{"sha":"74e538a609d77e731fb68444527a6e1f65cfbccb","author":{"name":"xndcn","email":"1e09e78f2250d149e0e983feae098f2c71429797@gmail.com"},"message":"30 items update","distinct":true,"url":"https://api.github.com/repos/xndcn/smzdm.com/commits/74e538a609d77e731fb68444527a6e1f65cfbccb"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339977","type":"IssuesEvent","actor":{"id":19289477,"login":"EmilJunker","display_login":"EmilJunker","gravatar_id":"","url":"https://api.github.com/users/EmilJunker","avatar_url":"https://avatars.githubusercontent.com/u/19289477?"},"repo":{"id":132147328,"name":"davidhealey/waistline","url":"https://api.github.com/repos/davidhealey/waistline"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/davidhealey/waistline/issues/269","repository_url":"https://api.github.com/repos/davidhealey/waistline","labels_url":"https://api.github.com/repos/davidhealey/waistline/issues/269/labels{/name}","comments_url":"https://api.github.com/repos/davidhealey/waistline/issues/269/comments","events_url":"https://api.github.com/repos/davidhealey/waistline/issues/269/events","html_url":"https://github.com/davidhealey/waistline/issues/269","id":949486862,"node_id":"MDU6SXNzdWU5NDk0ODY4NjI=","number":269,"title":"USDA API link not working","user":{"login":"EmilJunker","id":19289477,"node_id":"MDQ6VXNlcjE5Mjg5NDc3","avatar_url":"https://avatars.githubusercontent.com/u/19289477?v=4","gravatar_id":"","url":"https://api.github.com/users/EmilJunker","html_url":"https://github.com/EmilJunker","followers_url":"https://api.github.com/users/EmilJunker/followers","following_url":"https://api.github.com/users/EmilJunker/following{/other_user}","gists_url":"https://api.github.com/users/EmilJunker/gists{/gist_id}","starred_url":"https://api.github.com/users/EmilJunker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/EmilJunker/subscriptions","organizations_url":"https://api.github.com/users/EmilJunker/orgs","repos_url":"https://api.github.com/users/EmilJunker/repos","events_url":"https://api.github.com/users/EmilJunker/events{/privacy}","received_events_url":"https://api.github.com/users/EmilJunker/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:02Z","closed_at":null,"author_association":"NONE","active_lock_reason":null,"body":"On the USDA API settings page, it says \"You can request a free API Key by clicking _here_\" but nothing happens when I tap on the link.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339990","type":"CreateEvent","actor":{"id":80450565,"login":"Tanvisingh10","display_login":"Tanvisingh10","gravatar_id":"","url":"https://api.github.com/users/Tanvisingh10","avatar_url":"https://avatars.githubusercontent.com/u/80450565?"},"repo":{"id":388056968,"name":"Tanvisingh10/Project-84","url":"https://api.github.com/repos/Tanvisingh10/Project-84"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339995","type":"PushEvent","actor":{"id":87714900,"login":"Luissprof","display_login":"Luissprof","gravatar_id":"","url":"https://api.github.com/users/Luissprof","avatar_url":"https://avatars.githubusercontent.com/u/87714900?"},"repo":{"id":387857059,"name":"Luissprof/reposit-riodetestes","url":"https://api.github.com/repos/Luissprof/reposit-riodetestes"},"payload":{"push_id":7562451383,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"d669f56382ec09cf1f0e3dbfe6994515c4defcfb","before":"c2bd4bd2ccd354e5bc985bc234cec6e97d8aaf4b","commits":[{"sha":"d669f56382ec09cf1f0e3dbfe6994515c4defcfb","author":{"name":"Luís Filipe Santos","email":"300efc7396c1b017e7a510eafe43d3043fa92d06@criticaltechworks.com"},"message":"teste1","distinct":true,"url":"https://api.github.com/repos/Luissprof/reposit-riodetestes/commits/d669f56382ec09cf1f0e3dbfe6994515c4defcfb"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340002","type":"PushEvent","actor":{"id":16854166,"login":"juli9797","display_login":"juli9797","gravatar_id":"","url":"https://api.github.com/users/juli9797","avatar_url":"https://avatars.githubusercontent.com/u/16854166?"},"repo":{"id":387826926,"name":"juli9797/despace","url":"https://api.github.com/repos/juli9797/despace"},"payload":{"push_id":7562451364,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"e31671398e4b0acb693288c8a1fe8f090254e0a5","before":"a1250cc47cf289db73c56d3c28ab1e94190cf1d0","commits":[{"sha":"e31671398e4b0acb693288c8a1fe8f090254e0a5","author":{"name":"julian","email":"4b7893977a90fdb0a95ca7f0ee78f262e522f6a6@gmail.com"},"message":"switched to std::filesystem::rename)","distinct":true,"url":"https://api.github.com/repos/juli9797/despace/commits/e31671398e4b0acb693288c8a1fe8f090254e0a5"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340018","type":"IssuesEvent","actor":{"id":48132025,"login":"progress-git-user","display_login":"progress-git-user","gravatar_id":"","url":"https://api.github.com/users/progress-git-user","avatar_url":"https://avatars.githubusercontent.com/u/48132025?"},"repo":{"id":174307044,"name":"support-ops/sit-repo","url":"https://api.github.com/repos/support-ops/sit-repo"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/support-ops/sit-repo/issues/55296","repository_url":"https://api.github.com/repos/support-ops/sit-repo","labels_url":"https://api.github.com/repos/support-ops/sit-repo/issues/55296/labels{/name}","comments_url":"https://api.github.com/repos/support-ops/sit-repo/issues/55296/comments","events_url":"https://api.github.com/repos/support-ops/sit-repo/issues/55296/events","html_url":"https://github.com/support-ops/sit-repo/issues/55296","id":949486861,"node_id":"MDU6SXNzdWU5NDk0ODY4NjE=","number":55296,"title":"Public Thread 81a33935-791a-441c-8d22-70c438d6f679","user":{"login":"progress-git-user","id":48132025,"node_id":"MDQ6VXNlcjQ4MTMyMDI1","avatar_url":"https://avatars.githubusercontent.com/u/48132025?v=4","gravatar_id":"","url":"https://api.github.com/users/progress-git-user","html_url":"https://github.com/progress-git-user","followers_url":"https://api.github.com/users/progress-git-user/followers","following_url":"https://api.github.com/users/progress-git-user/following{/other_user}","gists_url":"https://api.github.com/users/progress-git-user/gists{/gist_id}","starred_url":"https://api.github.com/users/progress-git-user/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/progress-git-user/subscriptions","organizations_url":"https://api.github.com/users/progress-git-user/orgs","repos_url":"https://api.github.com/users/progress-git-user/repos","events_url":"https://api.github.com/users/progress-git-user/events{/privacy}","received_events_url":"https://api.github.com/users/progress-git-user/received_events","type":"User","site_admin":false},"labels":[{"id":1310856078,"node_id":"MDU6TGFiZWwxMzEwODU2MDc4","url":"https://api.github.com/repos/support-ops/sit-repo/labels/FP:%20Pending%20Review","name":"FP: Pending Review","color":"ededed","default":false,"description":null}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T09:00:02Z","updated_at":"2021-07-21T09:00:03Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"body":"Message 75208471-4231-4f0d-bfdf-9591cdeeb249","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:03Z","org":{"id":48132131,"login":"support-ops","gravatar_id":"","url":"https://api.github.com/orgs/support-ops","avatar_url":"https://avatars.githubusercontent.com/u/48132131?"}} +{"id":"17246340035","type":"PushEvent","actor":{"id":28395786,"login":"leisure1211","display_login":"leisure1211","gravatar_id":"","url":"https://api.github.com/users/leisure1211","avatar_url":"https://avatars.githubusercontent.com/u/28395786?"},"repo":{"id":276402235,"name":"leisure1211/leaf","url":"https://api.github.com/repos/leisure1211/leaf"},"payload":{"push_id":7562451384,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"0cd7a4053b5de34ecb41989d2be5115d6b7a3f2d","before":"3cb65c6958ef1e7e8c1124b4c3c50394d1801ea1","commits":[{"sha":"0cd7a4053b5de34ecb41989d2be5115d6b7a3f2d","author":{"name":"leisure1211","email":"37c4dd09a77d3ad4dd46b41239997ef191795160@qq.com"},"message":"Upload by PicGo","distinct":true,"url":"https://api.github.com/repos/leisure1211/leaf/commits/0cd7a4053b5de34ecb41989d2be5115d6b7a3f2d"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340041","type":"PushEvent","actor":{"id":1122618,"login":"AndyLnd","display_login":"AndyLnd","gravatar_id":"","url":"https://api.github.com/users/AndyLnd","avatar_url":"https://avatars.githubusercontent.com/u/1122618?"},"repo":{"id":63881328,"name":"wireapp/wire-webapp","url":"https://api.github.com/repos/wireapp/wire-webapp"},"payload":{"push_id":7562451373,"size":1,"distinct_size":1,"ref":"refs/heads/runfix/reactive-cell-state","head":"181e796a28c9ff84ac335885ad2d4f419272d1c0","before":"690fa434169d97a3b72ba3afedd71b0def2d82bb","commits":[{"sha":"181e796a28c9ff84ac335885ad2d4f419272d1c0","author":{"name":"AndyLnd","email":"e3579b1e47f273529f0f929453e939a68ede9fd1@wire.com"},"message":"remove conversation dependency","distinct":true,"url":"https://api.github.com/repos/wireapp/wire-webapp/commits/181e796a28c9ff84ac335885ad2d4f419272d1c0"}]},"public":true,"created_at":"2021-07-21T09:00:03Z","org":{"id":16047324,"login":"wireapp","gravatar_id":"","url":"https://api.github.com/orgs/wireapp","avatar_url":"https://avatars.githubusercontent.com/u/16047324?"}} +{"id":"17246340055","type":"PushEvent","actor":{"id":87330551,"login":"xz241","display_login":"xz241","gravatar_id":"","url":"https://api.github.com/users/xz241","avatar_url":"https://avatars.githubusercontent.com/u/87330551?"},"repo":{"id":385328687,"name":"xz241/wp-uploads","url":"https://api.github.com/repos/xz241/wp-uploads"},"payload":{"push_id":7562451389,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"2a09914ed7c072ee60c61bdaf9e023625cef6b19","before":"d2ef866944436499cc87340aed2c248e5c5cf793","commits":[{"sha":"2a09914ed7c072ee60c61bdaf9e023625cef6b19","author":{"name":"xz241","email":"e17353502d0344fb9b4d10b6a471cc23c11850ec@users.noreply.github.com"},"message":"","distinct":true,"url":"https://api.github.com/repos/xz241/wp-uploads/commits/2a09914ed7c072ee60c61bdaf9e023625cef6b19"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340058","type":"PushEvent","actor":{"id":87752113,"login":"nidscb","display_login":"nidscb","gravatar_id":"","url":"https://api.github.com/users/nidscb","avatar_url":"https://avatars.githubusercontent.com/u/87752113?"},"repo":{"id":388036549,"name":"iamgique/git-working","url":"https://api.github.com/repos/iamgique/git-working"},"payload":{"push_id":7562451392,"size":7,"distinct_size":7,"ref":"refs/heads/main","head":"a0c473980aa8c1922285791336bfa36e45ee9fcf","before":"efb495fa4eacb63c7a07089cea5bf2f89d0d15e4","commits":[{"sha":"ff1302e86974440e99ca4eab2927a08a52a8675b","author":{"name":"NuNid Ja","email":"35e81bf38bacc2ba4f40dbe5d23f534be8fa699e@scb.co.th"},"message":"add","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/ff1302e86974440e99ca4eab2927a08a52a8675b"},{"sha":"48bc0372b47373e1ac808f3283fdcddd2f82704b","author":{"name":"NATCHANUN WANGBOON","email":"e9b56d831f8a83c79c3c257084b666e5882dc819@scbcorp.co.th"},"message":"add Natchanun.c","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/48bc0372b47373e1ac808f3283fdcddd2f82704b"},{"sha":"8023e67468cc7417cff39b6f2a096a8cb05a5380","author":{"name":"NuNid Ja","email":"35e81bf38bacc2ba4f40dbe5d23f534be8fa699e@scb.co.th"},"message":"Merge https://github.com/iamgique/git-working into main","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/8023e67468cc7417cff39b6f2a096a8cb05a5380"},{"sha":"4d5804f80ffbe79245e49f7660cb0e8928c1dd6b","author":{"name":"NATCHANUN WANGBOON","email":"e9b56d831f8a83c79c3c257084b666e5882dc819@scbcorp.co.th"},"message":"Merge branch 'main' of https://github.com/iamgique/git-working into main","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/4d5804f80ffbe79245e49f7660cb0e8928c1dd6b"},{"sha":"754bd6980895612e108ece27199110a82d04553d","author":{"name":"NuNid Ja","email":"35e81bf38bacc2ba4f40dbe5d23f534be8fa699e@scb.co.th"},"message":"Merge https://github.com/iamgique/git-working into main","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/754bd6980895612e108ece27199110a82d04553d"},{"sha":"a5f89277f2553404812a945113dd46fac3ba5e15","author":{"name":"NuNid Ja","email":"35e81bf38bacc2ba4f40dbe5d23f534be8fa699e@scb.co.th"},"message":"nid","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/a5f89277f2553404812a945113dd46fac3ba5e15"},{"sha":"a0c473980aa8c1922285791336bfa36e45ee9fcf","author":{"name":"NuNid Ja","email":"35e81bf38bacc2ba4f40dbe5d23f534be8fa699e@scb.co.th"},"message":"Merge https://github.com/iamgique/git-working into main","distinct":true,"url":"https://api.github.com/repos/iamgique/git-working/commits/a0c473980aa8c1922285791336bfa36e45ee9fcf"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340069","type":"PushEvent","actor":{"id":82069321,"login":"HoRay86","display_login":"HoRay86","gravatar_id":"","url":"https://api.github.com/users/HoRay86","avatar_url":"https://avatars.githubusercontent.com/u/82069321?"},"repo":{"id":355380005,"name":"HoRay86/HoRay1.github.io","url":"https://api.github.com/repos/HoRay86/HoRay1.github.io"},"payload":{"push_id":7562451395,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"eee9ef47fe8ed2f3f4cb2bb5e5f7bb096f34728f","before":"dcaadaa8d91fa7091382d09a06d626848fbbad33","commits":[{"sha":"eee9ef47fe8ed2f3f4cb2bb5e5f7bb096f34728f","author":{"name":"HoRay86","email":"87acc6f87c5c2eb827b87e4817dcb9351db1ba61@users.noreply.github.com"},"message":"img更新","distinct":true,"url":"https://api.github.com/repos/HoRay86/HoRay1.github.io/commits/eee9ef47fe8ed2f3f4cb2bb5e5f7bb096f34728f"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340083","type":"PushEvent","actor":{"id":2687598,"login":"milesholt","display_login":"milesholt","gravatar_id":"","url":"https://api.github.com/users/milesholt","avatar_url":"https://avatars.githubusercontent.com/u/2687598?"},"repo":{"id":247480564,"name":"milesholt/autotrade1","url":"https://api.github.com/repos/milesholt/autotrade1"},"payload":{"push_id":7562451400,"size":1,"distinct_size":1,"ref":"refs/heads/version2","head":"fc70cfabd89fd6d77de8114666499d964eea668f","before":"4523c94960849d711badb2877a5d273c11ef90f5","commits":[{"sha":"fc70cfabd89fd6d77de8114666499d964eea668f","author":{"name":"milesholt","email":"1a73af9e7ae00182733b2292511b814be66f065f@milesholt.co.uk"},"message":"File updated - July 21, 2021 10:00 AM","distinct":true,"url":"https://api.github.com/repos/milesholt/autotrade1/commits/fc70cfabd89fd6d77de8114666499d964eea668f"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340085","type":"IssueCommentEvent","actor":{"id":6266037,"login":"Findus23","display_login":"Findus23","gravatar_id":"","url":"https://api.github.com/users/Findus23","avatar_url":"https://avatars.githubusercontent.com/u/6266037?"},"repo":{"id":1548202,"name":"matomo-org/matomo","url":"https://api.github.com/repos/matomo-org/matomo"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/matomo-org/matomo/issues/17798","repository_url":"https://api.github.com/repos/matomo-org/matomo","labels_url":"https://api.github.com/repos/matomo-org/matomo/issues/17798/labels{/name}","comments_url":"https://api.github.com/repos/matomo-org/matomo/issues/17798/comments","events_url":"https://api.github.com/repos/matomo-org/matomo/issues/17798/events","html_url":"https://github.com/matomo-org/matomo/pull/17798","id":948212798,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzMDg0MzI1","number":17798,"title":"Set CSP header to prevent XSS","user":{"login":"justinvelluppillai","id":1127121,"node_id":"MDQ6VXNlcjExMjcxMjE=","avatar_url":"https://avatars.githubusercontent.com/u/1127121?v=4","gravatar_id":"","url":"https://api.github.com/users/justinvelluppillai","html_url":"https://github.com/justinvelluppillai","followers_url":"https://api.github.com/users/justinvelluppillai/followers","following_url":"https://api.github.com/users/justinvelluppillai/following{/other_user}","gists_url":"https://api.github.com/users/justinvelluppillai/gists{/gist_id}","starred_url":"https://api.github.com/users/justinvelluppillai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/justinvelluppillai/subscriptions","organizations_url":"https://api.github.com/users/justinvelluppillai/orgs","repos_url":"https://api.github.com/users/justinvelluppillai/repos","events_url":"https://api.github.com/users/justinvelluppillai/events{/privacy}","received_events_url":"https://api.github.com/users/justinvelluppillai/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2021-07-20T03:26:02Z","updated_at":"2021-07-21T09:00:03Z","closed_at":null,"author_association":"CONTRIBUTOR","active_lock_reason":null,"pull_request":{"url":"https://api.github.com/repos/matomo-org/matomo/pulls/17798","html_url":"https://github.com/matomo-org/matomo/pull/17798","diff_url":"https://github.com/matomo-org/matomo/pull/17798.diff","patch_url":"https://github.com/matomo-org/matomo/pull/17798.patch"},"body":"### Description:\r\n\r\nFixes #17773 by setting a Content-Security-Policy which is fairly loose at this stage.\r\n\r\n### Review\r\n\r\n* [ ] Functional review done\r\n* [ ] Potential edge cases thought about (behavior of the code with strange input, with strange internal state or possible interactions with other Matomo subsystems)\r\n* [ ] Usability review done (is anything maybe unclear or think about anything that would cause people to reach out to support)\r\n* [ ] Security review done [see checklist](https://developer.matomo.org/guides/security-in-piwik#checklist)\r\n* [ ] Code review done\r\n* [ ] Tests were added if useful/possible\r\n* [ ] Reviewed for breaking changes\r\n* [ ] Developer changelog updated if needed\r\n* [ ] Documentation added if needed\r\n* [ ] Existing documentation updated if needed\r\n","performed_via_github_app":null},"comment":{"url":"https://api.github.com/repos/matomo-org/matomo/issues/comments/884018722","html_url":"https://github.com/matomo-org/matomo/pull/17798#issuecomment-884018722","issue_url":"https://api.github.com/repos/matomo-org/matomo/issues/17798","id":884018722,"node_id":"IC_kwDOABefqs40sQ4i","user":{"login":"Findus23","id":6266037,"node_id":"MDQ6VXNlcjYyNjYwMzc=","avatar_url":"https://avatars.githubusercontent.com/u/6266037?v=4","gravatar_id":"","url":"https://api.github.com/users/Findus23","html_url":"https://github.com/Findus23","followers_url":"https://api.github.com/users/Findus23/followers","following_url":"https://api.github.com/users/Findus23/following{/other_user}","gists_url":"https://api.github.com/users/Findus23/gists{/gist_id}","starred_url":"https://api.github.com/users/Findus23/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Findus23/subscriptions","organizations_url":"https://api.github.com/users/Findus23/orgs","repos_url":"https://api.github.com/users/Findus23/repos","events_url":"https://api.github.com/users/Findus23/events{/privacy}","received_events_url":"https://api.github.com/users/Findus23/received_events","type":"User","site_admin":false},"created_at":"2021-07-21T09:00:03Z","updated_at":"2021-07-21T09:00:03Z","author_association":"MEMBER","body":"I think we should only be using `Content-Security-Policy-Report-Only` for at least one or two releases as I'm sure there are a lot of people with weird setups where the CSP will break in fascinating ways. \r\nI still think it is worth creating a secure one for Matomo as it will make Matomo a lot more secure, but also provide an option to fall back to Report-Only or disable it, for setups where using one is not possible.","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:03Z","org":{"id":698038,"login":"matomo-org","gravatar_id":"","url":"https://api.github.com/orgs/matomo-org","avatar_url":"https://avatars.githubusercontent.com/u/698038?"}} +{"id":"17246340086","type":"PullRequestReviewEvent","actor":{"id":13147834,"login":"YajB","display_login":"YajB","gravatar_id":"","url":"https://api.github.com/users/YajB","avatar_url":"https://avatars.githubusercontent.com/u/13147834?"},"repo":{"id":135286803,"name":"logzio/logz-docs","url":"https://api.github.com/repos/logzio/logz-docs"},"payload":{"action":"created","review":{"id":711422105,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDIyMTA1","user":{"login":"YajB","id":13147834,"node_id":"MDQ6VXNlcjEzMTQ3ODM0","avatar_url":"https://avatars.githubusercontent.com/u/13147834?v=4","gravatar_id":"","url":"https://api.github.com/users/YajB","html_url":"https://github.com/YajB","followers_url":"https://api.github.com/users/YajB/followers","following_url":"https://api.github.com/users/YajB/following{/other_user}","gists_url":"https://api.github.com/users/YajB/gists{/gist_id}","starred_url":"https://api.github.com/users/YajB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/YajB/subscriptions","organizations_url":"https://api.github.com/users/YajB/orgs","repos_url":"https://api.github.com/users/YajB/repos","events_url":"https://api.github.com/users/YajB/events{/privacy}","received_events_url":"https://api.github.com/users/YajB/received_events","type":"User","site_admin":false},"body":"Recommendations:\r\n- Where order matters, number the substeps in each procedure section for clearer user flow. \r\n- \"The following screen will appear:\" Change to present tense : \r\n\"The following screen appears\" or \r\n\"...is displayed\" \r\n or, \r\n> In the following screen: \r\n> 1.\r\n> 2.\r\n> 3.\r\n\r\n- In step 4 **Install the extension**, telling a user to \"wait until...\" is potentially anxiety-inducing and may raise the question of \"how long?\" - \r\n\r\nConsider: \r\n\"Select Install: You will be redirected to the **Installed extensions** page.\"\r\n\r\n- In step 5: \"...confirm that you authorize this extension.\" or \"...confirm the action.\" \r\n\r\n- In step 6: \"Upon authorizing the extension, you will be taken to the Logs Export page. Here you can see the events that are exported to Logz.io\". \r\n\r\nConsider \r\n\" On authorizing the extension, the Logs Export page opens, where you view (or see)....\"\r\n Or\r\n \"After you authorize the extension, you are directed to the **Logs Export** page...","commit_id":"1bf31508c3a89241a54730a0e8c72300f997dbef","submitted_at":"2021-07-21T09:00:03Z","state":"commented","html_url":"https://github.com/logzio/logz-docs/pull/1196#pullrequestreview-711422105","pull_request_url":"https://api.github.com/repos/logzio/logz-docs/pulls/1196","author_association":"COLLABORATOR","_links":{"html":{"href":"https://github.com/logzio/logz-docs/pull/1196#pullrequestreview-711422105"},"pull_request":{"href":"https://api.github.com/repos/logzio/logz-docs/pulls/1196"}}},"pull_request":{"url":"https://api.github.com/repos/logzio/logz-docs/pulls/1196","id":687364705,"node_id":"MDExOlB1bGxSZXF1ZXN0Njg3MzY0NzA1","html_url":"https://github.com/logzio/logz-docs/pull/1196","diff_url":"https://github.com/logzio/logz-docs/pull/1196.diff","patch_url":"https://github.com/logzio/logz-docs/pull/1196.patch","issue_url":"https://api.github.com/repos/logzio/logz-docs/issues/1196","number":1196,"state":"open","locked":false,"title":"DOC-58: Added auth0 integration","user":{"login":"nico-shishkin","id":86240618,"node_id":"MDQ6VXNlcjg2MjQwNjE4","avatar_url":"https://avatars.githubusercontent.com/u/86240618?v=4","gravatar_id":"","url":"https://api.github.com/users/nico-shishkin","html_url":"https://github.com/nico-shishkin","followers_url":"https://api.github.com/users/nico-shishkin/followers","following_url":"https://api.github.com/users/nico-shishkin/following{/other_user}","gists_url":"https://api.github.com/users/nico-shishkin/gists{/gist_id}","starred_url":"https://api.github.com/users/nico-shishkin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nico-shishkin/subscriptions","organizations_url":"https://api.github.com/users/nico-shishkin/orgs","repos_url":"https://api.github.com/users/nico-shishkin/repos","events_url":"https://api.github.com/users/nico-shishkin/events{/privacy}","received_events_url":"https://api.github.com/users/nico-shishkin/received_events","type":"User","site_admin":false},"body":"# What changed\r\n\r\nAdded:\r\n\r\nhttps://deploy-preview-1196--logz-docs.netlify.app/shipping/security-sources/auth0.html\r\n\r\nhttps://app.logz.io/#/dashboard/send-your-data/security-sources/auth0?deployPreviewNumber=1196\r\n\r\n----\r\n\r\n\r\n\r\n## Pages to review\r\n\r\n\r\n\r\n- LinkToPage1\r\n- LinkToPage2\r\n\r\n## Remaining work\r\n\r\n\r\n- [ ] Technical review\r\n- [ ] Copy Review\r\n- [ ] Redirects: \r\n\r\n## Post launch\r\n\r\nTo be completed by the docs team upon merge:\r\n\r\n- [ ] Teams to update with the new information:\r\n- [ ] Replace original content on the support portal with 'this page has been moved to ...' - paste the URL\r\n- [ ] Update these log shipping pages in the app: - paste the URL\r\n","created_at":"2021-07-11T16:50:55Z","updated_at":"2021-07-21T09:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c20141ea89bd9b58539cbc6c8feac28e7314e9eb","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/logzio/logz-docs/pulls/1196/commits","review_comments_url":"https://api.github.com/repos/logzio/logz-docs/pulls/1196/comments","review_comment_url":"https://api.github.com/repos/logzio/logz-docs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/logzio/logz-docs/issues/1196/comments","statuses_url":"https://api.github.com/repos/logzio/logz-docs/statuses/1bf31508c3a89241a54730a0e8c72300f997dbef","head":{"label":"logzio:auth0","ref":"auth0","sha":"1bf31508c3a89241a54730a0e8c72300f997dbef","user":{"login":"logzio","id":7902073,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc5MDIwNzM=","avatar_url":"https://avatars.githubusercontent.com/u/7902073?v=4","gravatar_id":"","url":"https://api.github.com/users/logzio","html_url":"https://github.com/logzio","followers_url":"https://api.github.com/users/logzio/followers","following_url":"https://api.github.com/users/logzio/following{/other_user}","gists_url":"https://api.github.com/users/logzio/gists{/gist_id}","starred_url":"https://api.github.com/users/logzio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/logzio/subscriptions","organizations_url":"https://api.github.com/users/logzio/orgs","repos_url":"https://api.github.com/users/logzio/repos","events_url":"https://api.github.com/users/logzio/events{/privacy}","received_events_url":"https://api.github.com/users/logzio/received_events","type":"Organization","site_admin":false},"repo":{"id":135286803,"node_id":"MDEwOlJlcG9zaXRvcnkxMzUyODY4MDM=","name":"logz-docs","full_name":"logzio/logz-docs","private":false,"owner":{"login":"logzio","id":7902073,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc5MDIwNzM=","avatar_url":"https://avatars.githubusercontent.com/u/7902073?v=4","gravatar_id":"","url":"https://api.github.com/users/logzio","html_url":"https://github.com/logzio","followers_url":"https://api.github.com/users/logzio/followers","following_url":"https://api.github.com/users/logzio/following{/other_user}","gists_url":"https://api.github.com/users/logzio/gists{/gist_id}","starred_url":"https://api.github.com/users/logzio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/logzio/subscriptions","organizations_url":"https://api.github.com/users/logzio/orgs","repos_url":"https://api.github.com/users/logzio/repos","events_url":"https://api.github.com/users/logzio/events{/privacy}","received_events_url":"https://api.github.com/users/logzio/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/logzio/logz-docs","description":"Logz.io user documentation","fork":false,"url":"https://api.github.com/repos/logzio/logz-docs","forks_url":"https://api.github.com/repos/logzio/logz-docs/forks","keys_url":"https://api.github.com/repos/logzio/logz-docs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/logzio/logz-docs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/logzio/logz-docs/teams","hooks_url":"https://api.github.com/repos/logzio/logz-docs/hooks","issue_events_url":"https://api.github.com/repos/logzio/logz-docs/issues/events{/number}","events_url":"https://api.github.com/repos/logzio/logz-docs/events","assignees_url":"https://api.github.com/repos/logzio/logz-docs/assignees{/user}","branches_url":"https://api.github.com/repos/logzio/logz-docs/branches{/branch}","tags_url":"https://api.github.com/repos/logzio/logz-docs/tags","blobs_url":"https://api.github.com/repos/logzio/logz-docs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/logzio/logz-docs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/logzio/logz-docs/git/refs{/sha}","trees_url":"https://api.github.com/repos/logzio/logz-docs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/logzio/logz-docs/statuses/{sha}","languages_url":"https://api.github.com/repos/logzio/logz-docs/languages","stargazers_url":"https://api.github.com/repos/logzio/logz-docs/stargazers","contributors_url":"https://api.github.com/repos/logzio/logz-docs/contributors","subscribers_url":"https://api.github.com/repos/logzio/logz-docs/subscribers","subscription_url":"https://api.github.com/repos/logzio/logz-docs/subscription","commits_url":"https://api.github.com/repos/logzio/logz-docs/commits{/sha}","git_commits_url":"https://api.github.com/repos/logzio/logz-docs/git/commits{/sha}","comments_url":"https://api.github.com/repos/logzio/logz-docs/comments{/number}","issue_comment_url":"https://api.github.com/repos/logzio/logz-docs/issues/comments{/number}","contents_url":"https://api.github.com/repos/logzio/logz-docs/contents/{+path}","compare_url":"https://api.github.com/repos/logzio/logz-docs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/logzio/logz-docs/merges","archive_url":"https://api.github.com/repos/logzio/logz-docs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/logzio/logz-docs/downloads","issues_url":"https://api.github.com/repos/logzio/logz-docs/issues{/number}","pulls_url":"https://api.github.com/repos/logzio/logz-docs/pulls{/number}","milestones_url":"https://api.github.com/repos/logzio/logz-docs/milestones{/number}","notifications_url":"https://api.github.com/repos/logzio/logz-docs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/logzio/logz-docs/labels{/name}","releases_url":"https://api.github.com/repos/logzio/logz-docs/releases{/id}","deployments_url":"https://api.github.com/repos/logzio/logz-docs/deployments","created_at":"2018-05-29T11:35:46Z","updated_at":"2021-07-20T15:00:29Z","pushed_at":"2021-07-21T08:39:42Z","git_url":"git://github.com/logzio/logz-docs.git","ssh_url":"git@github.com:logzio/logz-docs.git","clone_url":"https://github.com/logzio/logz-docs.git","svn_url":"https://github.com/logzio/logz-docs","homepage":"https://docs.logz.io","size":19711,"stargazers_count":11,"watchers_count":11,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":28,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":60,"license":null,"forks":28,"open_issues":60,"watchers":11,"default_branch":"master"}},"base":{"label":"logzio:master","ref":"master","sha":"41825251b962016e17ccfb4b44202ae99b4cd496","user":{"login":"logzio","id":7902073,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc5MDIwNzM=","avatar_url":"https://avatars.githubusercontent.com/u/7902073?v=4","gravatar_id":"","url":"https://api.github.com/users/logzio","html_url":"https://github.com/logzio","followers_url":"https://api.github.com/users/logzio/followers","following_url":"https://api.github.com/users/logzio/following{/other_user}","gists_url":"https://api.github.com/users/logzio/gists{/gist_id}","starred_url":"https://api.github.com/users/logzio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/logzio/subscriptions","organizations_url":"https://api.github.com/users/logzio/orgs","repos_url":"https://api.github.com/users/logzio/repos","events_url":"https://api.github.com/users/logzio/events{/privacy}","received_events_url":"https://api.github.com/users/logzio/received_events","type":"Organization","site_admin":false},"repo":{"id":135286803,"node_id":"MDEwOlJlcG9zaXRvcnkxMzUyODY4MDM=","name":"logz-docs","full_name":"logzio/logz-docs","private":false,"owner":{"login":"logzio","id":7902073,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc5MDIwNzM=","avatar_url":"https://avatars.githubusercontent.com/u/7902073?v=4","gravatar_id":"","url":"https://api.github.com/users/logzio","html_url":"https://github.com/logzio","followers_url":"https://api.github.com/users/logzio/followers","following_url":"https://api.github.com/users/logzio/following{/other_user}","gists_url":"https://api.github.com/users/logzio/gists{/gist_id}","starred_url":"https://api.github.com/users/logzio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/logzio/subscriptions","organizations_url":"https://api.github.com/users/logzio/orgs","repos_url":"https://api.github.com/users/logzio/repos","events_url":"https://api.github.com/users/logzio/events{/privacy}","received_events_url":"https://api.github.com/users/logzio/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/logzio/logz-docs","description":"Logz.io user documentation","fork":false,"url":"https://api.github.com/repos/logzio/logz-docs","forks_url":"https://api.github.com/repos/logzio/logz-docs/forks","keys_url":"https://api.github.com/repos/logzio/logz-docs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/logzio/logz-docs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/logzio/logz-docs/teams","hooks_url":"https://api.github.com/repos/logzio/logz-docs/hooks","issue_events_url":"https://api.github.com/repos/logzio/logz-docs/issues/events{/number}","events_url":"https://api.github.com/repos/logzio/logz-docs/events","assignees_url":"https://api.github.com/repos/logzio/logz-docs/assignees{/user}","branches_url":"https://api.github.com/repos/logzio/logz-docs/branches{/branch}","tags_url":"https://api.github.com/repos/logzio/logz-docs/tags","blobs_url":"https://api.github.com/repos/logzio/logz-docs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/logzio/logz-docs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/logzio/logz-docs/git/refs{/sha}","trees_url":"https://api.github.com/repos/logzio/logz-docs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/logzio/logz-docs/statuses/{sha}","languages_url":"https://api.github.com/repos/logzio/logz-docs/languages","stargazers_url":"https://api.github.com/repos/logzio/logz-docs/stargazers","contributors_url":"https://api.github.com/repos/logzio/logz-docs/contributors","subscribers_url":"https://api.github.com/repos/logzio/logz-docs/subscribers","subscription_url":"https://api.github.com/repos/logzio/logz-docs/subscription","commits_url":"https://api.github.com/repos/logzio/logz-docs/commits{/sha}","git_commits_url":"https://api.github.com/repos/logzio/logz-docs/git/commits{/sha}","comments_url":"https://api.github.com/repos/logzio/logz-docs/comments{/number}","issue_comment_url":"https://api.github.com/repos/logzio/logz-docs/issues/comments{/number}","contents_url":"https://api.github.com/repos/logzio/logz-docs/contents/{+path}","compare_url":"https://api.github.com/repos/logzio/logz-docs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/logzio/logz-docs/merges","archive_url":"https://api.github.com/repos/logzio/logz-docs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/logzio/logz-docs/downloads","issues_url":"https://api.github.com/repos/logzio/logz-docs/issues{/number}","pulls_url":"https://api.github.com/repos/logzio/logz-docs/pulls{/number}","milestones_url":"https://api.github.com/repos/logzio/logz-docs/milestones{/number}","notifications_url":"https://api.github.com/repos/logzio/logz-docs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/logzio/logz-docs/labels{/name}","releases_url":"https://api.github.com/repos/logzio/logz-docs/releases{/id}","deployments_url":"https://api.github.com/repos/logzio/logz-docs/deployments","created_at":"2018-05-29T11:35:46Z","updated_at":"2021-07-20T15:00:29Z","pushed_at":"2021-07-21T08:39:42Z","git_url":"git://github.com/logzio/logz-docs.git","ssh_url":"git@github.com:logzio/logz-docs.git","clone_url":"https://github.com/logzio/logz-docs.git","svn_url":"https://github.com/logzio/logz-docs","homepage":"https://docs.logz.io","size":19711,"stargazers_count":11,"watchers_count":11,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":28,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":60,"license":null,"forks":28,"open_issues":60,"watchers":11,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/logzio/logz-docs/pulls/1196"},"html":{"href":"https://github.com/logzio/logz-docs/pull/1196"},"issue":{"href":"https://api.github.com/repos/logzio/logz-docs/issues/1196"},"comments":{"href":"https://api.github.com/repos/logzio/logz-docs/issues/1196/comments"},"review_comments":{"href":"https://api.github.com/repos/logzio/logz-docs/pulls/1196/comments"},"review_comment":{"href":"https://api.github.com/repos/logzio/logz-docs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/logzio/logz-docs/pulls/1196/commits"},"statuses":{"href":"https://api.github.com/repos/logzio/logz-docs/statuses/1bf31508c3a89241a54730a0e8c72300f997dbef"}},"author_association":"COLLABORATOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:03Z","org":{"id":7902073,"login":"logzio","gravatar_id":"","url":"https://api.github.com/orgs/logzio","avatar_url":"https://avatars.githubusercontent.com/u/7902073?"}} +{"id":"17246340106","type":"PushEvent","actor":{"id":77620363,"login":"hunksammy","display_login":"hunksammy","gravatar_id":"","url":"https://api.github.com/users/hunksammy","avatar_url":"https://avatars.githubusercontent.com/u/77620363?"},"repo":{"id":330883873,"name":"hunksammy/2084377289testbysam","url":"https://api.github.com/repos/hunksammy/2084377289testbysam"},"payload":{"push_id":7562451419,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"23d2d09c5ffc922ead6565a16c9c198d65df4a8f","before":"32fa2f80be9f0a86462668142027a1addfecc313","commits":[{"sha":"23d2d09c5ffc922ead6565a16c9c198d65df4a8f","author":{"name":"hunksammy","email":"29ceb644d373db31fb44a8f97cba06d00e31a5e9@gmail.com"},"message":"t","distinct":true,"url":"https://api.github.com/repos/hunksammy/2084377289testbysam/commits/23d2d09c5ffc922ead6565a16c9c198d65df4a8f"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246339407","type":"ReleaseEvent","actor":{"id":79913779,"login":"conda-forge-curator[bot]","display_login":"conda-forge-curator","gravatar_id":"","url":"https://api.github.com/users/conda-forge-curator[bot]","avatar_url":"https://avatars.githubusercontent.com/u/79913779?"},"repo":{"id":288431736,"name":"conda-forge/releases","url":"https://api.github.com/repos/conda-forge/releases"},"payload":{"action":"published","release":{"url":"https://api.github.com/repos/conda-forge/releases/releases/46527560","assets_url":"https://api.github.com/repos/conda-forge/releases/releases/46527560/assets","upload_url":"https://uploads.github.com/repos/conda-forge/releases/releases/46527560/assets{?name,label}","html_url":"https://github.com/conda-forge/releases/releases/tag/win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2","id":46527560,"author":{"login":"conda-forge-curator[bot]","id":79913779,"node_id":"MDM6Qm90Nzk5MTM3Nzk=","avatar_url":"https://avatars.githubusercontent.com/in/102928?v=4","gravatar_id":"","url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D","html_url":"https://github.com/apps/conda-forge-curator","followers_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/followers","following_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/repos","events_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/conda-forge-curator%5Bbot%5D/received_events","type":"Bot","site_admin":false},"node_id":"MDc6UmVsZWFzZTQ2NTI3NTYw","tag_name":"win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2","target_commitish":"c3a740206b9712f93a2b4982821d8b86964e2c3d","name":"win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2","draft":false,"prerelease":false,"created_at":"2021-03-04T14:45:35Z","published_at":"2021-07-21T09:00:00Z","assets":[],"tarball_url":"https://api.github.com/repos/conda-forge/releases/tarball/win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2","zipball_url":"https://api.github.com/repos/conda-forge/releases/zipball/win-64/astroid-2.6.5-py37h03978a9_0.tar.bz2","body":"","short_description_html":null,"is_short_description_html_truncated":false}},"public":true,"created_at":"2021-07-21T09:00:00Z","org":{"id":11897326,"login":"conda-forge","gravatar_id":"","url":"https://api.github.com/orgs/conda-forge","avatar_url":"https://avatars.githubusercontent.com/u/11897326?"}} +{"id":"17246339934","type":"PushEvent","actor":{"id":158862,"login":"arlac77","display_login":"arlac77","gravatar_id":"","url":"https://api.github.com/users/arlac77","avatar_url":"https://avatars.githubusercontent.com/u/158862?"},"repo":{"id":253911783,"name":"arlac77/sync-test-repository","url":"https://api.github.com/repos/arlac77/sync-test-repository"},"payload":{"push_id":7562451323,"size":1,"distinct_size":1,"ref":"refs/heads/template-sync/template-node-app","head":"b81911746ccbc04009e4a59ba4db8271fc53be43","before":"e0f397bcc6b705ac3abe4d10e24dd31db2bdce40","commits":[{"sha":"b81911746ccbc04009e4a59ba4db8271fc53be43","author":{"name":"Markus Felten","email":"d7345cc33cdae6a44846871372d59b8178f00e4c@gmx.de"},"message":"fix(engines): add >=14.17.3 remove >=12.18.0 (engines.node)\nchore(scripts): add mocha tests/*-test.js && ava --timeout 2m && npm run test:ava (scripts.test)\nchore(scripts): add c8 -x 'tests/**/*' --temp-directory build/tmp ava --timeout 2m tests/*.mjs && c8 report -r lcov -o build/coverage --temp-directory build/tmp (scripts.cover)\nchore(scripts): add ava --timeout 2m tests/*.mjs (scripts.test:ava)\nchore(deps): add ^3.15.0 remove ^3.9.0 (devDependencies.ava)\nchore(deps): add ^7.7.3 remove ^7.2.0 (devDependencies.c8)\nchore(deps): add ^17.4.4 remove ^17.1.1 (devDependencies.semantic-release)","distinct":true,"url":"https://api.github.com/repos/arlac77/sync-test-repository/commits/b81911746ccbc04009e4a59ba4db8271fc53be43"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340117","type":"PushEvent","actor":{"id":1354510,"login":"leo91000","display_login":"leo91000","gravatar_id":"","url":"https://api.github.com/users/leo91000","avatar_url":"https://avatars.githubusercontent.com/u/1354510?"},"repo":{"id":372306529,"name":"leo91000/covid-japan-informations","url":"https://api.github.com/repos/leo91000/covid-japan-informations"},"payload":{"push_id":7562451408,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"fafa76838f2d7ae82d83edab2dcb622ac423f29f","before":"564c260362255b2d829a869ff254f6da4d1f4d53","commits":[{"sha":"fafa76838f2d7ae82d83edab2dcb622ac423f29f","author":{"name":"Léo Coletta","email":"c44abbda51c039fd3030f3ac7bea105b5e3f239d@gmail.com"},"message":"update 2021-07-21T09:00:02.379Z","distinct":true,"url":"https://api.github.com/repos/leo91000/covid-japan-informations/commits/fafa76838f2d7ae82d83edab2dcb622ac423f29f"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340120","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":248646333,"name":"miwebst/ssRunner","url":"https://api.github.com/repos/miwebst/ssRunner"},"payload":{"push_id":7562451423,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"51ae1529c6065198c56c3de231ce4a71fa3f00b3","before":"d107e584f70d3ec90a4105a6286c6ea1d7dee0d7","commits":[{"sha":"51ae1529c6065198c56c3de231ce4a71fa3f00b3","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 9:00:02 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunner/commits/51ae1529c6065198c56c3de231ce4a71fa3f00b3"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340124","type":"CreateEvent","actor":{"id":42283162,"login":"aws-aemilia","display_login":"aws-aemilia","gravatar_id":"","url":"https://api.github.com/users/aws-aemilia","avatar_url":"https://avatars.githubusercontent.com/u/42283162?"},"repo":{"id":388056929,"name":"aws-aemilia/react-app29188951774410475","url":"https://api.github.com/repos/aws-aemilia/react-app29188951774410475"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340130","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":249503742,"name":"miwebst/ssRunnerReact","url":"https://api.github.com/repos/miwebst/ssRunnerReact"},"payload":{"push_id":7562451420,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"9db28650d668aea1ba90f112a80147271ae16809","before":"6992b1362e5906c546e574623e2300432850132e","commits":[{"sha":"9db28650d668aea1ba90f112a80147271ae16809","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 9:00:02 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunnerReact/commits/9db28650d668aea1ba90f112a80147271ae16809"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340135","type":"PushEvent","actor":{"id":62687145,"login":"vpnsuperapp","display_login":"vpnsuperapp","gravatar_id":"","url":"https://api.github.com/users/vpnsuperapp","avatar_url":"https://avatars.githubusercontent.com/u/62687145?"},"repo":{"id":250208038,"name":"vpnsuperapp/fast","url":"https://api.github.com/repos/vpnsuperapp/fast"},"payload":{"push_id":7562451431,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"e243c4a619a3bed3abd880b444749b8157c11514","before":"9d8a22e0234d4a85de68a32e7861ffa610d57edd","commits":[{"sha":"e243c4a619a3bed3abd880b444749b8157c11514","author":{"name":"vpnsuperapp","email":"a10ef24ae1deeaf0f19ef9771332325c38d8dfe4@users.noreply.github.com"},"message":"thank you","distinct":true,"url":"https://api.github.com/repos/vpnsuperapp/fast/commits/e243c4a619a3bed3abd880b444749b8157c11514"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340138","type":"PushEvent","actor":{"id":65212910,"login":"Hall-1910","display_login":"Hall-1910","gravatar_id":"","url":"https://api.github.com/users/Hall-1910","avatar_url":"https://avatars.githubusercontent.com/u/65212910?"},"repo":{"id":222505696,"name":"brand22/d3","url":"https://api.github.com/repos/brand22/d3"},"payload":{"push_id":7562451422,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"c2861fa9d58d7c3a1b3f2e517a996465e51a0ed5","before":"af741ccd65855d20fc1dbb2960ff8fe16621f450","commits":[{"sha":"c2861fa9d58d7c3a1b3f2e517a996465e51a0ed5","author":{"name":"Hall-1910","email":"82b2a317210219cb74c49a50f60ce4b4ef49066b@users.noreply.github.com"},"message":"m","distinct":true,"url":"https://api.github.com/repos/brand22/d3/commits/c2861fa9d58d7c3a1b3f2e517a996465e51a0ed5"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340139","type":"PushEvent","actor":{"id":87330551,"login":"xz241","display_login":"xz241","gravatar_id":"","url":"https://api.github.com/users/xz241","avatar_url":"https://avatars.githubusercontent.com/u/87330551?"},"repo":{"id":385328687,"name":"xz241/wp-uploads","url":"https://api.github.com/repos/xz241/wp-uploads"},"payload":{"push_id":7562451433,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"adf7992fa66f825614e3b6e2ded102040f057211","before":"2a09914ed7c072ee60c61bdaf9e023625cef6b19","commits":[{"sha":"adf7992fa66f825614e3b6e2ded102040f057211","author":{"name":"xz241","email":"e17353502d0344fb9b4d10b6a471cc23c11850ec@users.noreply.github.com"},"message":"","distinct":true,"url":"https://api.github.com/repos/xz241/wp-uploads/commits/adf7992fa66f825614e3b6e2ded102040f057211"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340140","type":"PushEvent","actor":{"id":50640090,"login":"miwebst","display_login":"miwebst","gravatar_id":"","url":"https://api.github.com/users/miwebst","avatar_url":"https://avatars.githubusercontent.com/u/50640090?"},"repo":{"id":249783941,"name":"miwebst/ssRunnerAngular","url":"https://api.github.com/repos/miwebst/ssRunnerAngular"},"payload":{"push_id":7562451410,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"cbf21837e966822d4fcab8e6b4b81cb2501afb44","before":"4aa971e975e04eec2e69a6c90b23ca78f5371a40","commits":[{"sha":"cbf21837e966822d4fcab8e6b4b81cb2501afb44","author":{"name":"Static Sites Runner","email":"655ff4c91d8e57f5219dcc5151b7d910327c974a@microsoft.com"},"message":"Runner Commit: 7/21/2021 9:00:02 AM","distinct":true,"url":"https://api.github.com/repos/miwebst/ssRunnerAngular/commits/cbf21837e966822d4fcab8e6b4b81cb2501afb44"}]},"public":true,"created_at":"2021-07-21T09:00:03Z"} +{"id":"17246340144","type":"PullRequestEvent","actor":{"id":24419357,"login":"jan-dolejsi","display_login":"jan-dolejsi","gravatar_id":"","url":"https://api.github.com/users/jan-dolejsi","avatar_url":"https://avatars.githubusercontent.com/u/24419357?"},"repo":{"id":88296913,"name":"jan-dolejsi/vscode-pddl","url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl"},"payload":{"action":"closed","number":106,"pull_request":{"url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/106","id":694150505,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTUwNTA1","html_url":"https://github.com/jan-dolejsi/vscode-pddl/pull/106","diff_url":"https://github.com/jan-dolejsi/vscode-pddl/pull/106.diff","patch_url":"https://github.com/jan-dolejsi/vscode-pddl/pull/106.patch","issue_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/106","number":106,"state":"closed","locked":false,"title":"Bump lodash from 4.17.20 to 4.17.21","user":{"login":"dependabot[bot]","id":49699333,"node_id":"MDM6Qm90NDk2OTkzMzM=","avatar_url":"https://avatars.githubusercontent.com/in/29110?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot%5Bbot%5D","html_url":"https://github.com/apps/dependabot","followers_url":"https://api.github.com/users/dependabot%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.\n
    \nCommits\n
      \n
    • f299b52 Bump to v4.17.21
    • \n
    • c4847eb Improve performance of toNumber, trim and trimEnd on large input strings
    • \n
    • 3469357 Prevent command injection through _.template's variable option
    • \n
    • See full diff in compare view
    • \n
    \n
    \n
    \n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=lodash&package-manager=npm_and_yarn&previous-version=4.17.20&new-version=4.17.21)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language\n- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language\n- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language\n- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language\n\nYou can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/jan-dolejsi/vscode-pddl/network/alerts).\n\n
    ","created_at":"2021-07-21T08:12:19Z","updated_at":"2021-07-21T09:00:03Z","closed_at":"2021-07-21T09:00:03Z","merged_at":"2021-07-21T09:00:03Z","merge_commit_sha":"e2018a657671dae067d78233f08e31bd8c89453b","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":1732797251,"node_id":"MDU6TGFiZWwxNzMyNzk3MjUx","url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/labels/dependencies","name":"dependencies","color":"0366d6","default":false,"description":"Pull requests that update a dependency file"}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/106/commits","review_comments_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/106/comments","review_comment_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/106/comments","statuses_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/statuses/8693cf737e450b96e021362f1741030442deb19f","head":{"label":"jan-dolejsi:dependabot/npm_and_yarn/lodash-4.17.21","ref":"dependabot/npm_and_yarn/lodash-4.17.21","sha":"8693cf737e450b96e021362f1741030442deb19f","user":{"login":"jan-dolejsi","id":24419357,"node_id":"MDQ6VXNlcjI0NDE5MzU3","avatar_url":"https://avatars.githubusercontent.com/u/24419357?v=4","gravatar_id":"","url":"https://api.github.com/users/jan-dolejsi","html_url":"https://github.com/jan-dolejsi","followers_url":"https://api.github.com/users/jan-dolejsi/followers","following_url":"https://api.github.com/users/jan-dolejsi/following{/other_user}","gists_url":"https://api.github.com/users/jan-dolejsi/gists{/gist_id}","starred_url":"https://api.github.com/users/jan-dolejsi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jan-dolejsi/subscriptions","organizations_url":"https://api.github.com/users/jan-dolejsi/orgs","repos_url":"https://api.github.com/users/jan-dolejsi/repos","events_url":"https://api.github.com/users/jan-dolejsi/events{/privacy}","received_events_url":"https://api.github.com/users/jan-dolejsi/received_events","type":"User","site_admin":false},"repo":{"id":88296913,"node_id":"MDEwOlJlcG9zaXRvcnk4ODI5NjkxMw==","name":"vscode-pddl","full_name":"jan-dolejsi/vscode-pddl","private":false,"owner":{"login":"jan-dolejsi","id":24419357,"node_id":"MDQ6VXNlcjI0NDE5MzU3","avatar_url":"https://avatars.githubusercontent.com/u/24419357?v=4","gravatar_id":"","url":"https://api.github.com/users/jan-dolejsi","html_url":"https://github.com/jan-dolejsi","followers_url":"https://api.github.com/users/jan-dolejsi/followers","following_url":"https://api.github.com/users/jan-dolejsi/following{/other_user}","gists_url":"https://api.github.com/users/jan-dolejsi/gists{/gist_id}","starred_url":"https://api.github.com/users/jan-dolejsi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jan-dolejsi/subscriptions","organizations_url":"https://api.github.com/users/jan-dolejsi/orgs","repos_url":"https://api.github.com/users/jan-dolejsi/repos","events_url":"https://api.github.com/users/jan-dolejsi/events{/privacy}","received_events_url":"https://api.github.com/users/jan-dolejsi/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jan-dolejsi/vscode-pddl","description":"Planning Domain Description Language (PDDL) grammar, syntax highlighting, code snippets, parser and planner integration for Visual Studio Code.","fork":false,"url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl","forks_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/forks","keys_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/teams","hooks_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/hooks","issue_events_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/events{/number}","events_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/events","assignees_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/assignees{/user}","branches_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/branches{/branch}","tags_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/tags","blobs_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/refs{/sha}","trees_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/statuses/{sha}","languages_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/languages","stargazers_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/stargazers","contributors_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/contributors","subscribers_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/subscribers","subscription_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/subscription","commits_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/commits{/sha}","git_commits_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/commits{/sha}","comments_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/comments{/number}","issue_comment_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/comments{/number}","contents_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/contents/{+path}","compare_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/merges","archive_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/downloads","issues_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues{/number}","pulls_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls{/number}","milestones_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/milestones{/number}","notifications_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/labels{/name}","releases_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/releases{/id}","deployments_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/deployments","created_at":"2017-04-14T19:33:34Z","updated_at":"2021-07-21T08:09:36Z","pushed_at":"2021-07-21T09:00:03Z","git_url":"git://github.com/jan-dolejsi/vscode-pddl.git","ssh_url":"git@github.com:jan-dolejsi/vscode-pddl.git","clone_url":"https://github.com/jan-dolejsi/vscode-pddl.git","svn_url":"https://github.com/jan-dolejsi/vscode-pddl","homepage":"","size":4527,"stargazers_count":43,"watchers_count":43,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":33,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":13,"open_issues":33,"watchers":43,"default_branch":"master"}},"base":{"label":"jan-dolejsi:master","ref":"master","sha":"7f6854405011d8df417853832739e067266cdb00","user":{"login":"jan-dolejsi","id":24419357,"node_id":"MDQ6VXNlcjI0NDE5MzU3","avatar_url":"https://avatars.githubusercontent.com/u/24419357?v=4","gravatar_id":"","url":"https://api.github.com/users/jan-dolejsi","html_url":"https://github.com/jan-dolejsi","followers_url":"https://api.github.com/users/jan-dolejsi/followers","following_url":"https://api.github.com/users/jan-dolejsi/following{/other_user}","gists_url":"https://api.github.com/users/jan-dolejsi/gists{/gist_id}","starred_url":"https://api.github.com/users/jan-dolejsi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jan-dolejsi/subscriptions","organizations_url":"https://api.github.com/users/jan-dolejsi/orgs","repos_url":"https://api.github.com/users/jan-dolejsi/repos","events_url":"https://api.github.com/users/jan-dolejsi/events{/privacy}","received_events_url":"https://api.github.com/users/jan-dolejsi/received_events","type":"User","site_admin":false},"repo":{"id":88296913,"node_id":"MDEwOlJlcG9zaXRvcnk4ODI5NjkxMw==","name":"vscode-pddl","full_name":"jan-dolejsi/vscode-pddl","private":false,"owner":{"login":"jan-dolejsi","id":24419357,"node_id":"MDQ6VXNlcjI0NDE5MzU3","avatar_url":"https://avatars.githubusercontent.com/u/24419357?v=4","gravatar_id":"","url":"https://api.github.com/users/jan-dolejsi","html_url":"https://github.com/jan-dolejsi","followers_url":"https://api.github.com/users/jan-dolejsi/followers","following_url":"https://api.github.com/users/jan-dolejsi/following{/other_user}","gists_url":"https://api.github.com/users/jan-dolejsi/gists{/gist_id}","starred_url":"https://api.github.com/users/jan-dolejsi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jan-dolejsi/subscriptions","organizations_url":"https://api.github.com/users/jan-dolejsi/orgs","repos_url":"https://api.github.com/users/jan-dolejsi/repos","events_url":"https://api.github.com/users/jan-dolejsi/events{/privacy}","received_events_url":"https://api.github.com/users/jan-dolejsi/received_events","type":"User","site_admin":false},"html_url":"https://github.com/jan-dolejsi/vscode-pddl","description":"Planning Domain Description Language (PDDL) grammar, syntax highlighting, code snippets, parser and planner integration for Visual Studio Code.","fork":false,"url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl","forks_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/forks","keys_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/teams","hooks_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/hooks","issue_events_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/events{/number}","events_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/events","assignees_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/assignees{/user}","branches_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/branches{/branch}","tags_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/tags","blobs_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/refs{/sha}","trees_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/statuses/{sha}","languages_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/languages","stargazers_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/stargazers","contributors_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/contributors","subscribers_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/subscribers","subscription_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/subscription","commits_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/commits{/sha}","git_commits_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/git/commits{/sha}","comments_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/comments{/number}","issue_comment_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/comments{/number}","contents_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/contents/{+path}","compare_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/merges","archive_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/downloads","issues_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues{/number}","pulls_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls{/number}","milestones_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/milestones{/number}","notifications_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/labels{/name}","releases_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/releases{/id}","deployments_url":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/deployments","created_at":"2017-04-14T19:33:34Z","updated_at":"2021-07-21T08:09:36Z","pushed_at":"2021-07-21T09:00:03Z","git_url":"git://github.com/jan-dolejsi/vscode-pddl.git","ssh_url":"git@github.com:jan-dolejsi/vscode-pddl.git","clone_url":"https://github.com/jan-dolejsi/vscode-pddl.git","svn_url":"https://github.com/jan-dolejsi/vscode-pddl","homepage":"","size":4527,"stargazers_count":43,"watchers_count":43,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":33,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":13,"open_issues":33,"watchers":43,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/106"},"html":{"href":"https://github.com/jan-dolejsi/vscode-pddl/pull/106"},"issue":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/106"},"comments":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/issues/106/comments"},"review_comments":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/106/comments"},"review_comment":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/pulls/106/commits"},"statuses":{"href":"https://api.github.com/repos/jan-dolejsi/vscode-pddl/statuses/8693cf737e450b96e021362f1741030442deb19f"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"jan-dolejsi","id":24419357,"node_id":"MDQ6VXNlcjI0NDE5MzU3","avatar_url":"https://avatars.githubusercontent.com/u/24419357?v=4","gravatar_id":"","url":"https://api.github.com/users/jan-dolejsi","html_url":"https://github.com/jan-dolejsi","followers_url":"https://api.github.com/users/jan-dolejsi/followers","following_url":"https://api.github.com/users/jan-dolejsi/following{/other_user}","gists_url":"https://api.github.com/users/jan-dolejsi/gists{/gist_id}","starred_url":"https://api.github.com/users/jan-dolejsi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jan-dolejsi/subscriptions","organizations_url":"https://api.github.com/users/jan-dolejsi/orgs","repos_url":"https://api.github.com/users/jan-dolejsi/repos","events_url":"https://api.github.com/users/jan-dolejsi/events{/privacy}","received_events_url":"https://api.github.com/users/jan-dolejsi/received_events","type":"User","site_admin":false},"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":1,"additions":3,"deletions":9,"changed_files":1}},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340150","type":"PushEvent","actor":{"id":72054858,"login":"jafar-shodiq","display_login":"jafar-shodiq","gravatar_id":"","url":"https://api.github.com/users/jafar-shodiq","avatar_url":"https://avatars.githubusercontent.com/u/72054858?"},"repo":{"id":388005216,"name":"jafar-shodiq/complete-proj","url":"https://api.github.com/repos/jafar-shodiq/complete-proj"},"payload":{"push_id":7562451440,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"c146c9f13d4a948850019b5d82a4eb5f13b58aae","before":"01bf398a03377be22889442ff84f0fe600cfee99","commits":[{"sha":"c146c9f13d4a948850019b5d82a4eb5f13b58aae","author":{"name":"Jafar Shodiq","email":"df4effa2a7c0b5077d0f000b7640be0f6c43880b@gmail.com"},"message":"first commit","distinct":true,"url":"https://api.github.com/repos/jafar-shodiq/complete-proj/commits/c146c9f13d4a948850019b5d82a4eb5f13b58aae"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340153","type":"CreateEvent","actor":{"id":56758491,"login":"saiarchana463","display_login":"saiarchana463","gravatar_id":"","url":"https://api.github.com/users/saiarchana463","avatar_url":"https://avatars.githubusercontent.com/u/56758491?"},"repo":{"id":388056972,"name":"saiarchana463/prac","url":"https://api.github.com/repos/saiarchana463/prac"},"payload":{"ref":null,"ref_type":"repository","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340154","type":"IssuesEvent","actor":{"id":56415283,"login":"Space-Turtle0","display_login":"Space-Turtle0","gravatar_id":"","url":"https://api.github.com/users/Space-Turtle0","avatar_url":"https://avatars.githubusercontent.com/u/56415283?"},"repo":{"id":328220827,"name":"Space-Turtle0/PortalBOT-Hosting","url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting"},"payload":{"action":"opened","issue":{"url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting/issues/209","repository_url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting","labels_url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting/issues/209/labels{/name}","comments_url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting/issues/209/comments","events_url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting/issues/209/events","html_url":"https://github.com/Space-Turtle0/PortalBOT-Hosting/issues/209","id":949486869,"node_id":"MDU6SXNzdWU5NDk0ODY4Njk=","number":209,"title":"🛑 STABLE is down","user":{"login":"Space-Turtle0","id":56415283,"node_id":"MDQ6VXNlcjU2NDE1Mjgz","avatar_url":"https://avatars.githubusercontent.com/u/56415283?v=4","gravatar_id":"","url":"https://api.github.com/users/Space-Turtle0","html_url":"https://github.com/Space-Turtle0","followers_url":"https://api.github.com/users/Space-Turtle0/followers","following_url":"https://api.github.com/users/Space-Turtle0/following{/other_user}","gists_url":"https://api.github.com/users/Space-Turtle0/gists{/gist_id}","starred_url":"https://api.github.com/users/Space-Turtle0/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Space-Turtle0/subscriptions","organizations_url":"https://api.github.com/users/Space-Turtle0/orgs","repos_url":"https://api.github.com/users/Space-Turtle0/repos","events_url":"https://api.github.com/users/Space-Turtle0/events{/privacy}","received_events_url":"https://api.github.com/users/Space-Turtle0/received_events","type":"User","site_admin":false},"labels":[{"id":2677312959,"node_id":"MDU6TGFiZWwyNjc3MzEyOTU5","url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting/labels/stable","name":"stable","color":"ededed","default":false,"description":null},{"id":2645118087,"node_id":"MDU6TGFiZWwyNjQ1MTE4MDg3","url":"https://api.github.com/repos/Space-Turtle0/PortalBOT-Hosting/labels/status","name":"status","color":"ededed","default":false,"description":null}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2021-07-21T09:00:03Z","updated_at":"2021-07-21T09:00:03Z","closed_at":null,"author_association":"OWNER","active_lock_reason":null,"body":"In [`a248098`](https://github.com/Space-Turtle0/PortalBOT-Hosting/commit/a248098b77dc0c403b5843f0bca0c8d98e7afcc9\n), STABLE ($PORTALBOT_STABLE) was **down**:\n- HTTP code: 0\n- Response time: 0 ms\n","performed_via_github_app":null}},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340165","type":"PushEvent","actor":{"id":68417258,"login":"breakingheatmap","display_login":"breakingheatmap","gravatar_id":"","url":"https://api.github.com/users/breakingheatmap","avatar_url":"https://avatars.githubusercontent.com/u/68417258?"},"repo":{"id":280356388,"name":"breakingheatmap/breakingheatmap","url":"https://api.github.com/repos/breakingheatmap/breakingheatmap"},"payload":{"push_id":7562451371,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"505f71c07780dca3dd06b32d93ba0056409aabb8","before":"a2b346732919027ef2ee16484cca939cf09eeb46","commits":[{"sha":"505f71c07780dca3dd06b32d93ba0056409aabb8","author":{"name":"sudomaze","email":"6fbf11c1befd5a6b40696150a1a62f21b2fc7860@gmail.com"},"message":"1626857999","distinct":true,"url":"https://api.github.com/repos/breakingheatmap/breakingheatmap/commits/505f71c07780dca3dd06b32d93ba0056409aabb8"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340167","type":"PushEvent","actor":{"id":49872940,"login":"CC5447","display_login":"CC5447","gravatar_id":"","url":"https://api.github.com/users/CC5447","avatar_url":"https://avatars.githubusercontent.com/u/49872940?"},"repo":{"id":278609073,"name":"CC5447/Cat","url":"https://api.github.com/repos/CC5447/Cat"},"payload":{"push_id":7562451437,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"866dcdde151635a69a3e0f062ab8ad2ed4cce5b3","before":"6b27361afdb227f3d39c39d463971e7ab6d1d065","commits":[{"sha":"866dcdde151635a69a3e0f062ab8ad2ed4cce5b3","author":{"name":"CC5447","email":"766acee26f18aa9ff3d3fe7f3a9dccc94d2da63c@users.noreply.github.com"},"message":"Update Untitled Diagram.drawio","distinct":true,"url":"https://api.github.com/repos/CC5447/Cat/commits/866dcdde151635a69a3e0f062ab8ad2ed4cce5b3"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340173","type":"PushEvent","actor":{"id":62852156,"login":"DevJakey","display_login":"DevJakey","gravatar_id":"","url":"https://api.github.com/users/DevJakey","avatar_url":"https://avatars.githubusercontent.com/u/62852156?"},"repo":{"id":358965426,"name":"DevJakey/DevJakey","url":"https://api.github.com/repos/DevJakey/DevJakey"},"payload":{"push_id":7562451405,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"f7f59c47d9def471ec46c83000521e5d7c8d6216","before":"7942719dba04f97595846d6650764003e35bd98d","commits":[{"sha":"f7f59c47d9def471ec46c83000521e5d7c8d6216","author":{"name":"DevJakey","email":"c867a43406065281684282e5a2dee005bd8c655a@users.noreply.github.com"},"message":"Update README.md","distinct":true,"url":"https://api.github.com/repos/DevJakey/DevJakey/commits/f7f59c47d9def471ec46c83000521e5d7c8d6216"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340178","type":"PushEvent","actor":{"id":73460065,"login":"KINyonji","display_login":"KINyonji","gravatar_id":"","url":"https://api.github.com/users/KINyonji","avatar_url":"https://avatars.githubusercontent.com/u/73460065?"},"repo":{"id":382640846,"name":"KINyonji/Board_Spring","url":"https://api.github.com/repos/KINyonji/Board_Spring"},"payload":{"push_id":7562451450,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"8852ad8af5c4fda8d5bef03c399b7e9c039a6639","before":"cfd670632d41a2cca908cf8ab6545ecfda578bbc","commits":[{"sha":"8852ad8af5c4fda8d5bef03c399b7e9c039a6639","author":{"name":"KINyonji","email":"d74f643880c3d9027fd35030c0c33a34c31b3f97@users.noreply.github.com"},"message":".","distinct":true,"url":"https://api.github.com/repos/KINyonji/Board_Spring/commits/8852ad8af5c4fda8d5bef03c399b7e9c039a6639"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340182","type":"PushEvent","actor":{"id":68257043,"login":"haruyama8940","display_login":"haruyama8940","gravatar_id":"","url":"https://api.github.com/users/haruyama8940","avatar_url":"https://avatars.githubusercontent.com/u/68257043?"},"repo":{"id":378888019,"name":"haruyama8940/switching_by_etc","url":"https://api.github.com/repos/haruyama8940/switching_by_etc"},"payload":{"push_id":7562451428,"size":1,"distinct_size":1,"ref":"refs/heads/devel","head":"b255968c38517fa75893542ec7f2ac09d4402e69","before":"84108e18670f38dc4a17011cb13055c95b9a3624","commits":[{"sha":"b255968c38517fa75893542ec7f2ac09d4402e69","author":{"name":"haruyama8940","email":"8d383dd5dae3345c1a3439469328e9fe1994fc64@gmail.com"},"message":"add switching_by_alpha","distinct":true,"url":"https://api.github.com/repos/haruyama8940/switching_by_etc/commits/b255968c38517fa75893542ec7f2ac09d4402e69"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340184","type":"PullRequestReviewEvent","actor":{"id":52416718,"login":"fussel178","display_login":"fussel178","gravatar_id":"","url":"https://api.github.com/users/fussel178","avatar_url":"https://avatars.githubusercontent.com/u/52416718?"},"repo":{"id":377954977,"name":"fussel178/react-spring-rocket","url":"https://api.github.com/repos/fussel178/react-spring-rocket"},"payload":{"action":"created","review":{"id":711422114,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDIyMTE0","user":{"login":"fussel178","id":52416718,"node_id":"MDQ6VXNlcjUyNDE2NzE4","avatar_url":"https://avatars.githubusercontent.com/u/52416718?v=4","gravatar_id":"","url":"https://api.github.com/users/fussel178","html_url":"https://github.com/fussel178","followers_url":"https://api.github.com/users/fussel178/followers","following_url":"https://api.github.com/users/fussel178/following{/other_user}","gists_url":"https://api.github.com/users/fussel178/gists{/gist_id}","starred_url":"https://api.github.com/users/fussel178/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fussel178/subscriptions","organizations_url":"https://api.github.com/users/fussel178/orgs","repos_url":"https://api.github.com/users/fussel178/repos","events_url":"https://api.github.com/users/fussel178/events{/privacy}","received_events_url":"https://api.github.com/users/fussel178/received_events","type":"User","site_admin":false},"body":"","commit_id":"4e26bdd37f51890ca2493293ce40e6f1b83fd0ae","submitted_at":"2021-07-21T09:00:03Z","state":"approved","html_url":"https://github.com/fussel178/react-spring-rocket/pull/31#pullrequestreview-711422114","pull_request_url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31","author_association":"OWNER","_links":{"html":{"href":"https://github.com/fussel178/react-spring-rocket/pull/31#pullrequestreview-711422114"},"pull_request":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31"}}},"pull_request":{"url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31","id":694112951,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTEyOTUx","html_url":"https://github.com/fussel178/react-spring-rocket/pull/31","diff_url":"https://github.com/fussel178/react-spring-rocket/pull/31.diff","patch_url":"https://github.com/fussel178/react-spring-rocket/pull/31.patch","issue_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/31","number":31,"state":"open","locked":false,"title":"chore(deps): bump actions/setup-node from 2.2.0 to 2.3.0","user":{"login":"dependabot[bot]","id":49699333,"node_id":"MDM6Qm90NDk2OTkzMzM=","avatar_url":"https://avatars.githubusercontent.com/in/29110?v=4","gravatar_id":"","url":"https://api.github.com/users/dependabot%5Bbot%5D","html_url":"https://github.com/apps/dependabot","followers_url":"https://api.github.com/users/dependabot%5Bbot%5D/followers","following_url":"https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dependabot%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/dependabot%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/dependabot%5Bbot%5D/repos","events_url":"https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/dependabot%5Bbot%5D/received_events","type":"Bot","site_admin":false},"body":"Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.2.0 to 2.3.0.\n
    \nRelease notes\n

    Sourced from actions/setup-node's releases.

    \n
    \n

    Support caching pnpm dependencies

    \n

    This release introduces dependency caching support for the pnpm package manager (#278).

    \n

    Caching pnpm dependencies:

    \n
    # This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n

    steps:

    \n
      \n
    • uses: actions/checkout@v2
    • \n
    • uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2\nwith:\nversion: 6.10.0
    • \n
    • uses: actions/setup-node@v2\nwith:\nnode-version: '14'\ncache: 'pnpm'
    • \n
    • run: pnpm install
    • \n
    • run: pnpm test\n
    \n\n

    NOTE: pnpm caching support requires pnpm version >= 6.10.0

    \n
    \n
    \n
    \nCommits\n
      \n
    • aa759c6 Merge pull request #278 from jacobwgillespie/cache-pnpm
    • \n
    • 0ae03de Reorder to npm, yarn, pnpm
    • \n
    • 4bc87b8 Bump e2e tests to 6.10.0
    • \n
    • b96348a Remove unused imports
    • \n
    • 3af302a Switch to pnpm store path command
    • \n
    • f452812 Unmock fs.existsSync after tests
    • \n
    • e93556c Mock fs.existsSync in tests
    • \n
    • 0453e51 Regenerate compiled files
    • \n
    • 399982b Move existence check to cache-save
    • \n
    • d278e78 Add logic to check that cache folder exists
    • \n
    • Additional commits viewable in compare view
    • \n
    \n
    \n
    \n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=2.2.0&new-version=2.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
    \nDependabot commands and options\n
    \n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
    ","created_at":"2021-07-21T07:12:46Z","updated_at":"2021-07-21T09:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e82e44c45721e2f694174b8c9b701412888fc35b","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":3095788298,"node_id":"MDU6TGFiZWwzMDk1Nzg4Mjk4","url":"https://api.github.com/repos/fussel178/react-spring-rocket/labels/dependencies","name":"dependencies","color":"0366d6","default":false,"description":"Pull requests that update a dependency file"},{"id":3095788302,"node_id":"MDU6TGFiZWwzMDk1Nzg4MzAy","url":"https://api.github.com/repos/fussel178/react-spring-rocket/labels/github_actions","name":"github_actions","color":"000000","default":false,"description":"Pull requests that update Github_actions code"}],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31/commits","review_comments_url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31/comments","review_comment_url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/comments{/number}","comments_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/31/comments","statuses_url":"https://api.github.com/repos/fussel178/react-spring-rocket/statuses/4e26bdd37f51890ca2493293ce40e6f1b83fd0ae","head":{"label":"fussel178:dependabot/github_actions/actions/setup-node-2.3.0","ref":"dependabot/github_actions/actions/setup-node-2.3.0","sha":"4e26bdd37f51890ca2493293ce40e6f1b83fd0ae","user":{"login":"fussel178","id":52416718,"node_id":"MDQ6VXNlcjUyNDE2NzE4","avatar_url":"https://avatars.githubusercontent.com/u/52416718?v=4","gravatar_id":"","url":"https://api.github.com/users/fussel178","html_url":"https://github.com/fussel178","followers_url":"https://api.github.com/users/fussel178/followers","following_url":"https://api.github.com/users/fussel178/following{/other_user}","gists_url":"https://api.github.com/users/fussel178/gists{/gist_id}","starred_url":"https://api.github.com/users/fussel178/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fussel178/subscriptions","organizations_url":"https://api.github.com/users/fussel178/orgs","repos_url":"https://api.github.com/users/fussel178/repos","events_url":"https://api.github.com/users/fussel178/events{/privacy}","received_events_url":"https://api.github.com/users/fussel178/received_events","type":"User","site_admin":false},"repo":{"id":377954977,"node_id":"MDEwOlJlcG9zaXRvcnkzNzc5NTQ5Nzc=","name":"react-spring-rocket","full_name":"fussel178/react-spring-rocket","private":false,"owner":{"login":"fussel178","id":52416718,"node_id":"MDQ6VXNlcjUyNDE2NzE4","avatar_url":"https://avatars.githubusercontent.com/u/52416718?v=4","gravatar_id":"","url":"https://api.github.com/users/fussel178","html_url":"https://github.com/fussel178","followers_url":"https://api.github.com/users/fussel178/followers","following_url":"https://api.github.com/users/fussel178/following{/other_user}","gists_url":"https://api.github.com/users/fussel178/gists{/gist_id}","starred_url":"https://api.github.com/users/fussel178/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fussel178/subscriptions","organizations_url":"https://api.github.com/users/fussel178/orgs","repos_url":"https://api.github.com/users/fussel178/repos","events_url":"https://api.github.com/users/fussel178/events{/privacy}","received_events_url":"https://api.github.com/users/fussel178/received_events","type":"User","site_admin":false},"html_url":"https://github.com/fussel178/react-spring-rocket","description":"A simple rocket. Made with React Spring.","fork":false,"url":"https://api.github.com/repos/fussel178/react-spring-rocket","forks_url":"https://api.github.com/repos/fussel178/react-spring-rocket/forks","keys_url":"https://api.github.com/repos/fussel178/react-spring-rocket/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fussel178/react-spring-rocket/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fussel178/react-spring-rocket/teams","hooks_url":"https://api.github.com/repos/fussel178/react-spring-rocket/hooks","issue_events_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/events{/number}","events_url":"https://api.github.com/repos/fussel178/react-spring-rocket/events","assignees_url":"https://api.github.com/repos/fussel178/react-spring-rocket/assignees{/user}","branches_url":"https://api.github.com/repos/fussel178/react-spring-rocket/branches{/branch}","tags_url":"https://api.github.com/repos/fussel178/react-spring-rocket/tags","blobs_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/refs{/sha}","trees_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fussel178/react-spring-rocket/statuses/{sha}","languages_url":"https://api.github.com/repos/fussel178/react-spring-rocket/languages","stargazers_url":"https://api.github.com/repos/fussel178/react-spring-rocket/stargazers","contributors_url":"https://api.github.com/repos/fussel178/react-spring-rocket/contributors","subscribers_url":"https://api.github.com/repos/fussel178/react-spring-rocket/subscribers","subscription_url":"https://api.github.com/repos/fussel178/react-spring-rocket/subscription","commits_url":"https://api.github.com/repos/fussel178/react-spring-rocket/commits{/sha}","git_commits_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/commits{/sha}","comments_url":"https://api.github.com/repos/fussel178/react-spring-rocket/comments{/number}","issue_comment_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/comments{/number}","contents_url":"https://api.github.com/repos/fussel178/react-spring-rocket/contents/{+path}","compare_url":"https://api.github.com/repos/fussel178/react-spring-rocket/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fussel178/react-spring-rocket/merges","archive_url":"https://api.github.com/repos/fussel178/react-spring-rocket/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fussel178/react-spring-rocket/downloads","issues_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues{/number}","pulls_url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls{/number}","milestones_url":"https://api.github.com/repos/fussel178/react-spring-rocket/milestones{/number}","notifications_url":"https://api.github.com/repos/fussel178/react-spring-rocket/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fussel178/react-spring-rocket/labels{/name}","releases_url":"https://api.github.com/repos/fussel178/react-spring-rocket/releases{/id}","deployments_url":"https://api.github.com/repos/fussel178/react-spring-rocket/deployments","created_at":"2021-06-17T20:36:51Z","updated_at":"2021-07-20T21:54:20Z","pushed_at":"2021-07-21T07:13:21Z","git_url":"git://github.com/fussel178/react-spring-rocket.git","ssh_url":"git@github.com:fussel178/react-spring-rocket.git","clone_url":"https://github.com/fussel178/react-spring-rocket.git","svn_url":"https://github.com/fussel178/react-spring-rocket","homepage":"https://fussel178.github.io/react-spring-rocket/","size":9606,"stargazers_count":2,"watchers_count":2,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":5,"watchers":2,"default_branch":"main"}},"base":{"label":"fussel178:main","ref":"main","sha":"64937a602194c3ab5e4e2394e2a869d32e6fcc0b","user":{"login":"fussel178","id":52416718,"node_id":"MDQ6VXNlcjUyNDE2NzE4","avatar_url":"https://avatars.githubusercontent.com/u/52416718?v=4","gravatar_id":"","url":"https://api.github.com/users/fussel178","html_url":"https://github.com/fussel178","followers_url":"https://api.github.com/users/fussel178/followers","following_url":"https://api.github.com/users/fussel178/following{/other_user}","gists_url":"https://api.github.com/users/fussel178/gists{/gist_id}","starred_url":"https://api.github.com/users/fussel178/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fussel178/subscriptions","organizations_url":"https://api.github.com/users/fussel178/orgs","repos_url":"https://api.github.com/users/fussel178/repos","events_url":"https://api.github.com/users/fussel178/events{/privacy}","received_events_url":"https://api.github.com/users/fussel178/received_events","type":"User","site_admin":false},"repo":{"id":377954977,"node_id":"MDEwOlJlcG9zaXRvcnkzNzc5NTQ5Nzc=","name":"react-spring-rocket","full_name":"fussel178/react-spring-rocket","private":false,"owner":{"login":"fussel178","id":52416718,"node_id":"MDQ6VXNlcjUyNDE2NzE4","avatar_url":"https://avatars.githubusercontent.com/u/52416718?v=4","gravatar_id":"","url":"https://api.github.com/users/fussel178","html_url":"https://github.com/fussel178","followers_url":"https://api.github.com/users/fussel178/followers","following_url":"https://api.github.com/users/fussel178/following{/other_user}","gists_url":"https://api.github.com/users/fussel178/gists{/gist_id}","starred_url":"https://api.github.com/users/fussel178/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fussel178/subscriptions","organizations_url":"https://api.github.com/users/fussel178/orgs","repos_url":"https://api.github.com/users/fussel178/repos","events_url":"https://api.github.com/users/fussel178/events{/privacy}","received_events_url":"https://api.github.com/users/fussel178/received_events","type":"User","site_admin":false},"html_url":"https://github.com/fussel178/react-spring-rocket","description":"A simple rocket. Made with React Spring.","fork":false,"url":"https://api.github.com/repos/fussel178/react-spring-rocket","forks_url":"https://api.github.com/repos/fussel178/react-spring-rocket/forks","keys_url":"https://api.github.com/repos/fussel178/react-spring-rocket/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fussel178/react-spring-rocket/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fussel178/react-spring-rocket/teams","hooks_url":"https://api.github.com/repos/fussel178/react-spring-rocket/hooks","issue_events_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/events{/number}","events_url":"https://api.github.com/repos/fussel178/react-spring-rocket/events","assignees_url":"https://api.github.com/repos/fussel178/react-spring-rocket/assignees{/user}","branches_url":"https://api.github.com/repos/fussel178/react-spring-rocket/branches{/branch}","tags_url":"https://api.github.com/repos/fussel178/react-spring-rocket/tags","blobs_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/refs{/sha}","trees_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fussel178/react-spring-rocket/statuses/{sha}","languages_url":"https://api.github.com/repos/fussel178/react-spring-rocket/languages","stargazers_url":"https://api.github.com/repos/fussel178/react-spring-rocket/stargazers","contributors_url":"https://api.github.com/repos/fussel178/react-spring-rocket/contributors","subscribers_url":"https://api.github.com/repos/fussel178/react-spring-rocket/subscribers","subscription_url":"https://api.github.com/repos/fussel178/react-spring-rocket/subscription","commits_url":"https://api.github.com/repos/fussel178/react-spring-rocket/commits{/sha}","git_commits_url":"https://api.github.com/repos/fussel178/react-spring-rocket/git/commits{/sha}","comments_url":"https://api.github.com/repos/fussel178/react-spring-rocket/comments{/number}","issue_comment_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/comments{/number}","contents_url":"https://api.github.com/repos/fussel178/react-spring-rocket/contents/{+path}","compare_url":"https://api.github.com/repos/fussel178/react-spring-rocket/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fussel178/react-spring-rocket/merges","archive_url":"https://api.github.com/repos/fussel178/react-spring-rocket/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fussel178/react-spring-rocket/downloads","issues_url":"https://api.github.com/repos/fussel178/react-spring-rocket/issues{/number}","pulls_url":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls{/number}","milestones_url":"https://api.github.com/repos/fussel178/react-spring-rocket/milestones{/number}","notifications_url":"https://api.github.com/repos/fussel178/react-spring-rocket/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fussel178/react-spring-rocket/labels{/name}","releases_url":"https://api.github.com/repos/fussel178/react-spring-rocket/releases{/id}","deployments_url":"https://api.github.com/repos/fussel178/react-spring-rocket/deployments","created_at":"2021-06-17T20:36:51Z","updated_at":"2021-07-20T21:54:20Z","pushed_at":"2021-07-21T07:13:21Z","git_url":"git://github.com/fussel178/react-spring-rocket.git","ssh_url":"git@github.com:fussel178/react-spring-rocket.git","clone_url":"https://github.com/fussel178/react-spring-rocket.git","svn_url":"https://github.com/fussel178/react-spring-rocket","homepage":"https://fussel178.github.io/react-spring-rocket/","size":9606,"stargazers_count":2,"watchers_count":2,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":5,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":5,"watchers":2,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31"},"html":{"href":"https://github.com/fussel178/react-spring-rocket/pull/31"},"issue":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/31"},"comments":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/issues/31/comments"},"review_comments":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31/comments"},"review_comment":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/pulls/31/commits"},"statuses":{"href":"https://api.github.com/repos/fussel178/react-spring-rocket/statuses/4e26bdd37f51890ca2493293ce40e6f1b83fd0ae"}},"author_association":"CONTRIBUTOR","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340190","type":"PullRequestReviewEvent","actor":{"id":87754812,"login":"abhinavg2787","display_login":"abhinavg2787","gravatar_id":"","url":"https://api.github.com/users/abhinavg2787","avatar_url":"https://avatars.githubusercontent.com/u/87754812?"},"repo":{"id":387483056,"name":"abhinavg27/sysfoo","url":"https://api.github.com/repos/abhinavg27/sysfoo"},"payload":{"action":"created","review":{"id":711422116,"node_id":"MDE3OlB1bGxSZXF1ZXN0UmV2aWV3NzExNDIyMTE2","user":{"login":"abhinavg2787","id":87754812,"node_id":"MDQ6VXNlcjg3NzU0ODEy","avatar_url":"https://avatars.githubusercontent.com/u/87754812?v=4","gravatar_id":"","url":"https://api.github.com/users/abhinavg2787","html_url":"https://github.com/abhinavg2787","followers_url":"https://api.github.com/users/abhinavg2787/followers","following_url":"https://api.github.com/users/abhinavg2787/following{/other_user}","gists_url":"https://api.github.com/users/abhinavg2787/gists{/gist_id}","starred_url":"https://api.github.com/users/abhinavg2787/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abhinavg2787/subscriptions","organizations_url":"https://api.github.com/users/abhinavg2787/orgs","repos_url":"https://api.github.com/users/abhinavg2787/repos","events_url":"https://api.github.com/users/abhinavg2787/events{/privacy}","received_events_url":"https://api.github.com/users/abhinavg2787/received_events","type":"User","site_admin":false},"body":"LGTM.","commit_id":"40c2b9023159a7b4629e82e98f54df872d8825e1","submitted_at":"2021-07-21T09:00:03Z","state":"approved","html_url":"https://github.com/abhinavg27/sysfoo/pull/1#pullrequestreview-711422116","pull_request_url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1","author_association":"COLLABORATOR","_links":{"html":{"href":"https://github.com/abhinavg27/sysfoo/pull/1#pullrequestreview-711422116"},"pull_request":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1","id":693431224,"node_id":"MDExOlB1bGxSZXF1ZXN0NjkzNDMxMjI0","html_url":"https://github.com/abhinavg27/sysfoo/pull/1","diff_url":"https://github.com/abhinavg27/sysfoo/pull/1.diff","patch_url":"https://github.com/abhinavg27/sysfoo/pull/1.patch","issue_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues/1","number":1,"state":"open","locked":false,"title":"Test policy","user":{"login":"abhinavg27","id":38690244,"node_id":"MDQ6VXNlcjM4NjkwMjQ0","avatar_url":"https://avatars.githubusercontent.com/u/38690244?v=4","gravatar_id":"","url":"https://api.github.com/users/abhinavg27","html_url":"https://github.com/abhinavg27","followers_url":"https://api.github.com/users/abhinavg27/followers","following_url":"https://api.github.com/users/abhinavg27/following{/other_user}","gists_url":"https://api.github.com/users/abhinavg27/gists{/gist_id}","starred_url":"https://api.github.com/users/abhinavg27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abhinavg27/subscriptions","organizations_url":"https://api.github.com/users/abhinavg27/orgs","repos_url":"https://api.github.com/users/abhinavg27/repos","events_url":"https://api.github.com/users/abhinavg27/events{/privacy}","received_events_url":"https://api.github.com/users/abhinavg27/received_events","type":"User","site_admin":false},"body":"","created_at":"2021-07-20T12:38:42Z","updated_at":"2021-07-21T09:00:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"37b260d266651822455fc629af5b38a497896ec6","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1/commits","review_comments_url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1/comments","review_comment_url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/comments{/number}","comments_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues/1/comments","statuses_url":"https://api.github.com/repos/abhinavg27/sysfoo/statuses/40c2b9023159a7b4629e82e98f54df872d8825e1","head":{"label":"abhinavg27:test_policy","ref":"test_policy","sha":"40c2b9023159a7b4629e82e98f54df872d8825e1","user":{"login":"abhinavg27","id":38690244,"node_id":"MDQ6VXNlcjM4NjkwMjQ0","avatar_url":"https://avatars.githubusercontent.com/u/38690244?v=4","gravatar_id":"","url":"https://api.github.com/users/abhinavg27","html_url":"https://github.com/abhinavg27","followers_url":"https://api.github.com/users/abhinavg27/followers","following_url":"https://api.github.com/users/abhinavg27/following{/other_user}","gists_url":"https://api.github.com/users/abhinavg27/gists{/gist_id}","starred_url":"https://api.github.com/users/abhinavg27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abhinavg27/subscriptions","organizations_url":"https://api.github.com/users/abhinavg27/orgs","repos_url":"https://api.github.com/users/abhinavg27/repos","events_url":"https://api.github.com/users/abhinavg27/events{/privacy}","received_events_url":"https://api.github.com/users/abhinavg27/received_events","type":"User","site_admin":false},"repo":{"id":387483056,"node_id":"MDEwOlJlcG9zaXRvcnkzODc0ODMwNTY=","name":"sysfoo","full_name":"abhinavg27/sysfoo","private":false,"owner":{"login":"abhinavg27","id":38690244,"node_id":"MDQ6VXNlcjM4NjkwMjQ0","avatar_url":"https://avatars.githubusercontent.com/u/38690244?v=4","gravatar_id":"","url":"https://api.github.com/users/abhinavg27","html_url":"https://github.com/abhinavg27","followers_url":"https://api.github.com/users/abhinavg27/followers","following_url":"https://api.github.com/users/abhinavg27/following{/other_user}","gists_url":"https://api.github.com/users/abhinavg27/gists{/gist_id}","starred_url":"https://api.github.com/users/abhinavg27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abhinavg27/subscriptions","organizations_url":"https://api.github.com/users/abhinavg27/orgs","repos_url":"https://api.github.com/users/abhinavg27/repos","events_url":"https://api.github.com/users/abhinavg27/events{/privacy}","received_events_url":"https://api.github.com/users/abhinavg27/received_events","type":"User","site_admin":false},"html_url":"https://github.com/abhinavg27/sysfoo","description":"Sample java webapp with maven which prints system info","fork":true,"url":"https://api.github.com/repos/abhinavg27/sysfoo","forks_url":"https://api.github.com/repos/abhinavg27/sysfoo/forks","keys_url":"https://api.github.com/repos/abhinavg27/sysfoo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/abhinavg27/sysfoo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/abhinavg27/sysfoo/teams","hooks_url":"https://api.github.com/repos/abhinavg27/sysfoo/hooks","issue_events_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues/events{/number}","events_url":"https://api.github.com/repos/abhinavg27/sysfoo/events","assignees_url":"https://api.github.com/repos/abhinavg27/sysfoo/assignees{/user}","branches_url":"https://api.github.com/repos/abhinavg27/sysfoo/branches{/branch}","tags_url":"https://api.github.com/repos/abhinavg27/sysfoo/tags","blobs_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/refs{/sha}","trees_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/abhinavg27/sysfoo/statuses/{sha}","languages_url":"https://api.github.com/repos/abhinavg27/sysfoo/languages","stargazers_url":"https://api.github.com/repos/abhinavg27/sysfoo/stargazers","contributors_url":"https://api.github.com/repos/abhinavg27/sysfoo/contributors","subscribers_url":"https://api.github.com/repos/abhinavg27/sysfoo/subscribers","subscription_url":"https://api.github.com/repos/abhinavg27/sysfoo/subscription","commits_url":"https://api.github.com/repos/abhinavg27/sysfoo/commits{/sha}","git_commits_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/commits{/sha}","comments_url":"https://api.github.com/repos/abhinavg27/sysfoo/comments{/number}","issue_comment_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues/comments{/number}","contents_url":"https://api.github.com/repos/abhinavg27/sysfoo/contents/{+path}","compare_url":"https://api.github.com/repos/abhinavg27/sysfoo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/abhinavg27/sysfoo/merges","archive_url":"https://api.github.com/repos/abhinavg27/sysfoo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/abhinavg27/sysfoo/downloads","issues_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues{/number}","pulls_url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls{/number}","milestones_url":"https://api.github.com/repos/abhinavg27/sysfoo/milestones{/number}","notifications_url":"https://api.github.com/repos/abhinavg27/sysfoo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/abhinavg27/sysfoo/labels{/name}","releases_url":"https://api.github.com/repos/abhinavg27/sysfoo/releases{/id}","deployments_url":"https://api.github.com/repos/abhinavg27/sysfoo/deployments","created_at":"2021-07-19T13:58:05Z","updated_at":"2021-07-20T11:29:25Z","pushed_at":"2021-07-20T12:38:42Z","git_url":"git://github.com/abhinavg27/sysfoo.git","ssh_url":"git@github.com:abhinavg27/sysfoo.git","clone_url":"https://github.com/abhinavg27/sysfoo.git","svn_url":"https://github.com/abhinavg27/sysfoo","homepage":null,"size":1656,"stargazers_count":0,"watchers_count":0,"language":"SCSS","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"abhinavg27:master","ref":"master","sha":"8fa236dee8a9f54851441973d7ef502b66a2d777","user":{"login":"abhinavg27","id":38690244,"node_id":"MDQ6VXNlcjM4NjkwMjQ0","avatar_url":"https://avatars.githubusercontent.com/u/38690244?v=4","gravatar_id":"","url":"https://api.github.com/users/abhinavg27","html_url":"https://github.com/abhinavg27","followers_url":"https://api.github.com/users/abhinavg27/followers","following_url":"https://api.github.com/users/abhinavg27/following{/other_user}","gists_url":"https://api.github.com/users/abhinavg27/gists{/gist_id}","starred_url":"https://api.github.com/users/abhinavg27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abhinavg27/subscriptions","organizations_url":"https://api.github.com/users/abhinavg27/orgs","repos_url":"https://api.github.com/users/abhinavg27/repos","events_url":"https://api.github.com/users/abhinavg27/events{/privacy}","received_events_url":"https://api.github.com/users/abhinavg27/received_events","type":"User","site_admin":false},"repo":{"id":387483056,"node_id":"MDEwOlJlcG9zaXRvcnkzODc0ODMwNTY=","name":"sysfoo","full_name":"abhinavg27/sysfoo","private":false,"owner":{"login":"abhinavg27","id":38690244,"node_id":"MDQ6VXNlcjM4NjkwMjQ0","avatar_url":"https://avatars.githubusercontent.com/u/38690244?v=4","gravatar_id":"","url":"https://api.github.com/users/abhinavg27","html_url":"https://github.com/abhinavg27","followers_url":"https://api.github.com/users/abhinavg27/followers","following_url":"https://api.github.com/users/abhinavg27/following{/other_user}","gists_url":"https://api.github.com/users/abhinavg27/gists{/gist_id}","starred_url":"https://api.github.com/users/abhinavg27/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abhinavg27/subscriptions","organizations_url":"https://api.github.com/users/abhinavg27/orgs","repos_url":"https://api.github.com/users/abhinavg27/repos","events_url":"https://api.github.com/users/abhinavg27/events{/privacy}","received_events_url":"https://api.github.com/users/abhinavg27/received_events","type":"User","site_admin":false},"html_url":"https://github.com/abhinavg27/sysfoo","description":"Sample java webapp with maven which prints system info","fork":true,"url":"https://api.github.com/repos/abhinavg27/sysfoo","forks_url":"https://api.github.com/repos/abhinavg27/sysfoo/forks","keys_url":"https://api.github.com/repos/abhinavg27/sysfoo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/abhinavg27/sysfoo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/abhinavg27/sysfoo/teams","hooks_url":"https://api.github.com/repos/abhinavg27/sysfoo/hooks","issue_events_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues/events{/number}","events_url":"https://api.github.com/repos/abhinavg27/sysfoo/events","assignees_url":"https://api.github.com/repos/abhinavg27/sysfoo/assignees{/user}","branches_url":"https://api.github.com/repos/abhinavg27/sysfoo/branches{/branch}","tags_url":"https://api.github.com/repos/abhinavg27/sysfoo/tags","blobs_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/refs{/sha}","trees_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/abhinavg27/sysfoo/statuses/{sha}","languages_url":"https://api.github.com/repos/abhinavg27/sysfoo/languages","stargazers_url":"https://api.github.com/repos/abhinavg27/sysfoo/stargazers","contributors_url":"https://api.github.com/repos/abhinavg27/sysfoo/contributors","subscribers_url":"https://api.github.com/repos/abhinavg27/sysfoo/subscribers","subscription_url":"https://api.github.com/repos/abhinavg27/sysfoo/subscription","commits_url":"https://api.github.com/repos/abhinavg27/sysfoo/commits{/sha}","git_commits_url":"https://api.github.com/repos/abhinavg27/sysfoo/git/commits{/sha}","comments_url":"https://api.github.com/repos/abhinavg27/sysfoo/comments{/number}","issue_comment_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues/comments{/number}","contents_url":"https://api.github.com/repos/abhinavg27/sysfoo/contents/{+path}","compare_url":"https://api.github.com/repos/abhinavg27/sysfoo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/abhinavg27/sysfoo/merges","archive_url":"https://api.github.com/repos/abhinavg27/sysfoo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/abhinavg27/sysfoo/downloads","issues_url":"https://api.github.com/repos/abhinavg27/sysfoo/issues{/number}","pulls_url":"https://api.github.com/repos/abhinavg27/sysfoo/pulls{/number}","milestones_url":"https://api.github.com/repos/abhinavg27/sysfoo/milestones{/number}","notifications_url":"https://api.github.com/repos/abhinavg27/sysfoo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/abhinavg27/sysfoo/labels{/name}","releases_url":"https://api.github.com/repos/abhinavg27/sysfoo/releases{/id}","deployments_url":"https://api.github.com/repos/abhinavg27/sysfoo/deployments","created_at":"2021-07-19T13:58:05Z","updated_at":"2021-07-20T11:29:25Z","pushed_at":"2021-07-20T12:38:42Z","git_url":"git://github.com/abhinavg27/sysfoo.git","ssh_url":"git@github.com:abhinavg27/sysfoo.git","clone_url":"https://github.com/abhinavg27/sysfoo.git","svn_url":"https://github.com/abhinavg27/sysfoo","homepage":null,"size":1656,"stargazers_count":0,"watchers_count":0,"language":"SCSS","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1"},"html":{"href":"https://github.com/abhinavg27/sysfoo/pull/1"},"issue":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/issues/1"},"comments":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/abhinavg27/sysfoo/statuses/40c2b9023159a7b4629e82e98f54df872d8825e1"}},"author_association":"OWNER","auto_merge":null,"active_lock_reason":null}},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340191","type":"CreateEvent","actor":{"id":68545867,"login":"suman7569","display_login":"suman7569","gravatar_id":"","url":"https://api.github.com/users/suman7569","avatar_url":"https://avatars.githubusercontent.com/u/68545867?"},"repo":{"id":388056970,"name":"suman7569/Video_Calling","url":"https://api.github.com/repos/suman7569/Video_Calling"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340199","type":"PushEvent","actor":{"id":43335750,"login":"paologallinaharbur","display_login":"paologallinaharbur","gravatar_id":"","url":"https://api.github.com/users/paologallinaharbur","avatar_url":"https://avatars.githubusercontent.com/u/43335750?"},"repo":{"id":385546865,"name":"newrelic-forks/entity-definitions","url":"https://api.github.com/repos/newrelic-forks/entity-definitions"},"payload":{"push_id":7562451444,"size":1,"distinct_size":1,"ref":"refs/heads/pgallina/powerDNS","head":"302b0f1a51ce7a0b74b147a2f17bc526a2db3613","before":"109bf65708bfa0b2ecb69984597397e08065380d","commits":[{"sha":"302b0f1a51ce7a0b74b147a2f17bc526a2db3613","author":{"name":"paologallinaharbur","email":"58deb4c7dc322085b6f44f808f6cc1b2d6d2895d@gmail.com"},"message":"feat(dashboards): updated Dashboards for powerdns","distinct":true,"url":"https://api.github.com/repos/newrelic-forks/entity-definitions/commits/302b0f1a51ce7a0b74b147a2f17bc526a2db3613"}]},"public":true,"created_at":"2021-07-21T09:00:04Z","org":{"id":8526625,"login":"newrelic-forks","gravatar_id":"","url":"https://api.github.com/orgs/newrelic-forks","avatar_url":"https://avatars.githubusercontent.com/u/8526625?"}} +{"id":"17246340203","type":"DeleteEvent","actor":{"id":7077857,"login":"peterbarker","display_login":"peterbarker","gravatar_id":"","url":"https://api.github.com/users/peterbarker","avatar_url":"https://avatars.githubusercontent.com/u/7077857?"},"repo":{"id":34165570,"name":"peterbarker/ardupilot","url":"https://api.github.com/repos/peterbarker/ardupilot"},"payload":{"ref":"pr/drain-pexpects-in-set_parameters-loop","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340206","type":"CreateEvent","actor":{"id":87755047,"login":"dasintasuheri","display_login":"dasintasuheri","gravatar_id":"","url":"https://api.github.com/users/dasintasuheri","avatar_url":"https://avatars.githubusercontent.com/u/87755047?"},"repo":{"id":388056973,"name":"dasintasuheri/dsinta","url":"https://api.github.com/repos/dasintasuheri/dsinta"},"payload":{"ref":null,"ref_type":"repository","master_branch":"main","description":null,"pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340209","type":"PushEvent","actor":{"id":20703732,"login":"junjie2018","display_login":"junjie2018","gravatar_id":"","url":"https://api.github.com/users/junjie2018","avatar_url":"https://avatars.githubusercontent.com/u/20703732?"},"repo":{"id":382508484,"name":"junjie2018/junjie2018.github.io","url":"https://api.github.com/repos/junjie2018/junjie2018.github.io"},"payload":{"push_id":7562451435,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"49c4b55f2c4ba821673b403fd1ec1a6198e710ca","before":"3ebd32937852df67f5db62d42d637a5e89b5e715","commits":[{"sha":"49c4b55f2c4ba821673b403fd1ec1a6198e710ca","author":{"name":"小桀","email":"0955e153c666b72b7cede7efc5d13eb56c6f89b7@qq.com"},"message":"from github actions","distinct":true,"url":"https://api.github.com/repos/junjie2018/junjie2018.github.io/commits/49c4b55f2c4ba821673b403fd1ec1a6198e710ca"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340219","type":"PushEvent","actor":{"id":5253690,"login":"weirdcalculator","display_login":"weirdcalculator","gravatar_id":"","url":"https://api.github.com/users/weirdcalculator","avatar_url":"https://avatars.githubusercontent.com/u/5253690?"},"repo":{"id":153466799,"name":"weirdcalculator/wptdolphin","url":"https://api.github.com/repos/weirdcalculator/wptdolphin"},"payload":{"push_id":7562451473,"size":1,"distinct_size":1,"ref":"refs/heads/master","head":"b5e6e711f092c8b601e284af666927e872545973","before":"df45489efc07509c7b3714561e54329006f888b7","commits":[{"sha":"b5e6e711f092c8b601e284af666927e872545973","author":{"name":"Amin","email":"f839be73d85218da9157c84c3cf7240c2e88dab7@gmail.com"},"message":"test update","distinct":true,"url":"https://api.github.com/repos/weirdcalculator/wptdolphin/commits/b5e6e711f092c8b601e284af666927e872545973"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340228","type":"PushEvent","actor":{"id":59016359,"login":"ewp-scripts","display_login":"ewp-scripts","gravatar_id":"","url":"https://api.github.com/users/ewp-scripts","avatar_url":"https://avatars.githubusercontent.com/u/59016359?"},"repo":{"id":225612963,"name":"erasmus-without-paper/ewp-registry-log-prod","url":"https://api.github.com/repos/erasmus-without-paper/ewp-registry-log-prod"},"payload":{"push_id":7562451471,"size":2,"distinct_size":2,"ref":"refs/heads/master","head":"0a8c848f9eb232e57bc95ae967d2946a3e63b9f9","before":"30b8c3d161b001b5123cce7a9d6db20738f1fea4","commits":[{"sha":"18654d2c51e8b64e528bc984a8a6c7bbe8a9f8de","author":{"name":"EWP Registry Service","email":"8254d1186852c26c9fb3d343db6a5be86317fea9@registry.erasmuswithoutpaper.eu"},"message":"Update manifest: https://ewp.ugent.be/public-api/discovery/manifest.xml","distinct":true,"url":"https://api.github.com/repos/erasmus-without-paper/ewp-registry-log-prod/commits/18654d2c51e8b64e528bc984a8a6c7bbe8a9f8de"},{"sha":"0a8c848f9eb232e57bc95ae967d2946a3e63b9f9","author":{"name":"EWP Registry Service","email":"8254d1186852c26c9fb3d343db6a5be86317fea9@registry.erasmuswithoutpaper.eu"},"message":"Update manifest: https://ewp.erasmusport.com/manifest.xml","distinct":true,"url":"https://api.github.com/repos/erasmus-without-paper/ewp-registry-log-prod/commits/0a8c848f9eb232e57bc95ae967d2946a3e63b9f9"}]},"public":true,"created_at":"2021-07-21T09:00:04Z","org":{"id":15247436,"login":"erasmus-without-paper","gravatar_id":"","url":"https://api.github.com/orgs/erasmus-without-paper","avatar_url":"https://avatars.githubusercontent.com/u/15247436?"}} +{"id":"17246340232","type":"DeleteEvent","actor":{"id":10650072,"login":"nsteenbeek","display_login":"nsteenbeek","gravatar_id":"","url":"https://api.github.com/users/nsteenbeek","avatar_url":"https://avatars.githubusercontent.com/u/10650072?"},"repo":{"id":223273550,"name":"hso-nn/d365-cli","url":"https://api.github.com/repos/hso-nn/d365-cli"},"payload":{"ref":"bugfix/5.0.3","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:04Z","org":{"id":59829618,"login":"hso-nn","gravatar_id":"","url":"https://api.github.com/orgs/hso-nn","avatar_url":"https://avatars.githubusercontent.com/u/59829618?"}} +{"id":"17246340237","type":"DeleteEvent","actor":{"id":10810283,"login":"direwolf-github","display_login":"direwolf-github","gravatar_id":"","url":"https://api.github.com/users/direwolf-github","avatar_url":"https://avatars.githubusercontent.com/u/10810283?"},"repo":{"id":388056838,"name":"direwolf-github/my-app-91037827","url":"https://api.github.com/repos/direwolf-github/my-app-91037827"},"payload":{"ref":"branch-0f271523","ref_type":"branch","pusher_type":"user"},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340244","type":"PushEvent","actor":{"id":24226219,"login":"Nasfame","display_login":"Nasfame","gravatar_id":"","url":"https://api.github.com/users/Nasfame","avatar_url":"https://avatars.githubusercontent.com/u/24226219?"},"repo":{"id":362322638,"name":"Nasfame/Mibonacci","url":"https://api.github.com/repos/Nasfame/Mibonacci"},"payload":{"push_id":7562451472,"size":10,"distinct_size":10,"ref":"refs/heads/master","head":"c3afde09957e3c350507b6daddb6413bf15731c7","before":"5aec37f7f05577a018a0cee3583e2c3b2830ff19","commits":[{"sha":"b87deda2f5674f37fb4c0dd371e56de207aa361a","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"timeit/4407.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/b87deda2f5674f37fb4c0dd371e56de207aa361a"},{"sha":"207003d35f4849472500290c35457bcf7f552556","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"4408.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/207003d35f4849472500290c35457bcf7f552556"},{"sha":"834bb652868807f5487e8d00bbbaefd26593d8bb","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"timeit/4408.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/834bb652868807f5487e8d00bbbaefd26593d8bb"},{"sha":"aec40ddd363e14ad28ce31d30e16800b9ab47ac5","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"4409.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/aec40ddd363e14ad28ce31d30e16800b9ab47ac5"},{"sha":"e89c83afaa4152948c63e886cd9b48eb6ba5a0aa","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"timeit/4409.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/e89c83afaa4152948c63e886cd9b48eb6ba5a0aa"},{"sha":"2effccb44329698d447af60fe12fafcd38767730","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"4410.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/2effccb44329698d447af60fe12fafcd38767730"},{"sha":"83f2966b0d563f5f35e5866ba4bda5d54df30c19","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"timeit/4410.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/83f2966b0d563f5f35e5866ba4bda5d54df30c19"},{"sha":"d509e748cfdc94f64d4100e9799276e7f1388314","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"4411.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/d509e748cfdc94f64d4100e9799276e7f1388314"},{"sha":"262652bdef19ab55615ab41017b2e40e8eb5f0d7","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"timeit/4411.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/262652bdef19ab55615ab41017b2e40e8eb5f0d7"},{"sha":"c3afde09957e3c350507b6daddb6413bf15731c7","author":{"name":"Hiro Hamada","email":"d160ddea0366475ad05f3967f70c38535067c171@gmail.com"},"message":"4412.txt","distinct":true,"url":"https://api.github.com/repos/Nasfame/Mibonacci/commits/c3afde09957e3c350507b6daddb6413bf15731c7"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340248","type":"PullRequestEvent","actor":{"id":25895405,"login":"Zeroto521","display_login":"Zeroto521","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","avatar_url":"https://avatars.githubusercontent.com/u/25895405?"},"repo":{"id":373016534,"name":"Zeroto521/my-data-toolkit","url":"https://api.github.com/repos/Zeroto521/my-data-toolkit"},"payload":{"action":"closed","number":75,"pull_request":{"url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/75","id":694132621,"node_id":"MDExOlB1bGxSZXF1ZXN0Njk0MTMyNjIx","html_url":"https://github.com/Zeroto521/my-data-toolkit/pull/75","diff_url":"https://github.com/Zeroto521/my-data-toolkit/pull/75.diff","patch_url":"https://github.com/Zeroto521/my-data-toolkit/pull/75.patch","issue_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/75","number":75,"state":"closed","locked":false,"title":"EHN: assign new columns to dataframe via function","user":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"body":"- EHN: assign new columns to dataframe via function\r\n- TST, BUG: test assigntf + miss `operate` method in assigntf\r\n- add a new `make` command to lint source codes","created_at":"2021-07-21T07:45:37Z","updated_at":"2021-07-21T09:00:04Z","closed_at":"2021-07-21T09:00:04Z","merged_at":"2021-07-21T09:00:03Z","merge_commit_sha":"742fba6c6f86d0c6391cb409da8f13550021d6cf","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/75/commits","review_comments_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/75/comments","review_comment_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/75/comments","statuses_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/statuses/4300023c620c73df8dab34f08126dd66edb09f27","head":{"label":"Zeroto521:tf","ref":"tf","sha":"4300023c620c73df8dab34f08126dd66edb09f27","user":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"repo":{"id":373016534,"node_id":"MDEwOlJlcG9zaXRvcnkzNzMwMTY1MzQ=","name":"my-data-toolkit","full_name":"Zeroto521/my-data-toolkit","private":false,"owner":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Zeroto521/my-data-toolkit","description":"A series of toolkits to decrease the same work include geographic calculation, data engineering, and so on.","fork":false,"url":"https://api.github.com/repos/Zeroto521/my-data-toolkit","forks_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/forks","keys_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/teams","hooks_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/hooks","issue_events_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/events{/number}","events_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/events","assignees_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/assignees{/user}","branches_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/branches{/branch}","tags_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/tags","blobs_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/statuses/{sha}","languages_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/languages","stargazers_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/stargazers","contributors_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/contributors","subscribers_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/subscribers","subscription_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/subscription","commits_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/contents/{+path}","compare_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/merges","archive_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/downloads","issues_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues{/number}","pulls_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls{/number}","milestones_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/milestones{/number}","notifications_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/labels{/name}","releases_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/releases{/id}","deployments_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/deployments","created_at":"2021-06-02T02:29:50Z","updated_at":"2021-07-21T07:44:15Z","pushed_at":"2021-07-21T09:00:03Z","git_url":"git://github.com/Zeroto521/my-data-toolkit.git","ssh_url":"git@github.com:Zeroto521/my-data-toolkit.git","clone_url":"https://github.com/Zeroto521/my-data-toolkit.git","svn_url":"https://github.com/Zeroto521/my-data-toolkit","homepage":"","size":216,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"Zeroto521:master","ref":"master","sha":"7d11b8637ecd957172105dface7b0009bf825ffe","user":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"repo":{"id":373016534,"node_id":"MDEwOlJlcG9zaXRvcnkzNzMwMTY1MzQ=","name":"my-data-toolkit","full_name":"Zeroto521/my-data-toolkit","private":false,"owner":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"html_url":"https://github.com/Zeroto521/my-data-toolkit","description":"A series of toolkits to decrease the same work include geographic calculation, data engineering, and so on.","fork":false,"url":"https://api.github.com/repos/Zeroto521/my-data-toolkit","forks_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/forks","keys_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/teams","hooks_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/hooks","issue_events_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/events{/number}","events_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/events","assignees_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/assignees{/user}","branches_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/branches{/branch}","tags_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/tags","blobs_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/statuses/{sha}","languages_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/languages","stargazers_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/stargazers","contributors_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/contributors","subscribers_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/subscribers","subscription_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/subscription","commits_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/contents/{+path}","compare_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/merges","archive_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/downloads","issues_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues{/number}","pulls_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls{/number}","milestones_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/milestones{/number}","notifications_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/labels{/name}","releases_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/releases{/id}","deployments_url":"https://api.github.com/repos/Zeroto521/my-data-toolkit/deployments","created_at":"2021-06-02T02:29:50Z","updated_at":"2021-07-21T07:44:15Z","pushed_at":"2021-07-21T09:00:03Z","git_url":"git://github.com/Zeroto521/my-data-toolkit.git","ssh_url":"git@github.com:Zeroto521/my-data-toolkit.git","clone_url":"https://github.com/Zeroto521/my-data-toolkit.git","svn_url":"https://github.com/Zeroto521/my-data-toolkit","homepage":"","size":216,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/75"},"html":{"href":"https://github.com/Zeroto521/my-data-toolkit/pull/75"},"issue":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/75"},"comments":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/issues/75/comments"},"review_comments":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/75/comments"},"review_comment":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/pulls/75/commits"},"statuses":{"href":"https://api.github.com/repos/Zeroto521/my-data-toolkit/statuses/4300023c620c73df8dab34f08126dd66edb09f27"}},"author_association":"OWNER","auto_merge":{"enabled_by":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"merge_method":"merge","commit_title":"EHN: assign new columns to dataframe via function (#75)","commit_message":"- EHN: assign new columns to dataframe via function\r\n- TST, BUG: test assigntf + miss `operate` method in assigntf\r\n- add a new `make` command to lint source codes"},"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"Zeroto521","id":25895405,"node_id":"MDQ6VXNlcjI1ODk1NDA1","avatar_url":"https://avatars.githubusercontent.com/u/25895405?v=4","gravatar_id":"","url":"https://api.github.com/users/Zeroto521","html_url":"https://github.com/Zeroto521","followers_url":"https://api.github.com/users/Zeroto521/followers","following_url":"https://api.github.com/users/Zeroto521/following{/other_user}","gists_url":"https://api.github.com/users/Zeroto521/gists{/gist_id}","starred_url":"https://api.github.com/users/Zeroto521/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zeroto521/subscriptions","organizations_url":"https://api.github.com/users/Zeroto521/orgs","repos_url":"https://api.github.com/users/Zeroto521/repos","events_url":"https://api.github.com/users/Zeroto521/events{/privacy}","received_events_url":"https://api.github.com/users/Zeroto521/received_events","type":"User","site_admin":false},"comments":2,"review_comments":0,"maintainer_can_modify":false,"commits":5,"additions":35,"deletions":2,"changed_files":3}},"public":true,"created_at":"2021-07-21T09:00:04Z"} +{"id":"17246340250","type":"PushEvent","actor":{"id":75016136,"login":"CastformMadrid","display_login":"CastformMadrid","gravatar_id":"","url":"https://api.github.com/users/CastformMadrid","avatar_url":"https://avatars.githubusercontent.com/u/75016136?"},"repo":{"id":315383656,"name":"alexelgt/Castform-Madrid","url":"https://api.github.com/repos/alexelgt/Castform-Madrid"},"payload":{"push_id":7562451482,"size":1,"distinct_size":1,"ref":"refs/heads/main","head":"a4708d6dcf8d8ba3b15c37ac47a92c29394e7c0d","before":"0a69f3e9a2644084e923725e8bed1eff1e10c7bd","commits":[{"sha":"a4708d6dcf8d8ba3b15c37ac47a92c29394e7c0d","author":{"name":"CastformMadrid","email":"4bf3034b7f6dbaf30fedab8adaf278c90d929f80@gmail.com"},"message":"21/7/2021 11:00","distinct":true,"url":"https://api.github.com/repos/alexelgt/Castform-Madrid/commits/a4708d6dcf8d8ba3b15c37ac47a92c29394e7c0d"}]},"public":true,"created_at":"2021-07-21T09:00:04Z"} diff --git a/pinot-tools/src/main/resources/examples/stream/githubEvents/docker/pullRequestMergedEvents_realtime_table_config.json b/pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/docker/pullRequestMergedEvents_realtime_table_config.json similarity index 100% rename from pinot-tools/src/main/resources/examples/stream/githubEvents/docker/pullRequestMergedEvents_realtime_table_config.json rename to pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/docker/pullRequestMergedEvents_realtime_table_config.json diff --git a/pinot-tools/src/main/resources/examples/stream/githubEvents/pullRequestMergedEvents_kinesis_realtime_table_config.json b/pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/pullRequestMergedEvents_kinesis_realtime_table_config.json similarity index 100% rename from pinot-tools/src/main/resources/examples/stream/githubEvents/pullRequestMergedEvents_kinesis_realtime_table_config.json rename to pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/pullRequestMergedEvents_kinesis_realtime_table_config.json diff --git a/pinot-tools/src/main/resources/examples/stream/githubEvents/pullRequestMergedEvents_realtime_table_config.json b/pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/pullRequestMergedEvents_realtime_table_config.json similarity index 100% rename from pinot-tools/src/main/resources/examples/stream/githubEvents/pullRequestMergedEvents_realtime_table_config.json rename to pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/pullRequestMergedEvents_realtime_table_config.json diff --git a/pinot-tools/src/main/resources/examples/stream/githubEvents/pullRequestMergedEvents_schema.json b/pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/pullRequestMergedEvents_schema.json similarity index 100% rename from pinot-tools/src/main/resources/examples/stream/githubEvents/pullRequestMergedEvents_schema.json rename to pinot-tools/src/main/resources/examples/stream/pullRequestMergedEvents/pullRequestMergedEvents_schema.json diff --git a/pinot-tools/src/main/resources/examples/stream/upsertMeetupRsvp/upsertMeetupRsvp_realtime_table_config.json b/pinot-tools/src/main/resources/examples/stream/upsertMeetupRsvp/upsertMeetupRsvp_realtime_table_config.json index d29431c6835..d7fa20d8fc3 100644 --- a/pinot-tools/src/main/resources/examples/stream/upsertMeetupRsvp/upsertMeetupRsvp_realtime_table_config.json +++ b/pinot-tools/src/main/resources/examples/stream/upsertMeetupRsvp/upsertMeetupRsvp_realtime_table_config.json @@ -8,7 +8,7 @@ "retentionTimeValue": "1", "segmentPushType": "APPEND", "segmentAssignmentStrategy": "BalanceNumSegmentAssignmentStrategy", - "schemaName": "meetupRsvp", + "schemaName": "upsertMeetupRsvp", "replicasPerPartition": "1", "replicaGroupStrategyConfig": { "partitionColumn": "event_id", @@ -57,6 +57,8 @@ "instanceSelectorType": "strictReplicaGroup" }, "upsertConfig": { - "mode": "FULL" + "mode": "FULL", + "enableSnapshot": "true", + "enablePreload": "true" } } diff --git a/pinot-tools/src/main/resources/examples/stream/upsertPartialMeetupRsvp/upsertPartialMeetupRsvp_realtime_table_config.json b/pinot-tools/src/main/resources/examples/stream/upsertPartialMeetupRsvp/upsertPartialMeetupRsvp_realtime_table_config.json index 2ebf3bb3795..e712f5f13dd 100644 --- a/pinot-tools/src/main/resources/examples/stream/upsertPartialMeetupRsvp/upsertPartialMeetupRsvp_realtime_table_config.json +++ b/pinot-tools/src/main/resources/examples/stream/upsertPartialMeetupRsvp/upsertPartialMeetupRsvp_realtime_table_config.json @@ -8,7 +8,7 @@ "retentionTimeValue": "1", "segmentPushType": "APPEND", "segmentAssignmentStrategy": "BalanceNumSegmentAssignmentStrategy", - "schemaName": "meetupRsvp", + "schemaName": "upsertPartialMeetupRsvp", "replicasPerPartition": "1" }, "tenants": {}, diff --git a/pom.xml b/pom.xml index 4c9b0510ef8..fdfe0506317 100644 --- a/pom.xml +++ b/pom.xml @@ -122,22 +122,22 @@ true 1.9.2 - 1.11.1 + 1.11.2 1.0.4 0.7 2.12.7 3.6.3 2.12.3 - 2.35 + 2.39 2.4.4 2.6.1 - 1.6.6 + 1.6.9 2.10.1 4.6 2.7.0 - 2.4.7 + 2.4.10 2.3.2 - 1.29.0 + 1.30.0 8.2.0 0.9.11 @@ -148,17 +148,18 @@ 3.1 4.2.2 - 1.1.8.2 + 1.1.10.1 1.5.2-3 1.8.0 2.17.1 - 4.1.79.Final + 4.1.94.Final 1.0.3 - 1.16.1 - 4.0.0 + 1.19.0 + 4.1.1 1.26 0.13.0 - + 0.4.3 + 2.20.94 -Xms4g -Xmx4g @@ -167,8 +168,8 @@ To change kafka connector dependency, we only need to update this version number config. TODO: figure out a way to inject kafka dependency instead of explicitly setting the kafka module dependency --> 2.0 - 3.19.2 - 1.41.0 + 3.22.0 + 1.53.0 5.5.3 @@ -249,6 +250,23 @@ + + + + + nl.jqno.equalsverifier + equalsverifier + 3.6 + test + + + org.mockito + mockito-core + 3.12.4 + test + + + other-jdk-maven-compiler-plugin @@ -422,7 +440,7 @@ nl.jqno.equalsverifier equalsverifier - 3.6 + 3.14.1 test @@ -463,17 +481,33 @@ com.google.guava guava - 20.0 + 32.0.1-jre + + + com.google.errorprone + error_prone_annotations + + + org.checkerframework + checker-qual + + + + + com.google.auto.service + auto-service + 1.0.1 + true commons-cli commons-cli - 1.2 + 1.5.0 org.apache.commons commons-lang3 - 3.11 + 3.12.0 commons-collections @@ -523,7 +557,7 @@ joda-time joda-time - 2.10.5 + 2.11.2 org.apache.avro @@ -649,7 +683,7 @@ org.yaml snakeyaml - 1.33 + 2.0 org.xerial.larray @@ -853,30 +887,10 @@ orc-mapreduce 1.5.9 - - org.codehaus.jackson - jackson-core-asl - 1.9.13 - - - org.codehaus.jackson - jackson-mapper-asl - 1.9.13 - - - org.codehaus.jackson - jackson-jaxrs - 1.9.13 - - - org.codehaus.jackson - jackson-xc - 1.9.13 - org.webjars swagger-ui - 3.23.11 + 5.1.0 com.clearspring.analytics @@ -886,7 +900,7 @@ org.apache.datasketches datasketches-java - 1.2.0-incubating + 4.1.0 com.tdunning @@ -979,7 +993,7 @@ com.jcabi jcabi-log - 0.17.1 + 0.18.1 com.google.code.findbugs @@ -1100,7 +1114,7 @@ org.mockito mockito-core - 3.9.0 + 5.3.1 test @@ -1117,12 +1131,17 @@ com.h2database h2 - 1.4.193 + 2.2.220 com.github.jnr jnr-posix - 3.0.12 + 3.1.15 + + + com.github.jnr + jnr-constants + 0.10.3 info.picocli @@ -1204,12 +1223,66 @@ h3 ${h3.version} - com.github.seancfoley ipaddress 5.3.4 + + com.mercateo + test-clock + 1.0.2 + test + + + org.projectlombok + lombok + + + + + + org.projectlombok + lombok + 1.18.28 + + + + net.openhft + posix + 2.23.2 + + + net.openhft + chronicle-core + 2.24ea16 + + + org.ow2.asm + asm + 9.3 + + + net.java.dev.jna + jna + 5.5.0 + + + + com.yscope.clp + clp-ffi + ${clp-ffi.version} + + + + + + software.amazon.awssdk + bom + ${aws.sdk.version} + pom + import + @@ -1342,13 +1415,22 @@ false plain + + ${argLine} + --add-opens=java.base/java.nio=ALL-UNNAMED + --add-opens=java.base/sun.nio.ch=ALL-UNNAMED + --add-opens=java.base/java.lang=ALL-UNNAMED + --add-opens=java.base/java.util=ALL-UNNAMED + --add-exports=java.base/jdk.internal.util.random=ALL-UNNAMED + --add-opens=java.base/java.lang.reflect=ALL-UNNAMED + org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M3 + 3.2.1 enforce-dependency-convergence @@ -1367,7 +1449,7 @@ com.mycila license-maven-plugin - 4.0 + 4.2 org.apache.maven.plugins @@ -1555,7 +1637,7 @@ com.mycila license-maven-plugin - 4.0 + 4.2
    ${pinot.root}/HEADER
    @@ -1570,6 +1652,7 @@ **/*.generated **/*.json **/*.schema + **/*.sql **/java.sql.* @@ -1683,6 +1766,7 @@ **/*.generated **/*.json **/*.schema + **/*.sql **/java.sql.*