Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0545a43e37
commit
f690cfa8c6
|
|
@ -6,7 +6,7 @@ workflow:
|
|||
|
||||
include:
|
||||
- local: .gitlab/ci/version.yml
|
||||
- component: "gitlab.com/gitlab-org/quality/pipeline-common/allure-report@9.6.2"
|
||||
- component: "gitlab.com/gitlab-org/quality/pipeline-common/allure-report@9.6.3"
|
||||
inputs:
|
||||
job_name: "e2e-test-report"
|
||||
job_stage: "report"
|
||||
|
|
@ -16,7 +16,7 @@ include:
|
|||
gitlab_auth_token_variable_name: "PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE"
|
||||
allure_job_name: "${QA_RUN_TYPE}"
|
||||
- project: gitlab-org/quality/pipeline-common
|
||||
ref: 9.6.2
|
||||
ref: 9.6.3
|
||||
file:
|
||||
- /ci/base.gitlab-ci.yml
|
||||
- /ci/knapsack-report.yml
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ variables:
|
|||
# Retry failed specs in separate process
|
||||
QA_RETRY_FAILED_SPECS: "true"
|
||||
# helm chart ref used by test-on-cng pipeline
|
||||
GITLAB_HELM_CHART_REF: "04b75b6610e7f1e4a013bef4964563d0219cbd72"
|
||||
GITLAB_HELM_CHART_REF: "37f35b47e7762d1c4b229ed7f00579666d9c0d9c"
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
<svg width="280" height="140" viewBox="0 0 280 140" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_187_122567)">
|
||||
<g clip-path="url(#clip1_187_122567)">
|
||||
<circle cx="189.5" cy="-42.5" r="131.5" fill="url(#paint0_radial_187_122567)"/>
|
||||
<circle cx="-41.5" cy="-97.5" r="198.5" fill="url(#paint1_radial_187_122567)"/>
|
||||
<circle cx="309.5" cy="-7.5" r="121.5" fill="url(#paint2_radial_187_122567)"/>
|
||||
<g filter="url(#filter0_b_187_122567)">
|
||||
<path d="M0 4C0 1.79086 1.79086 0 4 0H276C278.209 0 280 1.79086 280 4V130H0V4Z" fill="white" fill-opacity="0.01"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M183.948 47.9647H100.897V100.482H183.948V47.9647Z" fill="white"/>
|
||||
<path d="M100.64 47.9647H184.06V98.9817C184.06 104.1 179.908 108.256 174.793 108.256H100.638V47.9647H100.64Z" fill="white"/>
|
||||
<path d="M184.314 34.7452H100.64V47.9676H184.314V34.7452Z" fill="#AEA5D6"/>
|
||||
<path d="M109.594 43.2574C110.644 43.2574 111.495 42.4056 111.495 41.3549C111.495 40.3043 110.644 39.4525 109.594 39.4525C108.544 39.4525 107.693 40.3043 107.693 41.3549C107.693 42.4056 108.544 43.2574 109.594 43.2574Z" fill="#10B1B1"/>
|
||||
<path d="M116.482 43.2574C117.532 43.2574 118.383 42.4056 118.383 41.3549C118.383 40.3043 117.532 39.4525 116.482 39.4525C115.432 39.4525 114.581 40.3043 114.581 41.3549C114.581 42.4056 115.432 43.2574 116.482 43.2574Z" fill="#A888F4"/>
|
||||
<path d="M123.368 43.2574C124.418 43.2574 125.269 42.4056 125.269 41.3549C125.269 40.3043 124.418 39.4525 123.368 39.4525C122.318 39.4525 121.467 40.3043 121.467 41.3549C121.467 42.4056 122.318 43.2574 123.368 43.2574Z" fill="#FF9D73"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.556 34.4038H165.377V35.0858H101.238V61.7159H100.556V34.4038Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.238 66.3383V83.4486H100.556V66.3383H101.238Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M183.721 99.0231V89.5341H184.403V99.0231C184.403 104.311 180.118 108.599 174.834 108.599H120.244V107.917H174.834C179.741 107.917 183.721 103.935 183.721 99.0231Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M183.721 83.1296V62.2422H184.403V83.1296H183.721Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.596 39.7936C108.734 39.7936 108.036 40.4924 108.036 41.355C108.036 42.2176 108.734 42.9164 109.596 42.9164C110.457 42.9164 111.155 42.2176 111.155 41.355C111.155 40.4924 110.457 39.7936 109.596 39.7936ZM107.354 41.355C107.354 40.1162 108.357 39.1116 109.596 39.1116C110.834 39.1116 111.837 40.1162 111.837 41.355C111.837 42.5938 110.834 43.5985 109.596 43.5985C108.357 43.5985 107.354 42.5938 107.354 41.355Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M116.48 39.7936C115.619 39.7936 114.92 40.4924 114.92 41.355C114.92 42.2176 115.619 42.9164 116.48 42.9164C117.342 42.9164 118.04 42.2176 118.04 41.355C118.04 40.4924 117.342 39.7936 116.48 39.7936ZM114.238 41.355C114.238 40.1162 115.242 39.1116 116.48 39.1116C117.719 39.1116 118.722 40.1162 118.722 41.355C118.722 42.5938 117.719 43.5985 116.48 43.5985C115.242 43.5985 114.238 42.5938 114.238 41.355Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M123.367 39.7936C122.506 39.7936 121.808 40.4924 121.808 41.355C121.808 42.2176 122.506 42.9164 123.367 42.9164C124.229 42.9164 124.927 42.2176 124.927 41.355C124.927 40.4924 124.229 39.7936 123.367 39.7936ZM121.125 41.355C121.125 40.1162 122.129 39.1116 123.367 39.1116C124.606 39.1116 125.609 40.1162 125.609 41.355C125.609 42.5938 124.606 43.5985 123.367 43.5985C122.129 43.5985 121.125 42.5938 121.125 41.355Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M160.466 48.3058H100.897V47.6238H160.466V48.3058Z" fill="#171321"/>
|
||||
<path d="M173.511 80.7124H152.485V83.4598H173.511V80.7124Z" fill="#D0C5E2"/>
|
||||
<path d="M149.08 80.7009H111.703V83.4484H149.08V80.7009Z" fill="#E7E4F2"/>
|
||||
<path d="M111.702 68.5293H132.728V65.7819H111.702V68.5293Z" fill="#E7E4F2"/>
|
||||
<path d="M136.131 68.5336H173.508V65.7861H136.131V68.5336Z" fill="#AEA5D6"/>
|
||||
<path d="M111.703 75.9889H120.838V73.2415H111.703V75.9889Z" fill="#AEA5D6"/>
|
||||
<path d="M155.891 75.9978H173.512V73.2504H155.891V75.9978Z" fill="#E7E4F2"/>
|
||||
<path d="M124.244 75.9978H152.485V73.2504H124.244V75.9978Z" fill="#D0C5E2"/>
|
||||
<path d="M141.099 58.3217H124.332V61.0691H141.099V58.3217Z" fill="#D0C5E2"/>
|
||||
<path d="M173.512 58.3217H144.31V61.0691H173.512V58.3217Z" fill="#E7E4F2"/>
|
||||
<path d="M120.926 58.3198H111.703V61.0673H120.926V58.3198Z" fill="#AEA5D6"/>
|
||||
<path d="M144.115 90.9242H160.882V88.1768H144.115V90.9242Z" fill="#AEA5D6"/>
|
||||
<path d="M111.703 90.908H140.905V88.1605H111.703V90.908Z" fill="#D0C5E2"/>
|
||||
<path d="M164.288 90.9242H173.512V88.1768H164.288V90.9242Z" fill="#D0C5E2"/>
|
||||
<path d="M173.508 95.6178H158.224V98.3652H173.508V95.6178Z" fill="#D0C5E2"/>
|
||||
<path d="M154.82 95.6199H111.703V98.3673H154.82V95.6199Z" fill="#E7E4F2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M167.801 34.8965L168.463 35.0598L162.398 59.6461L189.091 57.5112L189.145 58.1911L161.509 60.4014L167.801 34.8965Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M134.795 81.6335L109.428 116.595L108.876 116.195L133.312 82.5167L92.5055 87.8873L92.4165 87.2111L134.795 81.6335Z" fill="#171321"/>
|
||||
<path d="M187.019 57.9366C197.646 57.9366 206.262 49.3147 206.262 38.6791C206.262 28.0435 197.646 19.4216 187.019 19.4216C176.392 19.4216 167.777 28.0435 167.777 38.6791C167.777 49.3147 176.392 57.9366 187.019 57.9366Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M187.019 19.6948C176.543 19.6948 168.05 28.1943 168.05 38.6794C168.05 49.1646 176.543 57.6641 187.019 57.6641C197.495 57.6641 205.989 49.1646 205.989 38.6794C205.989 28.1943 197.495 19.6948 187.019 19.6948ZM167.504 38.6794C167.504 27.8934 176.241 19.1492 187.019 19.1492C197.797 19.1492 206.534 27.8934 206.534 38.6794C206.534 49.4655 197.797 58.2097 187.019 58.2097C176.241 58.2097 167.504 49.4655 167.504 38.6794Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M212.255 38.6791C212.255 24.7294 200.956 13.4218 187.018 13.4218V12.677C201.368 12.677 213 24.3186 213 38.6791H212.255Z" fill="#AEA5D6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.0944 108.321C67.0944 122.27 78.3936 133.578 92.3316 133.578V134.323C77.9817 134.323 66.3496 122.681 66.3496 108.321H67.0944Z" fill="#AEA5D6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M181.794 28.4035C183.212 28.0221 184.673 28.8643 185.054 30.2837L184.395 30.4605C184.112 29.4049 183.026 28.7787 181.971 29.0622L181.794 28.4035Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M192.066 29.0622C191.011 28.7787 189.925 29.4049 189.642 30.4605L188.983 30.2837C189.364 28.8643 190.824 28.0221 192.243 28.4035L192.066 29.0622Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M195.892 33.5398V36.1862L192.04 37.4786L191.823 36.832L195.21 35.6956V33.5398H195.892Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M195.213 44.8355L192.045 43.7774L192.262 43.1305L195.895 44.3442V46.991H195.213V44.8355Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M178.827 35.6957V33.5398H178.145V36.1861L181.995 37.4786L182.212 36.832L178.827 35.6957Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M181.776 43.1305L181.992 43.7773L178.827 44.8354V46.991H178.145V44.3443L181.776 43.1305Z" fill="#171321"/>
|
||||
<path d="M182.675 34.3942L181.129 37.5941C179.03 41.9415 182.192 46.9908 187.019 46.9908C191.845 46.9908 195.008 41.9415 192.908 37.5941L191.363 34.3942" fill="#A888F4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M186.678 46.9908V34.3942H187.36V46.9908H186.678Z" fill="#171321"/>
|
||||
<path d="M191.25 34.3939H182.788V33.2534C182.788 28.2918 191.25 28.2987 191.25 33.2534V34.3939Z" fill="#7759C1"/>
|
||||
<path d="M92.4612 126.064C103.088 126.064 111.704 117.442 111.704 106.806C111.704 96.1706 103.088 87.5487 92.4612 87.5487C81.8339 87.5487 73.2188 96.1706 73.2188 106.806C73.2188 117.442 81.8339 126.064 92.4612 126.064Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.4604 87.89C82.0217 87.89 73.559 96.359 73.559 106.806C73.559 117.254 82.0217 125.723 92.4604 125.723C102.899 125.723 111.362 117.254 111.362 106.806C111.362 96.359 102.899 87.89 92.4604 87.89ZM72.877 106.806C72.877 95.9828 81.6445 87.208 92.4604 87.208C103.276 87.208 112.044 95.9828 112.044 106.806C112.044 117.63 103.276 126.405 92.4604 126.405C81.6445 126.405 72.877 117.63 72.877 106.806Z" fill="#171321"/>
|
||||
<path d="M98.2885 105.28H95.9653V101.385C95.9653 99.3302 94.2951 97.6587 92.2419 97.6587C90.1887 97.6587 88.5184 99.3302 88.5184 101.385V105.28H86.1953V101.385C86.1953 98.0489 88.9083 95.3337 92.2419 95.3337C95.5754 95.3337 98.2885 98.0489 98.2885 101.385V105.28Z" fill="#7759C1"/>
|
||||
<path d="M100.894 104.186H83.3677V116.836H100.894V104.186Z" fill="#A888F4"/>
|
||||
<path d="M92.1343 111.015C92.8235 111.015 93.3823 110.456 93.3823 109.766C93.3823 109.076 92.8235 108.517 92.1343 108.517C91.445 108.517 90.8862 109.076 90.8862 109.766C90.8862 110.456 91.445 111.015 92.1343 111.015Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M91.792 112.209V110.722H92.474V112.209H91.792Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.2401 53.2567V60.9103C87.2401 61.9966 88.1212 62.878 89.2061 62.878H121.061C122.523 62.878 123.709 64.0653 123.709 65.5278V67.1601H123.027V65.5278C123.027 64.4415 122.146 63.5601 121.061 63.5601H89.2061C87.7441 63.5601 86.5581 62.3728 86.5581 60.9103V53.2567H87.2401Z" fill="#171321"/>
|
||||
<path d="M123.368 67.735C123.685 67.735 123.942 67.4776 123.942 67.1601C123.942 66.8426 123.685 66.5852 123.368 66.5852C123.051 66.5852 122.793 66.8426 122.793 67.1601C122.793 67.4776 123.051 67.735 123.368 67.735Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M123.368 66.9262C123.239 66.9262 123.134 67.0306 123.134 67.16C123.134 67.2895 123.239 67.3939 123.368 67.3939C123.496 67.3939 123.601 67.2895 123.601 67.16C123.601 67.0306 123.496 66.9262 123.368 66.9262ZM122.452 67.16C122.452 66.6545 122.862 66.2441 123.368 66.2441C123.873 66.2441 124.283 66.6545 124.283 67.16C124.283 67.6656 123.873 68.0759 123.368 68.0759C122.862 68.0759 122.452 67.6656 122.452 67.16Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M167.208 82.5551V84.88C167.208 85.5854 167.778 86.1551 168.482 86.1551H193.305V86.8371H168.482C167.4 86.8371 166.526 85.9616 166.526 84.88V82.5551H167.208Z" fill="#171321"/>
|
||||
<path d="M166.867 83.13C167.184 83.13 167.441 82.8726 167.441 82.5551C167.441 82.2376 167.184 81.9802 166.867 81.9802C166.55 81.9802 166.292 82.2376 166.292 82.5551C166.292 82.8726 166.55 83.13 166.867 83.13Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.866 82.3209C166.737 82.3209 166.633 82.4254 166.633 82.5548C166.633 82.6842 166.737 82.7887 166.866 82.7887C166.995 82.7887 167.1 82.6842 167.1 82.5548C167.1 82.4255 166.995 82.3209 166.866 82.3209ZM165.951 82.5548C165.951 82.0492 166.36 81.6389 166.866 81.6389C167.372 81.6389 167.782 82.0492 167.782 82.5548C167.782 83.0605 167.372 83.4707 166.866 83.4707C166.36 83.4707 165.951 83.0604 165.951 82.5548Z" fill="#171321"/>
|
||||
<path d="M86.9023 37.1553C82.8536 41.5743 77.6099 40.6 77.6099 40.6V48.5768C77.6099 49.962 77.8867 51.3404 78.4796 52.5917C80.8973 57.6918 86.9023 59.5873 86.9023 59.5873C86.9023 59.5873 92.9051 57.6918 95.3251 52.5917C95.918 51.3404 96.1948 49.962 96.1948 48.5768V40.6C96.1948 40.6 90.9511 41.5743 86.9023 37.1553V37.1553Z" fill="#10B1B1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M90.6411 45.6638L85.4674 50.8415L83.1592 48.5289L83.6419 48.0471L85.4677 49.8764L90.1586 45.1818L90.6411 45.6638Z" fill="#171321"/>
|
||||
<path d="M197.955 76.3168C193.902 80.7427 188.651 79.7684 188.651 79.7684V87.7567C188.651 89.1443 188.928 90.5249 189.521 91.7786C191.943 96.8856 197.955 98.7834 197.955 98.7834C197.955 98.7834 203.967 96.8856 206.39 91.7786C206.985 90.5249 207.259 89.1443 207.259 87.7567V79.7684C207.259 79.7684 202.009 80.7427 197.955 76.3168Z" fill="#FC6D26"/>
|
||||
<path d="M197.19 83.1044L197.566 88.4584H198.343L198.719 83.1044H197.19Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M196.806 82.7468H199.102L198.676 88.8156H197.232L196.806 82.7468ZM197.572 83.4616L197.898 88.1009H198.009L198.335 83.4616H197.572Z" fill="#171321"/>
|
||||
<path d="M198.343 90.2083H197.566V91.1248H198.343V90.2083Z" fill="#171321"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M197.208 89.851H198.7V91.4823H197.208V89.851ZM197.922 90.5657V90.7675H197.985V90.5657H197.922Z" fill="#171321"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_b_187_122567" x="-50" y="-50" width="380" height="230" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="25"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_187_122567"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_187_122567" result="shape"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_187_122567" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(189.5 -42.5) rotate(89.5818) scale(125.986)">
|
||||
<stop stop-color="#7759C2"/>
|
||||
<stop offset="1" stop-color="#7759C2" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_radial_187_122567" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-41.5 -97.5) rotate(89.5818) scale(190.176)">
|
||||
<stop stop-color="#D64028"/>
|
||||
<stop offset="1" stop-color="#D64028" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_187_122567" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(309.5 -7.5) rotate(89.5818) scale(116.405)">
|
||||
<stop stop-color="#EF76F1"/>
|
||||
<stop offset="1" stop-color="#EF76F1" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_187_122567">
|
||||
<rect width="280" height="140" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_187_122567">
|
||||
<path d="M0 4C0 1.79086 1.79086 0 4 0H276C278.209 0 280 1.79086 280 4V140H0V4Z" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
|
@ -18,7 +18,7 @@ export default ({ el, router }) => {
|
|||
const { projectPath, ref, isBlob, webIdeUrl, ...options } = convertObjectPropsToCamelCase(
|
||||
JSON.parse(el.dataset.options),
|
||||
);
|
||||
const { webIdePromoPopoverImg, cssClasses, defaultBranch } = el.dataset;
|
||||
const { cssClasses, defaultBranch } = el.dataset;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
|
|
@ -33,7 +33,6 @@ export default ({ el, router }) => {
|
|||
return h(WebIdeButton, {
|
||||
props: {
|
||||
isBlob,
|
||||
webIdePromoPopoverImg,
|
||||
webIdeUrl: isBlob
|
||||
? webIdeUrl
|
||||
: webIDEUrl(
|
||||
|
|
|
|||
|
|
@ -74,7 +74,13 @@ export default {
|
|||
setSelected({ data }) {
|
||||
if (!data) return;
|
||||
this.selected = data[ACCESS_LEVELS.CREATE].map(
|
||||
({ id, user_id: userId, group_id: groupId, access_level: accessLevel }) => {
|
||||
({
|
||||
id,
|
||||
user_id: userId,
|
||||
group_id: groupId,
|
||||
access_level: accessLevel,
|
||||
deploy_key_id: deployKeyId,
|
||||
}) => {
|
||||
if (userId) {
|
||||
return {
|
||||
id,
|
||||
|
|
@ -91,6 +97,14 @@ export default {
|
|||
};
|
||||
}
|
||||
|
||||
if (deployKeyId) {
|
||||
return {
|
||||
id,
|
||||
deploy_key_id: deployKeyId,
|
||||
type: LEVEL_TYPES.DEPLOY_KEY,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
access_level: accessLevel,
|
||||
|
|
|
|||
|
|
@ -7,11 +7,15 @@ import { keysFor, START_SEARCH_PROJECT_FILE } from '~/behaviors/shortcuts/keybin
|
|||
import { sanitize } from '~/lib/dompurify';
|
||||
import { InternalEvents } from '~/tracking';
|
||||
import { FIND_FILE_BUTTON_CLICK } from '~/tracking/constants';
|
||||
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
|
||||
import { visitUrl, joinPaths, webIDEUrl } from '~/lib/utils/url_utility';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
|
||||
import BlobControls from '~/repository/components/header_area/blob_controls.vue';
|
||||
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
|
||||
import SourceCodeDownloadDropdown from '~/vue_shared/components/download_dropdown/download_dropdown.vue';
|
||||
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
|
||||
|
||||
export default {
|
||||
name: 'HeaderArea',
|
||||
|
|
@ -24,6 +28,10 @@ export default {
|
|||
RefSelector,
|
||||
Breadcrumbs,
|
||||
BlobControls,
|
||||
CodeDropdown,
|
||||
SourceCodeDownloadDropdown,
|
||||
CloneCodeDropdown,
|
||||
WebIdeLink: () => import('ee_else_ce/vue_shared/components/web_ide_link.vue'),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
|
@ -45,6 +53,26 @@ export default {
|
|||
'projectRootPath',
|
||||
'comparePath',
|
||||
'isReadmeView',
|
||||
'isFork',
|
||||
'needsToFork',
|
||||
'gitpodEnabled',
|
||||
'isBlob',
|
||||
'showEditButton',
|
||||
'showWebIdeButton',
|
||||
'showGitpodButton',
|
||||
'showPipelineEditorUrl',
|
||||
'webIdeUrl',
|
||||
'editUrl',
|
||||
'pipelineEditorUrl',
|
||||
'gitpodUrl',
|
||||
'userPreferencesGitpodPath',
|
||||
'userProfileEnableGitpodPath',
|
||||
'httpUrl',
|
||||
'xcodeUrl',
|
||||
'sshUrl',
|
||||
'kerberosUrl',
|
||||
'downloadLinks',
|
||||
'downloadArtifacts',
|
||||
],
|
||||
props: {
|
||||
projectPath: {
|
||||
|
|
@ -84,6 +112,24 @@ export default {
|
|||
refSelectorValue() {
|
||||
return this.refType ? joinPaths('refs', this.refType, this.currentRef) : this.currentRef;
|
||||
},
|
||||
webIDEUrl() {
|
||||
return this.isBlob
|
||||
? this.webIdeUrl
|
||||
: webIDEUrl(
|
||||
joinPaths(
|
||||
'/',
|
||||
this.projectPath,
|
||||
'edit',
|
||||
this.currentRef,
|
||||
'-',
|
||||
this.$route?.params.path || '',
|
||||
'/',
|
||||
),
|
||||
);
|
||||
},
|
||||
projectIdAsNumber() {
|
||||
return getIdFromGraphQLId(this.projectId);
|
||||
},
|
||||
findFileTooltip() {
|
||||
const { description } = START_SEARCH_PROJECT_FILE;
|
||||
const key = this.findFileShortcutKey;
|
||||
|
|
@ -108,7 +154,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section class="nav-block gl-flex gl-flex-col gl-items-stretch sm:gl-flex-row">
|
||||
<section class="nav-block gl-flex gl-flex-col gl-items-stretch sm:gl-flex-row sm:gl-items-center">
|
||||
<div class="tree-ref-container mb-2 mb-md-0 gl-flex gl-flex-wrap gl-gap-2">
|
||||
<ref-selector
|
||||
v-if="!isReadmeView"
|
||||
|
|
@ -155,13 +201,58 @@ export default {
|
|||
v-gl-tooltip.html="findFileTooltip"
|
||||
:aria-keyshortcuts="findFileShortcutKey"
|
||||
data-testid="tree-find-file-control"
|
||||
class="gl-mt-3 gl-w-full sm:gl-mt-0 sm:gl-w-auto"
|
||||
class="gl-w-full sm:gl-w-auto"
|
||||
@click="handleFindFile"
|
||||
>
|
||||
{{ $options.i18n.findFile }}
|
||||
</gl-button>
|
||||
<!-- web ide -->
|
||||
<web-ide-link
|
||||
class="gl-w-full sm:!gl-ml-0 sm:gl-w-auto"
|
||||
data-testid="js-tree-web-ide-link"
|
||||
:project-id="projectIdAsNumber"
|
||||
:project-path="projectPath"
|
||||
:is-fork="isFork"
|
||||
:needs-to-fork="needsToFork"
|
||||
:gitpod-enabled="gitpodEnabled"
|
||||
:is-blob="isBlob"
|
||||
:show-edit-button="showEditButton"
|
||||
:show-web-ide-button="showWebIdeButton"
|
||||
:show-gitpod-button="showGitpodButton"
|
||||
:show-pipeline-editor-url="showPipelineEditorUrl"
|
||||
:web-ide-url="webIDEUrl"
|
||||
:edit-url="editUrl"
|
||||
:pipeline-editor-url="pipelineEditorUrl"
|
||||
:gitpod-url="gitpodUrl"
|
||||
:user-preferences-gitpod-path="userPreferencesGitpodPath"
|
||||
:user-profile-enable-gitpod-path="userProfileEnableGitpodPath"
|
||||
disable-fork-modal
|
||||
v-on="$listeners"
|
||||
/>
|
||||
<!-- code + mobile panel -->
|
||||
<div v-if="!isReadmeView" class="project-code-holder gl-w-full sm:gl-w-auto">
|
||||
<code-dropdown
|
||||
class="git-clone-holder js-git-clone-holder gl-hidden sm:gl-inline-block"
|
||||
:ssh-url="sshUrl"
|
||||
:http-url="httpUrl"
|
||||
:kerberos-url="kerberosUrl"
|
||||
:xcode-url="xcodeUrl"
|
||||
:current-path="currentPath"
|
||||
:directory-download-links="downloadLinks"
|
||||
/>
|
||||
<div class="gl-flex gl-items-stretch gl-gap-3 sm:gl-hidden">
|
||||
<source-code-download-dropdown
|
||||
:download-links="downloadLinks"
|
||||
:download-artifacts="downloadArtifacts"
|
||||
/>
|
||||
<clone-code-dropdown
|
||||
class="git-clone-holder js-git-clone-holder !gl-w-full"
|
||||
:ssh-url="sshUrl"
|
||||
:http-url="httpUrl"
|
||||
:kerberos-url="kerberosUrl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blob controls -->
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import apolloProvider from './graphql';
|
||||
import projectShortPathQuery from './queries/project_short_path.query.graphql';
|
||||
import projectPathQuery from './queries/project_path.query.graphql';
|
||||
|
|
@ -52,9 +52,33 @@ export default function initHeaderApp({ router, isReadmeView = false, isBlobView
|
|||
projectRootPath,
|
||||
comparePath,
|
||||
projectPath,
|
||||
webIdeButtonOptions,
|
||||
sshUrl,
|
||||
httpUrl,
|
||||
xcodeUrl,
|
||||
kerberosUrl,
|
||||
downloadLinks,
|
||||
downloadArtifacts,
|
||||
projectShortPath,
|
||||
} = headerEl.dataset;
|
||||
|
||||
const {
|
||||
isFork,
|
||||
needsToFork,
|
||||
gitpodEnabled,
|
||||
isBlob,
|
||||
showEditButton,
|
||||
showWebIdeButton,
|
||||
showGitpodButton,
|
||||
showPipelineEditorUrl,
|
||||
webIdeUrl,
|
||||
editUrl,
|
||||
pipelineEditorUrl,
|
||||
gitpodUrl,
|
||||
userPreferencesGitpodPath,
|
||||
userProfileEnableGitpodPath,
|
||||
} = convertObjectPropsToCamelCase(JSON.parse(webIdeButtonOptions));
|
||||
|
||||
initClientQueries({ projectPath, projectShortPath, ref, escapedRef });
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
|
|
@ -78,6 +102,26 @@ export default function initHeaderApp({ router, isReadmeView = false, isBlobView
|
|||
projectShortPath,
|
||||
comparePath,
|
||||
isReadmeView,
|
||||
isFork: parseBoolean(isFork),
|
||||
needsToFork: parseBoolean(needsToFork),
|
||||
gitpodEnabled: parseBoolean(gitpodEnabled),
|
||||
isBlob: parseBoolean(isBlob),
|
||||
showEditButton: parseBoolean(showEditButton),
|
||||
showWebIdeButton: parseBoolean(showWebIdeButton),
|
||||
showGitpodButton: parseBoolean(showGitpodButton),
|
||||
showPipelineEditorUrl: parseBoolean(showPipelineEditorUrl),
|
||||
webIdeUrl,
|
||||
editUrl,
|
||||
pipelineEditorUrl,
|
||||
gitpodUrl,
|
||||
userPreferencesGitpodPath,
|
||||
userProfileEnableGitpodPath,
|
||||
httpUrl,
|
||||
xcodeUrl,
|
||||
sshUrl,
|
||||
kerberosUrl,
|
||||
downloadLinks: downloadLinks ? JSON.parse(downloadLinks) : null,
|
||||
downloadArtifacts: downloadArtifacts ? JSON.parse(downloadArtifacts) : [],
|
||||
isBlobView,
|
||||
},
|
||||
apolloProvider,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { fetchPolicies } from '~/lib/graphql';
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import SnippetCodeDropdown from '~/vue_shared/components/code_dropdown/snippet_code_dropdown.vue';
|
||||
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/alert';
|
||||
import { VISIBILITY_LEVEL_PUBLIC_STRING } from '~/visibility_level/constants';
|
||||
|
|
@ -38,7 +38,7 @@ export const i18n = {
|
|||
|
||||
export default {
|
||||
components: {
|
||||
SnippetCodeDropdown,
|
||||
CloneCodeDropdown,
|
||||
GlIcon,
|
||||
GlSprintf,
|
||||
GlModal,
|
||||
|
|
@ -290,10 +290,10 @@ export default {
|
|||
{{ editItem.text }}
|
||||
</gl-button>
|
||||
|
||||
<snippet-code-dropdown
|
||||
<clone-code-dropdown
|
||||
v-if="canBeClonedOrEmbedded"
|
||||
:ssh-link="snippet.sshUrlToRepo"
|
||||
:http-link="snippet.httpUrlToRepo"
|
||||
:ssh-url="snippet.sshUrlToRepo"
|
||||
:http-url="snippet.httpUrlToRepo"
|
||||
:url="snippet.webUrl"
|
||||
:embeddable="embeddable"
|
||||
data-testid="code-button"
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export default {
|
|||
:disabled="btn.disabled || btn.loading"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-float-left md:gl-block"
|
||||
class="gl-float-left md:gl-inline-flex"
|
||||
@click="onClickAction(btn)"
|
||||
>
|
||||
{{ btn.text }}
|
||||
|
|
@ -160,7 +160,7 @@ export default {
|
|||
:disabled="btn.disabled || btn.loading"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-float-left gl-hidden md:gl-block"
|
||||
class="gl-float-left gl-hidden md:gl-inline-flex"
|
||||
@click="onClickAction(btn)"
|
||||
>
|
||||
{{ btn.text }}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export default {
|
|||
:disabled="btn.loading"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-float-left gl-hidden md:gl-block"
|
||||
class="gl-float-left gl-hidden md:gl-inline-flex"
|
||||
@click="($event) => onClickAction(btn, $event)"
|
||||
>
|
||||
<template v-if="btn.text">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import CloneCodeDropdown from './clone_code_dropdown.vue';
|
||||
|
||||
export default {
|
||||
component: CloneCodeDropdown,
|
||||
title: 'vue_shared/components/clone_code_dropdown',
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
components: { CloneCodeDropdown },
|
||||
props: Object.keys(argTypes),
|
||||
template: '<clone-code-dropdown v-bind="$props" />',
|
||||
});
|
||||
|
||||
const sshUrl = 'ssh://some-ssh-link';
|
||||
const httpLink = 'https://some-http-link';
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
sshUrl,
|
||||
httpUrl: httpLink,
|
||||
};
|
||||
|
|
@ -13,19 +13,25 @@ export default {
|
|||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
sshLink: {
|
||||
sshUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
httpLink: {
|
||||
httpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
kerberosUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
embeddable: {
|
||||
type: Boolean,
|
||||
|
|
@ -35,16 +41,24 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
httpLabel() {
|
||||
const protocol = this.httpLink ? getHTTPProtocol(this.httpLink)?.toUpperCase() : '';
|
||||
const protocol = this.httpUrl ? getHTTPProtocol(this.httpUrl)?.toUpperCase() : '';
|
||||
return sprintf(__('Clone with %{protocol}'), { protocol });
|
||||
},
|
||||
sections() {
|
||||
const sections = [
|
||||
{ label: __('Clone with SSH'), link: this.sshLink, testId: 'copy-ssh-url' },
|
||||
{ label: this.httpLabel, link: this.httpLink, testId: 'copy-http-url' },
|
||||
{ label: __('Clone with SSH'), link: this.sshUrl, testId: 'copy-ssh-url' },
|
||||
{ label: this.httpLabel, link: this.httpUrl, testId: 'copy-http-url' },
|
||||
];
|
||||
|
||||
if (this.embeddable) {
|
||||
if (this.kerberosUrl) {
|
||||
sections.push({
|
||||
label: __('Clone with KRB5'),
|
||||
link: this.kerberosUrl,
|
||||
testId: 'copy-kerberos-url',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.embeddable && this.url) {
|
||||
sections.push(
|
||||
{
|
||||
label: __('Embed'),
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import SnippetCodeDropdown from './snippet_code_dropdown.vue';
|
||||
|
||||
export default {
|
||||
component: SnippetCodeDropdown,
|
||||
title: 'vue_shared/components/snippet_code_dropdown',
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
components: { SnippetCodeDropdown },
|
||||
props: Object.keys(argTypes),
|
||||
template: '<clone-dropdown v-bind="$props" />',
|
||||
});
|
||||
|
||||
const sshLink = 'ssh://some-ssh-link';
|
||||
const httpLink = 'https://some-http-link';
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
sshLink,
|
||||
httpLink,
|
||||
};
|
||||
|
||||
export const HttpLink = Template.bind({});
|
||||
HttpLink.args = {
|
||||
httpLink,
|
||||
sshLink: '',
|
||||
};
|
||||
|
||||
export const SSHLink = Template.bind({});
|
||||
SSHLink.args = {
|
||||
sshLink,
|
||||
httpLink: '',
|
||||
};
|
||||
|
|
@ -124,11 +124,6 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
webIdePromoPopoverImg: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
cssClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
|
|||
|
|
@ -152,6 +152,13 @@ export const autocompleteDataSources = ({ fullPath, iid, workItemTypeId, isGroup
|
|||
workItemTypeId,
|
||||
isGroup,
|
||||
}),
|
||||
milestones: autocompleteSourcesPath({
|
||||
autocompleteType: 'milestones',
|
||||
fullPath,
|
||||
iid,
|
||||
workItemTypeId,
|
||||
isGroup,
|
||||
}),
|
||||
});
|
||||
|
||||
export const markdownPreviewPath = ({ fullPath, iid, isGroup = false }) => {
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ module Ci
|
|||
true
|
||||
end
|
||||
|
||||
def policy_allowed?(accessed_project, policy)
|
||||
def policies_allowed?(accessed_project, policies)
|
||||
return true if self_referential?(accessed_project)
|
||||
return true unless accessed_project.ci_inbound_job_token_scope_enabled?
|
||||
return false unless inbound_accessible?(accessed_project)
|
||||
|
||||
policy_allowed_for_accessed_project?(accessed_project, policy)
|
||||
policies_allowed_for_accessed_project?(accessed_project, policies)
|
||||
end
|
||||
|
||||
def outbound_projects
|
||||
|
|
@ -91,11 +91,13 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def policy_allowed_for_accessed_project?(accessed_project, policy)
|
||||
def policies_allowed_for_accessed_project?(accessed_project, policies)
|
||||
scope = nearest_scope(accessed_project)
|
||||
return true if scope.default_permissions?
|
||||
return false if policies.empty?
|
||||
|
||||
policy.to_s.in?(scope.job_token_policies)
|
||||
allowed_policies = scope.job_token_policies.map(&:to_sym)
|
||||
(policies - allowed_policies).empty?
|
||||
end
|
||||
|
||||
def nearest_scope(accessed_project)
|
||||
|
|
|
|||
|
|
@ -683,6 +683,10 @@ class User < ApplicationRecord
|
|||
scope :left_join_user_detail, -> { left_joins(:user_detail) }
|
||||
scope :preload_user_detail, -> { preload(:user_detail) }
|
||||
|
||||
scope :by_bot_namespace_ids, ->(namespace_ids) do
|
||||
project_bot.joins(:user_detail).where(user_detail: { bot_namespace_id: namespace_ids })
|
||||
end
|
||||
|
||||
def self.supported_keyset_orderings
|
||||
{
|
||||
id: [:asc, :desc],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ module WorkItems
|
|||
module Widgets
|
||||
class Milestone < Base
|
||||
delegate :milestone, to: :work_item
|
||||
|
||||
def self.quick_action_commands
|
||||
[:milestone, :remove_milestone]
|
||||
end
|
||||
|
||||
def self.quick_action_params
|
||||
[:milestone_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ module Ci
|
|||
WINDOW_LENGTH = 30.days
|
||||
|
||||
def execute
|
||||
return execute_with_new_table if ::Feature.enabled?(:ci_catalog_ranking_from_new_usage_table, :instance)
|
||||
|
||||
return ServiceResponse.success(message: "Processing complete for #{today}") if done_processing?
|
||||
|
||||
aggregator = Gitlab::Ci::Components::Usages::Aggregator.new(
|
||||
|
|
@ -36,8 +38,45 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def execute_with_new_table
|
||||
update_component_counts
|
||||
update_resource_counts
|
||||
|
||||
ServiceResponse.success(message: 'Usage counts updated for components and resources')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_component_counts
|
||||
Ci::Catalog::Resources::Component.find_each(batch_size: 1000) do |component|
|
||||
usage_count = component
|
||||
.last_usages
|
||||
.select(:used_by_project_id)
|
||||
.distinct
|
||||
.count
|
||||
|
||||
component.update_columns(
|
||||
last_30_day_usage_count: usage_count
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def update_resource_counts
|
||||
# Using a subquery to sum component counts for each resource
|
||||
resource_counts = Component
|
||||
.group(:catalog_resource_id)
|
||||
.select(
|
||||
:catalog_resource_id,
|
||||
'SUM(last_30_day_usage_count) as total_usage'
|
||||
)
|
||||
|
||||
resource_counts.each do |result|
|
||||
Ci::Catalog::Resource.where(id: result.catalog_resource_id).update_all(
|
||||
last_30_day_usage_count: result.total_usage
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: New catalog resources added today are considered already processed
|
||||
# because their `last_30_day_usage_count_updated_at` is defaulted to NOW().
|
||||
def done_processing?
|
||||
|
|
|
|||
|
|
@ -282,13 +282,9 @@ class IssuableBaseService < ::BaseContainerService
|
|||
|
||||
change_additional_attributes(issuable)
|
||||
|
||||
assign_requested_assignees(issuable)
|
||||
assign_requested_crm_contacts(issuable)
|
||||
widget_params = filter_widget_params
|
||||
|
||||
initialize_callbacks!(issuable)
|
||||
|
||||
if issuable.changed? || params.present? || widget_params.present? || @callbacks.present?
|
||||
if issuable.changed? || params.present? || @callbacks.present?
|
||||
issuable.assign_attributes(allowed_update_params(params))
|
||||
|
||||
before_update(issuable)
|
||||
|
|
@ -447,6 +443,9 @@ class IssuableBaseService < ::BaseContainerService
|
|||
change_subscription(issuable)
|
||||
change_todo(issuable)
|
||||
toggle_award(issuable)
|
||||
|
||||
assign_requested_assignees(issuable)
|
||||
assign_requested_crm_contacts(issuable)
|
||||
end
|
||||
|
||||
def change_state(issuable)
|
||||
|
|
@ -627,10 +626,6 @@ class IssuableBaseService < ::BaseContainerService
|
|||
issuable_sla.update(issuable_closed: issuable.closed?)
|
||||
end
|
||||
|
||||
def filter_widget_params
|
||||
params.delete(:widget_params)
|
||||
end
|
||||
|
||||
def filter_contact_params(issuable)
|
||||
return if params.slice(:add_contacts, :remove_contacts).empty?
|
||||
return if can?(current_user, :set_issue_crm_contacts, issuable)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ module QuickActions
|
|||
end
|
||||
|
||||
def find_milestones(project, params = {})
|
||||
return [] unless project
|
||||
|
||||
group_ids = project.group.self_and_ancestors.select(:id) if project.group
|
||||
|
||||
MilestonesFinder.new(params.merge(project_ids: [project.id], group_ids: group_ids)).execute
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ module WorkItems
|
|||
|
||||
def initialize(container:, current_user: nil, params: {}, perform_spam_check: false, widget_params: {})
|
||||
@extra_params = params.delete(:extra_params) || {}
|
||||
params[:widget_params] = true if widget_params.present?
|
||||
|
||||
super(container: container, current_user: current_user, params: params, perform_spam_check: perform_spam_check)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
- ref = local_assigns.fetch(:ref) { current_ref }
|
||||
- project = local_assigns.fetch(:project) { @project }
|
||||
- web_ide_button_data = web_ide_button_data({ blob: nil })
|
||||
- fork_options = fork_modal_options(@project, nil)
|
||||
- archive_prefix = ref ? "#{project.path}-#{ref.tr('/', '-')}" : ''
|
||||
- download_links = !project.empty_repo? ? download_links(project, ref, archive_prefix).to_json : ''
|
||||
- pipeline = local_assigns.fetch(:pipeline, nil)
|
||||
- download_artifacts = pipeline && previous_artifacts(project, ref, pipeline.latest_builds_with_artifacts).to_json || []
|
||||
- add_page_startup_api_call logs_file_project_ref_path(@project, ref, @path, format: "json", offset: 0, ref_type: @ref_type)
|
||||
- if readme_path = @project.repository.readme_path
|
||||
- add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
|
||||
|
|
@ -10,7 +16,24 @@
|
|||
|
||||
#tree-holder.tree-holder.clearfix.js-per-page.gl-mt-5{ data: { blame_per_page: Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE } }
|
||||
- if params[:common_repository_blob_header_app] == 'true'
|
||||
#js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } }
|
||||
#js-repository-blob-header-app{ data: {
|
||||
project_id: @project.id,
|
||||
ref: ref,
|
||||
ref_type: @ref_type.to_s,
|
||||
breadcrumbs: breadcrumb_data_attributes,
|
||||
project_root_path: project_path(@project),
|
||||
project_path: project.full_path,
|
||||
compare_path: compare_path,
|
||||
web_ide_button_options: web_ide_button_data.merge(fork_options).to_json,
|
||||
web_ide_button_default_branch: @project.default_branch_or_main,
|
||||
ssh_url: ssh_enabled? ? ssh_clone_url_to_repo(@project) : '',
|
||||
http_url: http_enabled? ? http_clone_url_to_repo(@project) : '',
|
||||
xcode_url: show_xcode_link?(@project) ? xcode_uri_to_repo(@project) : '',
|
||||
kerberos_url: alternative_kerberos_url? ? project.kerberos_url_to_repo : '',
|
||||
download_links: download_links,
|
||||
download_artifacts: download_artifacts,
|
||||
escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref)
|
||||
} }
|
||||
|
||||
- else
|
||||
.nav-block.gl-flex.gl-flex-col.sm:gl-flex-row.gl-items-stretch
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
- ref = local_assigns.fetch(:ref) { current_ref }
|
||||
- project = local_assigns.fetch(:project) { @project }
|
||||
- web_ide_button_data = web_ide_button_data({ blob: nil })
|
||||
- fork_options = fork_modal_options(@project, nil)
|
||||
- add_page_specific_style 'page_bundles/projects'
|
||||
- unless @ref.blank? || @repository&.root_ref == @ref
|
||||
- compare_path = project_compare_index_path(@project, from: @repository&.root_ref, to: @ref)
|
||||
|
|
@ -7,7 +9,18 @@
|
|||
- if (readme = @repository.readme) && readme.rich_viewer
|
||||
.tree-holder.gl-mt-5
|
||||
- if params[:common_repository_blob_header_app] == 'true'
|
||||
#js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } }
|
||||
#js-repository-blob-header-app{ data: {
|
||||
project_id: @project.id,
|
||||
ref: ref,
|
||||
ref_type: @ref_type.to_s,
|
||||
breadcrumbs: breadcrumb_data_attributes,
|
||||
project_root_path: project_path(@project),
|
||||
project_path: project.full_path,
|
||||
compare_path: compare_path,
|
||||
web_ide_button_options: web_ide_button_data.merge(fork_options).to_json,
|
||||
web_ide_button_default_branch: @project.default_branch_or_main,
|
||||
escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref)
|
||||
} }
|
||||
|
||||
- else
|
||||
.nav-block.mt-0
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
= render layout: 'projects/protected_tags/shared/protected_tag', locals: { protected_tag: protected_tag } do
|
||||
%td.create_access_levels-container
|
||||
= render 'projects/protected_tags/protected_tag_create_access_levels', protected_tag: protected_tag, create_access_level: protected_tag.create_access_levels.for_role
|
||||
= render 'projects/protected_tags/protected_tag_create_access_levels', protected_tag: protected_tag, create_access_level: protected_tag.create_access_levels
|
||||
= render_if_exists 'projects/protected_tags/protected_tag_extra_create_access_levels', protected_tag: protected_tag
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
- fork_options = fork_modal_options(@project, blob)
|
||||
- css_classes = false unless local_assigns[:css_classes]
|
||||
|
||||
.gl-inline-block{ data: { options: button_data.merge(fork_options).to_json, web_ide_promo_popover_img: image_path('web-ide-promo-popover.svg'), css_classes: css_classes, default_branch: @project.default_branch_or_main }, id: "js-#{type}-web-ide-link" }
|
||||
.gl-inline-block{ data: { options: button_data.merge(fork_options).to_json, css_classes: css_classes, default_branch: @project.default_branch_or_main }, id: "js-#{type}-web-ide-link" }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: ci_catalog_ranking_from_new_usage_table
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/work_items/495075
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173081
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/505196
|
||||
milestone: '17.7'
|
||||
group: group::pipeline authoring
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
# `16-9-deprecated-feature.yml`, where `16-9` is the announcement milestone,
|
||||
# not the removal milestone.
|
||||
#
|
||||
# See the deprecation guidelines to confirm your understanding of GitLab's definitions:
|
||||
# See the deprecation guidelines to confirm your understanding of the terminology:
|
||||
# https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology
|
||||
#
|
||||
# If an End of Support period applies, see the OPTIONAL section below.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
migration_job_name: BackfillIssuableSeveritiesNamespaceId
|
||||
description: Backfills sharding key `issuable_severities.namespace_id` from `issues`.
|
||||
feature_category: team_planning
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173728
|
||||
milestone: '17.7'
|
||||
queued_migration_version: 20241125145009
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -18,3 +18,4 @@ desired_sharding_key:
|
|||
sharding_key: namespace_id
|
||||
belongs_to: issue
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name: BackfillIssuableSeveritiesNamespaceId
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddNamespaceIdToIssuableSeverities < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
def change
|
||||
add_column :issuable_severities, :namespace_id, :bigint
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexIssuableSeveritiesOnNamespaceId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_issuable_severities_on_namespace_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :issuable_severities, :namespace_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :issuable_severities, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIssuableSeveritiesNamespaceIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :issuable_severities, :namespaces, column: :namespace_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :issuable_severities, column: :namespace_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIssuableSeveritiesNamespaceIdTrigger < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
def up
|
||||
install_sharding_key_assignment_trigger(
|
||||
table: :issuable_severities,
|
||||
sharding_key: :namespace_id,
|
||||
parent_table: :issues,
|
||||
parent_sharding_key: :namespace_id,
|
||||
foreign_key: :issue_id
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_sharding_key_assignment_trigger(
|
||||
table: :issuable_severities,
|
||||
sharding_key: :namespace_id,
|
||||
parent_table: :issues,
|
||||
parent_sharding_key: :namespace_id,
|
||||
foreign_key: :issue_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillIssuableSeveritiesNamespaceId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
MIGRATION = "BackfillIssuableSeveritiesNamespaceId"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:issuable_severities,
|
||||
:id,
|
||||
:namespace_id,
|
||||
:issues,
|
||||
:namespace_id,
|
||||
:issue_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
:issuable_severities,
|
||||
:id,
|
||||
[
|
||||
:namespace_id,
|
||||
:issues,
|
||||
:namespace_id,
|
||||
:issue_id
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
0ab0371d24515c2fcb804e47fa7526edfb681204fed2137db4e9498f4b7fe116
|
||||
|
|
@ -0,0 +1 @@
|
|||
a3632c565094b3a1672ff9c5c3f7ec04b993555ad3d1eb633b1b033c9741729f
|
||||
|
|
@ -0,0 +1 @@
|
|||
c2ce13c3186404aec3981a847dd971c3a3a9be1cedd7aad0554821e83c8fe1b5
|
||||
|
|
@ -0,0 +1 @@
|
|||
f27f4eacc953b45d95559b141ec42e6a312c44c9496a8283dc61dcb2443ddc47
|
||||
|
|
@ -0,0 +1 @@
|
|||
98452a102d7cafe648ad028a41f2d71a42a9bd429b1df7356f66cd78bad370ed
|
||||
|
|
@ -2721,6 +2721,22 @@ RETURN NEW;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_dfad97659d5f() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW."namespace_id" IS NULL THEN
|
||||
SELECT "namespace_id"
|
||||
INTO NEW."namespace_id"
|
||||
FROM "issues"
|
||||
WHERE "issues"."id" = NEW."issue_id";
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_e0864d1cff37() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
|
@ -13514,7 +13530,8 @@ ALTER SEQUENCE issuable_resource_links_id_seq OWNED BY issuable_resource_links.i
|
|||
CREATE TABLE issuable_severities (
|
||||
id bigint NOT NULL,
|
||||
issue_id bigint NOT NULL,
|
||||
severity smallint DEFAULT 0 NOT NULL
|
||||
severity smallint DEFAULT 0 NOT NULL,
|
||||
namespace_id bigint
|
||||
);
|
||||
|
||||
CREATE SEQUENCE issuable_severities_id_seq
|
||||
|
|
@ -30616,6 +30633,8 @@ CREATE INDEX index_issuable_resource_links_on_issue_id ON issuable_resource_link
|
|||
|
||||
CREATE UNIQUE INDEX index_issuable_severities_on_issue_id ON issuable_severities USING btree (issue_id);
|
||||
|
||||
CREATE INDEX index_issuable_severities_on_namespace_id ON issuable_severities USING btree (namespace_id);
|
||||
|
||||
CREATE INDEX index_issuable_slas_on_due_at_id_label_applied_issuable_closed ON issuable_slas USING btree (due_at, id) WHERE ((label_applied = false) AND (issuable_closed = false));
|
||||
|
||||
CREATE UNIQUE INDEX index_issuable_slas_on_issue_id ON issuable_slas USING btree (issue_id);
|
||||
|
|
@ -35428,6 +35447,8 @@ CREATE TRIGGER trigger_dc13168b8025 BEFORE INSERT OR UPDATE ON vulnerability_fla
|
|||
|
||||
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
|
||||
|
||||
CREATE TRIGGER trigger_dfad97659d5f BEFORE INSERT OR UPDATE ON issuable_severities FOR EACH ROW EXECUTE FUNCTION trigger_dfad97659d5f();
|
||||
|
||||
CREATE TRIGGER trigger_e0864d1cff37 BEFORE INSERT OR UPDATE ON packages_debian_group_architectures FOR EACH ROW EXECUTE FUNCTION trigger_e0864d1cff37();
|
||||
|
||||
CREATE TRIGGER trigger_e1da4a738230 BEFORE INSERT OR UPDATE ON vulnerability_external_issue_links FOR EACH ROW EXECUTE FUNCTION trigger_e1da4a738230();
|
||||
|
|
@ -37127,6 +37148,9 @@ ALTER TABLE ONLY protected_tag_create_access_levels
|
|||
ALTER TABLE ONLY application_settings
|
||||
ADD CONSTRAINT fk_f9867b3540 FOREIGN KEY (web_ide_oauth_application_id) REFERENCES oauth_applications(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY issuable_severities
|
||||
ADD CONSTRAINT fk_f9df19ecb6 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE p_ci_stages
|
||||
ADD CONSTRAINT fk_fb57e6cc56_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ DETAILS:
|
|||
> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
|
||||
> - `product_analytics_dashboards` [enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/398653) by default in GitLab 16.11.
|
||||
> - Feature flag `product_analytics_dashboards` [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/454059) in GitLab 17.1.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167296) to beta and feature flag `product_analytics_features` added in GitLab 17.5.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167296) to beta in GitLab 17.5 [with a flag](../administration/feature_flags.md) named `product_analytics_features`.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ info: Any user with at least the Maintainer role can merge updates to this conte
|
|||
# Metrics Dictionary Guide
|
||||
|
||||
[Service Ping](../service_ping/index.md) metrics are defined in individual YAML files definitions from which the
|
||||
[Metrics Dictionary](https://metrics.gitlab.com/) is built. Currently, the metrics dictionary is built automatically once a day. When a change to a metric is made in a YAML file, you can see the change in the dictionary within 24 hours.
|
||||
[Metrics Dictionary](https://metrics.gitlab.com/) is built. Currently, the metrics dictionary is built automatically once an hour.
|
||||
|
||||
- When a change to a metric is made in a YAML file, you can see the change in the dictionary within 1 hour of the change getting deployed to production.
|
||||
- When a change to an event is made in a YAML file, you can see the change in the dictionary within 1 hour of the change getting merged to the master branch.
|
||||
|
||||
This guide describes the dictionary and how it's implemented.
|
||||
|
||||
## Metrics Definition and validation
|
||||
|
|
@ -29,7 +33,7 @@ All metrics are stored in YAML files:
|
|||
WARNING:
|
||||
Only metrics with a metric definition YAML and whose status is not `removed` are added to the Service Ping JSON payload.
|
||||
|
||||
Each metric is defined in a separate YAML file consisting of a number of fields:
|
||||
Each metric is defined in a YAML file consisting of a number of fields:
|
||||
|
||||
| Field | Required | Additional information |
|
||||
|------------------------------|----------|------------------------|
|
||||
|
|
@ -38,7 +42,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields:
|
|||
| `product_group` | yes | The [group](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) that owns the metric. |
|
||||
| `value_type` | yes | `string`; one of [`string`, `number`, `boolean`, `object`](https://json-schema.org/understanding-json-schema/reference/type). |
|
||||
| `status` | yes | `string`; [status](#metric-statuses) of the metric, may be set to `active`, `removed`, `broken`. |
|
||||
| `time_frame` | yes | `string`; may be set to a value like `7d`, `28d`, `all`, `none`. |
|
||||
| `time_frame` | yes | `string` or `array`; may be set to `7d`, `28d`, `all`, `none` or an array including any of these values except for `none`. |
|
||||
| `data_source` | yes | `string`; may be set to a value like `database`, `redis`, `redis_hll`, `prometheus`, `system`, `license`, `internal_events`. |
|
||||
| `data_category` | yes | `string`; [categories](#data-category) of the metric, may be set to `operational`, `optional`, `subscription`, `standard`. The default value is `optional`. |
|
||||
| `instrumentation_class` | no | `string`; used for metrics with `data_source` other than `internal_events`. See [the class that implements the metric](metrics_instrumentation.md). |
|
||||
|
|
@ -59,12 +63,15 @@ The `key_path` of the metric is the location in the JSON Service Ping payload.
|
|||
|
||||
The `key_path` could be composed from multiple parts separated by `.` and it must be unique.
|
||||
|
||||
We recommend to add the metric in one of the top-level keys:
|
||||
If a metric definition has an array `time_frame`, the `key_path` defined in the YAML file will have a suffix automatically added for each of the included time frames:
|
||||
|
||||
- `settings`: for settings related metrics.
|
||||
- `counts_weekly`: for counters that have data for the most recent 7 days.
|
||||
- `counts_monthly`: for counters that have data for the most recent 28 days.
|
||||
- `counts`: for counters that have data for all time.
|
||||
| time_frame | `key_path` suffix|
|
||||
|------------|------------------|
|
||||
| `all` | no suffix |
|
||||
| `7d` | `_weekly` |
|
||||
| `28d` | `_monthly` |
|
||||
|
||||
The `key_path`s shown in the [Metrics Dictionary](https://metrics.gitlab.com/) include those suffixes.
|
||||
|
||||
### Metric statuses
|
||||
|
||||
|
|
@ -88,7 +95,7 @@ Metric definitions can have one of the following values for `value_type`:
|
|||
|
||||
### Metric `time_frame`
|
||||
|
||||
A metric's time frame is calculated based on the `time_frame` field and the `data_source` of the metric.
|
||||
A metric's time frame is calculated based on the `time_frame` field and the `data_source` of the metric. When `time_frame` is an array, the metric's values are calculated for each of the included time frames.
|
||||
|
||||
| data_source | time_frame | Description |
|
||||
|------------------------|------------|-------------------------------------------------|
|
||||
|
|
|
|||
|
|
@ -1004,7 +1004,10 @@ _KaTeX only supports a [subset](https://katex.org/docs/supported.html) of LaTeX.
|
|||
This syntax also works in AsciiDoc wikis and files using `:stem: latexmath`. For details, see
|
||||
the [Asciidoctor user manual](https://asciidoctor.org/docs/user-manual/#activating-stem-support).
|
||||
|
||||
To prevent malicious activity, GitLab renders only the first 50 inline math instances.
|
||||
To prevent malicious activity, GitLab renders only the first 50 inline math instances.
|
||||
You can disable this limit [for a group](../api/graphql/reference/index.md#mutationgroupupdate)
|
||||
or for the entire [self-managed instance](../administration/instance_limits.md#math-rendering-limits).
|
||||
|
||||
The number of math blocks is also limited based on render time. If the limit is exceeded,
|
||||
GitLab renders the excess math instances as text. Wiki and repository files do not have
|
||||
these limits.
|
||||
|
|
|
|||
|
|
@ -138,12 +138,14 @@ have weight assigned, because issues with no weight don't show on the chart.
|
|||
|
||||
## Roll up weights
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381879) in GitLab 16.11 [with a flag](../../../administration/feature_flags.md) named `rollup_timebox_chart`. Disabled by default.
|
||||
DETAILS:
|
||||
**Offering:** Self-managed
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381879) in GitLab 17.1 [with a flag](../../../administration/feature_flags.md) named `rollup_timebox_chart`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `rollup_timebox_chart`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is not available.
|
||||
This feature is not ready for production use.
|
||||
On self-managed GitLab, by default this feature is not available. For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
With [tasks](../../tasks.md), a more granular planning is possible.
|
||||
If this feature is enabled, the weight of issues that have tasks is derived from the tasks in the
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ module API
|
|||
values: %w[running success failed canceled]
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
route_setting :authorization, job_token_policies: [:admin_deployments, :admin_environments]
|
||||
post ':id/deployments' do
|
||||
authorize!(:create_deployment, user_project)
|
||||
authorize!(:create_environment, user_project)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ module API
|
|||
mutually_exclusive :name, :search, message: 'cannot be used together'
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
route_setting :authorization, job_token_policy: :read_environments
|
||||
route_setting :authorization, job_token_policies: :read_environments
|
||||
get ':id/environments' do
|
||||
authorize! :read_environment, user_project
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ module API
|
|||
return handle_job_token_failure!(project)
|
||||
end
|
||||
|
||||
return forbidden!(job_token_policy_unauthorized_message(project)) unless job_token_policy_authorized?(project)
|
||||
return forbidden!(job_token_policies_unauthorized_message(project)) unless job_token_policies_authorized?(project)
|
||||
|
||||
if project_moved?(id, project)
|
||||
return not_allowed!('Non GET methods are not allowed for moved projects') unless request.get?
|
||||
|
|
@ -1011,27 +1011,33 @@ module API
|
|||
end
|
||||
end
|
||||
|
||||
def job_token_policy_authorized?(project)
|
||||
def job_token_policies_authorized?(project)
|
||||
return true unless current_user&.from_ci_job_token?
|
||||
return true unless Feature.enabled?(:enforce_job_token_policies, current_user)
|
||||
|
||||
current_user.ci_job_token_scope.policy_allowed?(project, job_token_policy)
|
||||
current_user.ci_job_token_scope.policies_allowed?(project, job_token_policies)
|
||||
end
|
||||
|
||||
def job_token_policy_unauthorized_message(project)
|
||||
policy = job_token_policy
|
||||
if policy.present?
|
||||
def job_token_policies_unauthorized_message(project)
|
||||
policies = job_token_policies
|
||||
case policies.size
|
||||
when 0
|
||||
'This action is unauthorized for CI/CD job tokens.'
|
||||
when 1
|
||||
format("Insufficient permissions to access this resource in project %{project}. " \
|
||||
"The following token permission is required: %{permission}.", project: project.path, permission: policy)
|
||||
"The following token permission is required: %{policy}.",
|
||||
project: project.path, policy: policies[0])
|
||||
else
|
||||
'This action is not authorized for CI/CD job tokens.'
|
||||
format("Insufficient permissions to access this resource in project %{project}. " \
|
||||
"The following token permissions are required: %{policies}.",
|
||||
project: project.path, policies: policies.to_sentence)
|
||||
end
|
||||
end
|
||||
|
||||
def job_token_policy
|
||||
return unless respond_to?(:route_setting)
|
||||
def job_token_policies
|
||||
return [] unless respond_to?(:route_setting)
|
||||
|
||||
route_setting(:authorization).try(:fetch, :job_token_policy, nil)
|
||||
Array(route_setting(:authorization).try(:fetch, :job_token_policies, nil))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ module Authn
|
|||
payload, _header = ::JSONWebToken::RSAToken.decode(token, signing_public_key)
|
||||
|
||||
new(payload: payload, subject_type: subject_type)
|
||||
rescue JWT::DecodeError, Gitlab::Graphql::Errors::ArgumentError
|
||||
rescue JWT::DecodeError
|
||||
# The token received is not a JWT
|
||||
nil
|
||||
end
|
||||
|
|
@ -70,6 +70,9 @@ module Authn
|
|||
return unless payload
|
||||
|
||||
GitlabSchema.parse_gid(payload['sub'], expected_type: subject_type)&.find
|
||||
rescue Gitlab::Graphql::Errors::ArgumentError
|
||||
# The subject doesn't exist anymore
|
||||
nil
|
||||
end
|
||||
strong_memoize_attr :subject
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackfillIssuableSeveritiesNamespaceId < BackfillDesiredShardingKeyJob
|
||||
operation_name :backfill_issuable_severities_namespace_id
|
||||
feature_category :team_planning
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,8 +6,8 @@ gemspec
|
|||
|
||||
group :test do
|
||||
gem "climate_control", "~> 1.2.0"
|
||||
gem "gitlab-styles", "~> 13.0.1"
|
||||
gem "pry", "~> 0.14.2"
|
||||
gem "gitlab-styles", "~> 13.0.2"
|
||||
gem "pry", "~> 0.15.0"
|
||||
gem "rspec", "~> 3.13"
|
||||
gem "simplecov", "~> 0.22.0"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ GEM
|
|||
diff-lcs (1.5.1)
|
||||
docile (1.4.0)
|
||||
drb (2.2.1)
|
||||
gitlab-styles (13.0.1)
|
||||
rubocop (~> 1.67.0)
|
||||
gitlab-styles (13.0.2)
|
||||
rubocop (~> 1.68.0)
|
||||
rubocop-capybara (~> 2.21.0)
|
||||
rubocop-factory_bot (~> 2.26.1)
|
||||
rubocop-graphql (~> 1.5.4)
|
||||
|
|
@ -56,7 +56,7 @@ GEM
|
|||
racc
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pry (0.14.2)
|
||||
pry (0.15.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
racc (1.8.1)
|
||||
|
|
@ -77,7 +77,7 @@ GEM
|
|||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.67.0)
|
||||
rubocop (1.68.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
|
|
@ -141,8 +141,8 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
climate_control (~> 1.2.0)
|
||||
gitlab-cng!
|
||||
gitlab-styles (~> 13.0.1)
|
||||
pry (~> 0.14.2)
|
||||
gitlab-styles (~> 13.0.2)
|
||||
pry (~> 0.15.0)
|
||||
rspec (~> 3.13)
|
||||
simplecov (~> 0.22.0)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ module QA
|
|||
element 'blob-viewer-file-content'
|
||||
end
|
||||
|
||||
base.view 'app/assets/javascripts/vue_shared/components/code_dropdown/snippet_code_dropdown.vue' do
|
||||
base.view 'app/assets/javascripts/vue_shared/components/code_dropdown/clone_code_dropdown.vue' do
|
||||
element 'copy-http-url'
|
||||
element 'copy-ssh-url'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ require_relative './cli/flows/flow_advisor'
|
|||
require_relative './cli/flows/metric_definer'
|
||||
require_relative './cli/flows/usage_viewer'
|
||||
require_relative './cli/global_state'
|
||||
require_relative './cli/time_framed_key_path'
|
||||
require_relative './cli/metric'
|
||||
require_relative './cli/event'
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ module InternalEventsCli
|
|||
per_page: 20,
|
||||
&disabled_format_callback
|
||||
)
|
||||
@metrics = reduce_metrics_by_time_frame(@metrics)
|
||||
|
||||
assign_shared_attrs(:actions, :milestone) do
|
||||
{
|
||||
|
|
@ -221,10 +222,23 @@ module InternalEventsCli
|
|||
def file_saved_context_message(attributes)
|
||||
format_prefix " ", <<~TEXT.chomp
|
||||
- Visit #{format_info('https://metrics.gitlab.com')} to find dashboard links for this metric
|
||||
- Metric trend dashboard: #{format_info(metric_trend_path(attributes['key_path']))}
|
||||
#{metric_dashboard_links(attributes)}
|
||||
TEXT
|
||||
end
|
||||
|
||||
def metric_dashboard_links(attributes)
|
||||
time_frames = attributes['time_frame']
|
||||
unless time_frames.is_a?(Array)
|
||||
return "- Metric trend dashboard: #{format_info(metric_trend_path(attributes['key_path']))}"
|
||||
end
|
||||
|
||||
dashboards = time_frames.map do |time_frame|
|
||||
key_path = TimeFramedKeyPath.build(attributes['key_path'], time_frame)
|
||||
" - #{format_info(metric_trend_path(key_path))}"
|
||||
end
|
||||
["- Metric trend dashboards:", *dashboards].join("\n")
|
||||
end
|
||||
|
||||
# Check existing event files for attributes to copy over
|
||||
def prompt_for_copying_event_properties
|
||||
shared_values = collect_values_for_shared_event_properties
|
||||
|
|
@ -389,6 +403,19 @@ module InternalEventsCli
|
|||
end
|
||||
end
|
||||
|
||||
def reduce_metrics_by_time_frame(metrics)
|
||||
# MetricOptions class returns one metric per time_frame value,
|
||||
# here we merge them into a singular metric including all the time_frame values
|
||||
return metrics unless metrics.length > 1
|
||||
|
||||
time_frames = metrics.map do |metric|
|
||||
metric.time_frame.value
|
||||
end
|
||||
|
||||
attributes = metrics.first.to_h.merge(time_frame: time_frames)
|
||||
[Metric.new(**attributes)]
|
||||
end
|
||||
|
||||
# Helper for #prompt_for_event_filters
|
||||
def print_event_filter_header(event, idx, total)
|
||||
cli.say "\n"
|
||||
|
|
|
|||
|
|
@ -12,11 +12,24 @@ module InternalEventsCli
|
|||
end
|
||||
|
||||
def metrics
|
||||
@metrics ||= load_definitions(
|
||||
Metric,
|
||||
InternalEventsCli::NEW_METRIC_FIELDS,
|
||||
all_metric_paths
|
||||
)
|
||||
@metrics ||= begin
|
||||
loaded_files = load_definitions(
|
||||
Metric,
|
||||
InternalEventsCli::NEW_METRIC_FIELDS,
|
||||
all_metric_paths
|
||||
)
|
||||
loaded_files.flat_map do |metric|
|
||||
# copy logic of Gitlab::Usage::MetricDefinition
|
||||
next metric unless metric.time_frame.is_a?(Array)
|
||||
|
||||
metric.time_frame.map do |time_frame|
|
||||
current_metric = metric.dup
|
||||
current_metric.time_frame = time_frame
|
||||
current_metric.key_path = TimeFramedKeyPath.build(current_metric.key_path, time_frame)
|
||||
current_metric
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reload_definitions
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ module InternalEventsCli
|
|||
|
||||
# ex) "Monthly/Weekly"
|
||||
def time_frame_phrase
|
||||
phrase = metrics.map { |metric| metric.time_frame.description }.join('/')
|
||||
phrase = metrics.map { |metric| metric.time_frame.description.capitalize }.join('/')
|
||||
|
||||
disabled ? phrase : format_info(phrase)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -159,12 +159,15 @@ module InternalEventsCli
|
|||
# Automatically prepended to all new descriptions
|
||||
# ex) Total count of
|
||||
# ex) Weekly/Monthly count of unique
|
||||
# ex) Count of
|
||||
def description_prefix
|
||||
[
|
||||
description_components = [
|
||||
time_frame.description,
|
||||
identifier.prefix,
|
||||
*(identifier.plural if identifier.default?)
|
||||
].join(' ')
|
||||
].compact
|
||||
|
||||
description_components.join(' ').capitalize
|
||||
end
|
||||
|
||||
# Provides simplified but technically accurate description
|
||||
|
|
@ -172,8 +175,10 @@ module InternalEventsCli
|
|||
def technical_description
|
||||
event_name = actions.first if events.length == 1 && !filtered?
|
||||
event_name ||= 'the selected events'
|
||||
|
||||
"#{time_frame.description} #{identifier.description % event_name}"
|
||||
[
|
||||
time_frame.description,
|
||||
(identifier.description % event_name).to_s
|
||||
].compact.join(' ').capitalize
|
||||
end
|
||||
|
||||
def bulk_assign(key_value_pairs)
|
||||
|
|
@ -185,21 +190,25 @@ module InternalEventsCli
|
|||
TimeFrame = Struct.new(:value) do
|
||||
def description
|
||||
case value
|
||||
when Array
|
||||
nil # array time_frame metrics have no description prefix
|
||||
when '7d'
|
||||
'Weekly'
|
||||
'weekly'
|
||||
when '28d'
|
||||
'Monthly'
|
||||
'monthly'
|
||||
when 'all'
|
||||
'Total'
|
||||
'total'
|
||||
end
|
||||
end
|
||||
|
||||
def directory_name
|
||||
return "counts_all" if value.is_a? Array
|
||||
|
||||
"counts_#{value}"
|
||||
end
|
||||
|
||||
def key_path
|
||||
description&.downcase if value != 'all'
|
||||
description&.downcase if %w[7d 28d].include?(value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Helpers for shared & state across all CLI flows
|
||||
module InternalEventsCli
|
||||
class TimeFramedKeyPath
|
||||
METRIC_TIME_FRAME_SUFFIX = {
|
||||
'7d' => '_weekly',
|
||||
'28d' => '_monthly',
|
||||
'all' => ''
|
||||
}.freeze
|
||||
|
||||
def self.build(base_key_path, time_frame)
|
||||
# copy logic of Gitlab::Usage::MetricDefinition
|
||||
"#{base_key_path}#{METRIC_TIME_FRAME_SUFFIX[time_frame]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -121,10 +121,8 @@
|
|||
files:
|
||||
- path: config/events/random_name.yml
|
||||
content: spec/fixtures/scripts/internal_events/events/keyboard_smashed_event.yml
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_random_name_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_28d.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_random_name_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_7d.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_random_name.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric.yml
|
||||
|
||||
- description: Creates an event after helping the user figure out next steps
|
||||
inputs:
|
||||
|
|
|
|||
|
|
@ -10,17 +10,13 @@
|
|||
- "\n" # Select: config/events/internal_events_cli_used.yml
|
||||
- "\n" # Select: Weekly count of unique users
|
||||
- "who defined an internal event using the CLI\n" # Input description
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event.yml
|
||||
|
||||
- description: Create a weekly/monthly metric for a multiple events, but select only one event
|
||||
inputs:
|
||||
|
|
@ -35,17 +31,13 @@
|
|||
- "\n" # Submit selections
|
||||
- "\n" # Select: Weekly count of unique projects
|
||||
- "who defined an internal event using the CLI\n" # Input description
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event.yml
|
||||
|
||||
- description: Create a weekly/monthly metric for multiple events
|
||||
inputs:
|
||||
|
|
@ -65,17 +57,13 @@
|
|||
- "\e[B" # Arrow down to: Weekly count of unique projects
|
||||
- "\n" # Select: Weekly count of unique projects
|
||||
- "where a defition file was created with the CLI\n" # Input description
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "1\n" # Select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml
|
||||
- path: config/metrics/counts_all/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/project_id_multiple_events.yml
|
||||
|
||||
- description: Create an all time total metric for a single event
|
||||
inputs:
|
||||
|
|
@ -129,6 +117,33 @@
|
|||
- path: ee/config/metrics/counts_all/count_total_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml
|
||||
|
||||
- description: Create a weekly metric for a single event that already has a monthly metric
|
||||
inputs:
|
||||
files:
|
||||
- path: config/events/internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
|
||||
- path: config/metrics/counts_28d/count_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/ee_total_28d_single_event.yml
|
||||
keystrokes:
|
||||
- "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
|
||||
- "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
|
||||
- 'internal_events_cli_used' # Filters to this event
|
||||
- "\n" # Select: config/events/internal_events_cli_used.yml
|
||||
- "\e[A\e[A" # Arrow up to: Monthly/Weekly count of events
|
||||
- "\n" # Select: Monthly/Weekly count of events
|
||||
- "when an event was defined using the CLI\n" # Input description
|
||||
- "2\n" # Enum-select: Modify attributes
|
||||
- "\n" # Accept group from event definition
|
||||
- "\n" # Accept product categories from event definition
|
||||
- "\n" # Accept URL from event definition
|
||||
- "2\n" # Override tier -> Select: [premium, ultimate]
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: ee/config/metrics/counts_7d/count_total_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml
|
||||
|
||||
- description: Create a metric after helping the user figure out next steps
|
||||
inputs:
|
||||
files:
|
||||
|
|
@ -153,6 +168,7 @@
|
|||
files:
|
||||
- path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
|
||||
|
||||
- description: Create a metric and then another metric with copied events
|
||||
inputs:
|
||||
files:
|
||||
|
|
@ -165,10 +181,8 @@
|
|||
- "\n" # Select: config/events/internal_events_cli_used.yml
|
||||
- "\n" # Select: Weekly count of unique users
|
||||
- "who defined an internal event using the CLI\n" # Input description
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "2\n" # Create another metric with the same events
|
||||
- "\e[A" # Arrow up to: Total count of events
|
||||
- "\n" # Select: Total count of events
|
||||
|
|
@ -178,10 +192,8 @@
|
|||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event.yml
|
||||
- path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
|
||||
|
||||
|
|
@ -197,10 +209,8 @@
|
|||
- "\n" # Select: config/events/internal_events_cli_used.yml
|
||||
- "\n" # Select: Weekly count of unique users
|
||||
- "who defined an internal event using the CLI\n" # Input description
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "3\n" # Create another metric
|
||||
- "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
|
||||
- 'internal_events_cli_used' # Filters to this event
|
||||
|
|
@ -213,10 +223,8 @@
|
|||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event.yml
|
||||
- path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
|
||||
|
||||
|
|
@ -232,10 +240,8 @@
|
|||
- "\n" # Select: config/events/internal_events_cli_used.yml
|
||||
- "\n" # Select: Weekly count of unique users
|
||||
- "who defined an internal event using the CLI\n" # Input description
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "2\n" # Create another metric
|
||||
- "\e[A" # Arrow up to: Total count of events
|
||||
- "\n" # Select: Total count of events
|
||||
|
|
@ -256,10 +262,8 @@
|
|||
- "4\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event.yml
|
||||
- path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
|
||||
- path: ee/config/events/internal_events_cli_opened.yml
|
||||
|
|
@ -324,18 +328,13 @@
|
|||
- "\n" # Skip "Add extra property" filter
|
||||
- "who tried and failed to define an internal event using the CLI\n" # Input description
|
||||
- "failed_usage_attempts\n" # Input metric key path
|
||||
- "\n" # Submit monthly description for weekly
|
||||
- "\n" # Submit monthly name for weekly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_failed_usage_attempts_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event_additional_props.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_failed_usage_attempts_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event_additional_props.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_failed_usage_attempts.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event_additional_props.yml
|
||||
|
||||
- description: Create a weekly/monthly metric for a single event with all additional properties
|
||||
inputs:
|
||||
|
|
@ -355,18 +354,13 @@
|
|||
- "60\n" # Input valid "value"
|
||||
- "who tried and failed to define an internal event using the CLI\n" # Input description
|
||||
- "failed_usage_attempts_under_60s\n" # Input metric key path
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "\n" # Submit weekly name for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_failed_usage_attempts_under_60s_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event_all_additional_props.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_failed_usage_attempts_under_60s_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event_all_additional_props.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_failed_usage_attempts_under_60s.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event_all_additional_props.yml
|
||||
|
||||
- description: Create a weekly/monthly metric for a single event with custom additional properties filters
|
||||
inputs:
|
||||
|
|
@ -388,18 +382,13 @@
|
|||
- "12.4\n" # Input value for "custom_key4" filter
|
||||
- "who tried and failed to define an internal event using the CLI\n" # Input description
|
||||
- "failed_usage_attempts_under_60s\n" # Input metric key path
|
||||
- "\n" # Submit weekly description for monthly
|
||||
- "\n" # Submit weekly name for monthly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_user_id_from_failed_usage_attempts_under_60s_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event_custom_key_filter.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_user_id_from_failed_usage_attempts_under_60s_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event_custom_key_filter.yml
|
||||
- path: config/metrics/counts_all/count_distinct_user_id_from_failed_usage_attempts_under_60s.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/user_id_single_event_custom_key_filter.yml
|
||||
|
||||
- description: Create a weekly/monthly metric for multiple events with and without additional properties
|
||||
inputs:
|
||||
|
|
@ -479,17 +468,13 @@
|
|||
- "\e[A\e[A\e[A\e[A" # Arrow up to: Weekly count of unique values for label
|
||||
- "\n" # Select: Weekly count of unique values for label
|
||||
- "values provided for label\n" # Input description
|
||||
- "\n" # Submit monthly description for weekly
|
||||
- "1\n" # Enum-select: Copy & continue
|
||||
- "y\n" # Create file
|
||||
- "y\n" # Create file
|
||||
- "5\n" # Exit
|
||||
outputs:
|
||||
files:
|
||||
- path: config/metrics/counts_28d/count_distinct_label_from_internal_events_cli_used_monthly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/label_28d_single_event_additional_props.yml
|
||||
- path: config/metrics/counts_7d/count_distinct_label_from_internal_events_cli_used_weekly.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/label_7d_single_event_additional_props.yml
|
||||
- path: config/metrics/counts_all/count_distinct_label_from_internal_events_cli_used.yml
|
||||
content: spec/fixtures/scripts/internal_events/metrics/label_single_event_additional_props.yml
|
||||
|
||||
- description: Creates metrics with explicitly no product category
|
||||
inputs:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_random_name_weekly
|
||||
description: Weekly count of unique users random metric string
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_random_name
|
||||
description: Count of unique users random metric string
|
||||
product_group: import_and_integrate
|
||||
product_categories:
|
||||
- acquisition
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 7d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_random_name_monthly
|
||||
description: Monthly count of unique users random metric string
|
||||
product_group: import_and_integrate
|
||||
product_categories:
|
||||
- acquisition
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: random_name
|
||||
unique: user.id
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_label_from_internal_events_cli_used_monthly
|
||||
description: Monthly count of unique values provided for label
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: internal_events_cli_used
|
||||
unique: label
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_label_from_internal_events_cli_used_weekly
|
||||
description: Weekly count of unique values provided for label
|
||||
key_path: redis_hll_counters.count_distinct_label_from_internal_events_cli_used
|
||||
description: Count of unique values provided for label
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 7d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly
|
||||
description: Monthly count of unique projects where a defition file was created with the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: internal_events_cli_closed
|
||||
unique: project.id
|
||||
- name: internal_events_cli_used
|
||||
unique: project.id
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly
|
||||
description: Weekly count of unique projects where a defition file was created with the CLI
|
||||
key_path: redis_hll_counters.count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used
|
||||
description: Count of unique projects where a defition file was created with the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 7d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_monthly
|
||||
description: Monthly count of unique users who tried and failed to define an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: failure
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: incomplete
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_under_60s_monthly
|
||||
description: Monthly count of unique users who tried and failed to define an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: failure
|
||||
property: metrics
|
||||
value: 60
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: failure
|
||||
property: events
|
||||
value: 60
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: incomplete
|
||||
property: metrics
|
||||
value: 60
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: incomplete
|
||||
property: events
|
||||
value: 60
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_under_60s_monthly
|
||||
description: Monthly count of unique users who tried and failed to define an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: failure
|
||||
custom_key1: metrics
|
||||
custom_key3: 30
|
||||
custom_key4: 12.4
|
||||
- name: internal_events_cli_used
|
||||
unique: user.id
|
||||
filter:
|
||||
label: failure
|
||||
custom_key1: metrics
|
||||
custom_key3: 13
|
||||
custom_key4: 12.4
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_monthly
|
||||
description: Monthly count of unique users who defined an internal event using the CLI
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used
|
||||
description: Count of unique users who defined an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 28d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_weekly
|
||||
description: Weekly count of unique users who tried and failed to define an internal event using the CLI
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts
|
||||
description: Count of unique users who tried and failed to define an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 7d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_under_60s_weekly
|
||||
description: Weekly count of unique users who tried and failed to define an internal event using the CLI
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_under_60s
|
||||
description: Count of unique users who tried and failed to define an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 7d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_under_60s_weekly
|
||||
description: Weekly count of unique users who tried and failed to define an internal event using the CLI
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_failed_usage_attempts_under_60s
|
||||
description: Count of unique users who tried and failed to define an internal event using the CLI
|
||||
product_group: analytics_instrumentation
|
||||
product_categories:
|
||||
- service_ping
|
||||
|
|
@ -9,7 +9,9 @@ value_type: number
|
|||
status: active
|
||||
milestone: '16.6'
|
||||
introduced_by_url: TODO
|
||||
time_frame: 7d
|
||||
time_frame:
|
||||
- 28d
|
||||
- 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
|
|
@ -81,6 +81,7 @@ describe('Protected Tag Edit', () => {
|
|||
{ user_id: 1, id: 1 },
|
||||
{ group_id: 1, id: 2 },
|
||||
{ access_level: 3, id: 3 },
|
||||
{ deploy_key_id: 1, id: 4 },
|
||||
];
|
||||
mockAxios.onPatch().replyOnce(HTTP_STATUS_OK, { [ACCESS_LEVELS.CREATE]: updatedPermissions });
|
||||
findAccessDropdown().vm.$emit('hidden', [{ user_id: 1 }]);
|
||||
|
|
@ -92,6 +93,7 @@ describe('Protected Tag Edit', () => {
|
|||
{ user_id: 1, id: 1, type: LEVEL_TYPES.USER },
|
||||
{ group_id: 1, id: 2, type: LEVEL_TYPES.GROUP },
|
||||
{ access_level: 3, id: 3, type: LEVEL_TYPES.ROLE },
|
||||
{ deploy_key_id: 1, id: 4, type: LEVEL_TYPES.DEPLOY_KEY },
|
||||
];
|
||||
expect(findAccessDropdown().props('preselectedItems')).toEqual(newPreselected);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import HeaderArea from '~/repository/components/header_area.vue';
|
||||
import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
|
||||
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
|
||||
import SourceCodeDownloadDropdown from '~/vue_shared/components/download_dropdown/download_dropdown.vue';
|
||||
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
|
||||
import BlobControls from '~/repository/components/header_area/blob_controls.vue';
|
||||
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
|
||||
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
|
||||
|
|
@ -36,6 +39,33 @@ const defaultProvided = {
|
|||
projectRootPath: '/project/root/path',
|
||||
comparePath: undefined,
|
||||
isReadmeView: false,
|
||||
isFork: false,
|
||||
needsToFork: true,
|
||||
gitpodEnabled: false,
|
||||
isBlob: true,
|
||||
showEditButton: true,
|
||||
showWebIdeButton: true,
|
||||
showGitpodButton: false,
|
||||
showPipelineEditorUrl: true,
|
||||
webIdeUrl: 'https://gitlab.com/project/-/ide/',
|
||||
editUrl: 'https://gitlab.com/project/-/edit/main/',
|
||||
pipelineEditorUrl: 'https://gitlab.com/project/-/ci/editor',
|
||||
gitpodUrl: 'https://gitpod.io/#https://gitlab.com/project',
|
||||
userPreferencesGitpodPath: '/profile/preferences#gitpod',
|
||||
userProfileEnableGitpodPath: '/profile/preferences?enable_gitpod=true',
|
||||
httpUrl: 'https://gitlab.com/example-group/example-project.git',
|
||||
xcodeUrl: 'xcode://clone?repo=https://gitlab.com/example-group/example-project.git',
|
||||
sshUrl: 'git@gitlab.com:example-group/example-project.git',
|
||||
kerberosUrl: 'https://kerberos@gitlab.com/example-group/example-project.git',
|
||||
downloadLinks: [
|
||||
'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.zip',
|
||||
'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.gz',
|
||||
'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.bz2',
|
||||
'https://gitlab.com/example-group/example-project/-/releases',
|
||||
],
|
||||
downloadArtifacts: [
|
||||
'https://gitlab.com/example-group/example-project/-/jobs/artifacts/main/download?job=build',
|
||||
],
|
||||
};
|
||||
|
||||
describe('HeaderArea', () => {
|
||||
|
|
@ -45,6 +75,11 @@ describe('HeaderArea', () => {
|
|||
const findRefSelector = () => wrapper.findComponent(RefSelector);
|
||||
const findFindFileButton = () => wrapper.findByTestId('tree-find-file-control');
|
||||
const findCompareButton = () => wrapper.findByTestId('tree-compare-control');
|
||||
const findWebIdeButton = () => wrapper.findByTestId('js-tree-web-ide-link');
|
||||
const findCodeDropdown = () => wrapper.findComponent(CodeDropdown);
|
||||
const findSourceCodeDownloadDropdown = () => wrapper.findComponent(SourceCodeDownloadDropdown);
|
||||
const findCloneCodeDropdown = () => wrapper.findComponent(CloneCodeDropdown);
|
||||
|
||||
const { bindInternalEventDocument } = useMockInternalEventsTracking();
|
||||
|
||||
const createComponent = (props = {}, routeName = 'blobPathDecoded', provided = {}) => {
|
||||
|
|
@ -126,6 +161,35 @@ describe('HeaderArea', () => {
|
|||
expect(findCompareButton().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit button', () => {
|
||||
it('renders WebIdeLink component', () => {
|
||||
expect(findWebIdeButton().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodeDropdown', () => {
|
||||
it('renders CodeDropdown component with correct props for desktop layout', () => {
|
||||
expect(findCodeDropdown().exists()).toBe(true);
|
||||
expect(findCodeDropdown().props('sshUrl')).toBe(defaultProvided.sshUrl);
|
||||
expect(findCodeDropdown().props('httpUrl')).toBe(defaultProvided.httpUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SourceCodeDownloadDropdown', () => {
|
||||
it('renders SourceCodeDownloadDropdown and CloneCodeDropdown component with correct props for mobile layout', () => {
|
||||
expect(findSourceCodeDownloadDropdown().exists()).toBe(true);
|
||||
expect(findSourceCodeDownloadDropdown().props('downloadLinks')).toEqual(
|
||||
defaultProvided.downloadLinks,
|
||||
);
|
||||
expect(findSourceCodeDownloadDropdown().props('downloadArtifacts')).toEqual(
|
||||
defaultProvided.downloadArtifacts,
|
||||
);
|
||||
expect(findCloneCodeDropdown().exists()).toBe(true);
|
||||
expect(findCloneCodeDropdown().props('sshUrl')).toBe(defaultProvided.sshUrl);
|
||||
expect(findCloneCodeDropdown().props('httpUrl')).toBe(defaultProvided.httpUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered for blob view', () => {
|
||||
|
|
@ -136,6 +200,11 @@ describe('HeaderArea', () => {
|
|||
expect(blobControls.props('projectPath')).toBe('test/project');
|
||||
expect(blobControls.props('refType')).toBe('');
|
||||
});
|
||||
|
||||
it('does not render CodeDropdown and SourceCodeDownloadDropdown', () => {
|
||||
expect(findCodeDropdown().exists()).toBe(false);
|
||||
expect(findSourceCodeDownloadDropdown().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when isReadmeView is true', () => {
|
||||
|
|
@ -147,5 +216,10 @@ describe('HeaderArea', () => {
|
|||
expect(findRefSelector().exists()).toBe(false);
|
||||
expect(findBreadcrumbs().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render CodeDropdown and SourceCodeDownloadDropdown', () => {
|
||||
expect(findCodeDropdown().exists()).toBe(false);
|
||||
expect(findSourceCodeDownloadDropdown().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
|
||||
import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue';
|
||||
import SnippetCodeDropdown from '~/vue_shared/components/code_dropdown/snippet_code_dropdown.vue';
|
||||
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
|
||||
import ImportedBadge from '~/vue_shared/components/imported_badge.vue';
|
||||
import DeleteSnippetMutation from '~/snippets/mutations/delete_snippet.mutation.graphql';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
@ -86,7 +86,7 @@ describe('Snippet header component', () => {
|
|||
},
|
||||
},
|
||||
stubs: {
|
||||
SnippetCodeDropdown,
|
||||
CloneCodeDropdown,
|
||||
GlButton,
|
||||
GlDisclosureDropdown,
|
||||
GlDisclosureDropdownGroup,
|
||||
|
|
@ -113,7 +113,7 @@ describe('Snippet header component', () => {
|
|||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findTooltip = () => getBinding(findIcon().element, 'gl-tooltip');
|
||||
const findSpamIcon = () => wrapper.findByTestId('snippets-spam-icon');
|
||||
const findCodeDropdown = () => wrapper.findComponent(SnippetCodeDropdown);
|
||||
const findCodeDropdown = () => wrapper.findComponent(CloneCodeDropdown);
|
||||
const findImportedBadge = () => wrapper.findComponent(ImportedBadge);
|
||||
|
||||
const webUrl = 'http://foo.bar';
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { GlFormInputGroup } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import SnippetCodeDropdown from '~/vue_shared/components/code_dropdown/snippet_code_dropdown.vue';
|
||||
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
|
||||
import CodeDropdownItem from '~/vue_shared/components/code_dropdown/code_dropdown_item.vue';
|
||||
|
||||
describe('SnippetCodeDropdown', () => {
|
||||
describe('CloneCodeDropdown', () => {
|
||||
let wrapper;
|
||||
const sshLink = 'ssh://foo.bar';
|
||||
const httpLink = 'http://foo.bar';
|
||||
const httpsLink = 'https://foo.bar';
|
||||
const sshUrl = 'ssh://foo.bar';
|
||||
const httpUrl = 'http://foo.bar';
|
||||
const httpsUrl = 'https://foo.bar';
|
||||
const embedUrl = 'http://link.to.snippet';
|
||||
const embedCode = `<script src="${embedUrl}.js"></script>`;
|
||||
const defaultPropsData = {
|
||||
sshLink,
|
||||
httpLink,
|
||||
sshUrl,
|
||||
httpUrl,
|
||||
url: embedUrl,
|
||||
embeddable: true,
|
||||
};
|
||||
|
|
@ -24,13 +24,14 @@ describe('SnippetCodeDropdown', () => {
|
|||
const findCopyHttpUrlButton = () => wrapper.findComponentByTestId('copy-http-url');
|
||||
const findCopyEmbeddedCodeButton = () => wrapper.findComponentByTestId('copy-embedded-code');
|
||||
const findCopyShareUrlButton = () => wrapper.findComponentByTestId('copy-share-url');
|
||||
const findCopyKRB5UrlButton = () => wrapper.findComponentByTestId('copy-kerberos-url');
|
||||
|
||||
const createComponent = (propsData = defaultPropsData) => {
|
||||
wrapper = shallowMountExtended(SnippetCodeDropdown, {
|
||||
wrapper = shallowMountExtended(CloneCodeDropdown, {
|
||||
propsData,
|
||||
stubs: {
|
||||
GlFormInputGroup,
|
||||
SnippetCodeDropdown: stubComponent(SnippetCodeDropdown),
|
||||
CloneCodeDropdown: stubComponent(CloneCodeDropdown),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -38,8 +39,8 @@ describe('SnippetCodeDropdown', () => {
|
|||
describe('rendering', () => {
|
||||
it.each`
|
||||
name | index | link
|
||||
${'SSH'} | ${0} | ${sshLink}
|
||||
${'HTTP'} | ${1} | ${httpLink}
|
||||
${'SSH'} | ${0} | ${sshUrl}
|
||||
${'HTTP'} | ${1} | ${httpUrl}
|
||||
${'Embed'} | ${2} | ${embedCode}
|
||||
${'Share'} | ${3} | ${embedUrl}
|
||||
`('renders correct link and a copy-button for $name', ({ index, link }) => {
|
||||
|
|
@ -50,9 +51,9 @@ describe('SnippetCodeDropdown', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
name | finder | value
|
||||
${'sshLink'} | ${findCopySshUrlButton} | ${sshLink}
|
||||
${'httpLink'} | ${findCopyHttpUrlButton} | ${httpLink}
|
||||
name | finder | value
|
||||
${'sshUrl'} | ${findCopySshUrlButton} | ${sshUrl}
|
||||
${'httpUrl'} | ${findCopyHttpUrlButton} | ${httpUrl}
|
||||
`('does not fail if only $name is set', ({ name, finder, value }) => {
|
||||
createComponent({ [name]: value, url: embedCode });
|
||||
|
||||
|
|
@ -67,11 +68,30 @@ describe('SnippetCodeDropdown', () => {
|
|||
expect(findCopyEmbeddedCodeButton().exists()).toBe(false);
|
||||
expect(findCopyShareUrlButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render Embed and Share items, if url is not provided', () => {
|
||||
createComponent({ ...defaultPropsData, url: '' });
|
||||
|
||||
expect(findCopyEmbeddedCodeButton().exists()).toBe(false);
|
||||
expect(findCopyShareUrlButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render KRB5 link, if kerberosUrl is not provided', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findCopyKRB5UrlButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders KRB5 link, if kerberosUrl is provided', () => {
|
||||
createComponent({ ...defaultPropsData, kerberosUrl: 'http://:@gitlab.com/project-2.git' });
|
||||
|
||||
expect(findCopyKRB5UrlButton().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionality', () => {
|
||||
it('correctly calculates httpLabel for HTTPS protocol', () => {
|
||||
createComponent({ ...defaultPropsData, httpLink: httpsLink });
|
||||
createComponent({ ...defaultPropsData, httpUrl: httpsUrl });
|
||||
|
||||
expect(findCopyHttpUrlButton().props('label')).toContain('HTTPS');
|
||||
});
|
||||
|
|
@ -46,6 +46,8 @@ describe('autocompleteDataSources', () => {
|
|||
mergeRequests:
|
||||
'/foobar/project/group/-/autocomplete_sources/merge_requests?type=WorkItem&work_item_type_id=2',
|
||||
epics: '/foobar/project/group/-/autocomplete_sources/epics?type=WorkItem&work_item_type_id=2',
|
||||
milestones:
|
||||
'/foobar/project/group/-/autocomplete_sources/milestones?type=WorkItem&work_item_type_id=2',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -58,6 +60,7 @@ describe('autocompleteDataSources', () => {
|
|||
mergeRequests:
|
||||
'/foobar/project/group/-/autocomplete_sources/merge_requests?type=WorkItem&type_id=2',
|
||||
epics: '/foobar/project/group/-/autocomplete_sources/epics?type=WorkItem&type_id=2',
|
||||
milestones: '/foobar/project/group/-/autocomplete_sources/milestones?type=WorkItem&type_id=2',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -81,6 +84,8 @@ describe('autocompleteDataSources', () => {
|
|||
mergeRequests:
|
||||
'/foobar/groups/group/-/autocomplete_sources/merge_requests?type=WorkItem&work_item_type_id=2',
|
||||
epics: '/foobar/groups/group/-/autocomplete_sources/epics?type=WorkItem&work_item_type_id=2',
|
||||
milestones:
|
||||
'/foobar/groups/group/-/autocomplete_sources/milestones?type=WorkItem&work_item_type_id=2',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -99,6 +104,7 @@ describe('autocompleteDataSources', () => {
|
|||
mergeRequests:
|
||||
'/foobar/groups/group/-/autocomplete_sources/merge_requests?type=WorkItem&type_id=2',
|
||||
epics: '/foobar/groups/group/-/autocomplete_sources/epics?type=WorkItem&type_id=2',
|
||||
milestones: '/foobar/groups/group/-/autocomplete_sources/milestones?type=WorkItem&type_id=2',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ RSpec.describe API::Helpers, feature_category: :shared do
|
|||
)
|
||||
|
||||
allow(helper).to receive(:route_authentication_setting).and_return({})
|
||||
allow(helper).to receive(:route_setting).with(:authorization).and_return(job_token_policy: job_token_policy)
|
||||
allow(helper).to receive(:route_setting).with(:authorization).and_return(job_token_policies: job_token_policy)
|
||||
allow(user).to receive(:ci_job_token_scope).and_return(user.set_ci_job_token_scope!(job))
|
||||
end
|
||||
|
||||
|
|
@ -301,11 +301,24 @@ RSpec.describe API::Helpers, feature_category: :shared do
|
|||
expect(helper)
|
||||
.to receive(:forbidden!)
|
||||
.with("Insufficient permissions to access this resource in project #{project.path}. " \
|
||||
"The following token permission is required: #{job_token_policy}.")
|
||||
'The following token permission is required: not_allowed_policy.')
|
||||
|
||||
find_project!
|
||||
end
|
||||
|
||||
context 'when multiple policies are required' do
|
||||
let_it_be(:job_token_policy) { [:policy_1, :policy_2] }
|
||||
|
||||
it 'returns forbidden' do
|
||||
expect(helper)
|
||||
.to receive(:forbidden!)
|
||||
.with("Insufficient permissions to access this resource in project #{project.path}. " \
|
||||
'The following token permissions are required: policy_1 and policy_2.')
|
||||
|
||||
find_project!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the `enforce_job_token_policies` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_job_token_policies: false)
|
||||
|
|
@ -319,7 +332,7 @@ RSpec.describe API::Helpers, feature_category: :shared do
|
|||
let_it_be(:job_token_policy) { nil }
|
||||
|
||||
it 'returns forbidden' do
|
||||
expect(helper).to receive(:forbidden!).with('This action is not authorized for CI/CD job tokens.')
|
||||
expect(helper).to receive(:forbidden!).with('This action is unauthorized for CI/CD job tokens.')
|
||||
|
||||
find_project!
|
||||
end
|
||||
|
|
@ -332,6 +345,18 @@ RSpec.describe API::Helpers, feature_category: :shared do
|
|||
it { is_expected.to eq project }
|
||||
end
|
||||
end
|
||||
|
||||
context "when route settings don't exist" do
|
||||
before do
|
||||
allow(helper).to receive(:respond_to?).with(:route_setting).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns forbidden' do
|
||||
expect(helper).to receive(:forbidden!).with('This action is unauthorized for CI/CD job tokens.')
|
||||
|
||||
find_project!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -113,9 +113,7 @@ RSpec.describe Authn::Tokens::Jwt, feature_category: :system_access do
|
|||
context 'with wrong subject type' do
|
||||
let(:subject_type) { Project }
|
||||
|
||||
it 'raises a Gitlab::Graphql::Errors::ArgumentError' do
|
||||
expect { jwt_subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
|
||||
end
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillIssuableSeveritiesNamespaceId,
|
||||
feature_category: :team_planning,
|
||||
schema: 20241125145005 do
|
||||
include_examples 'desired sharding key backfill job' do
|
||||
let(:batch_table) { :issuable_severities }
|
||||
let(:backfill_column) { :namespace_id }
|
||||
let(:backfill_via_table) { :issues }
|
||||
let(:backfill_via_column) { :namespace_id }
|
||||
let(:backfill_via_foreign_key) { :issue_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -903,6 +903,7 @@ project:
|
|||
- observability_logs
|
||||
- security_exclusions
|
||||
- security_statistics
|
||||
- vulnerability_management_policies
|
||||
award_emoji:
|
||||
- awardable
|
||||
- user
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillIssuableSeveritiesNamespaceId, feature_category: :team_planning do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :issuable_severities,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main_cell,
|
||||
job_arguments: [
|
||||
:namespace_id,
|
||||
:issues,
|
||||
:namespace_id,
|
||||
:issue_id
|
||||
]
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -229,32 +229,39 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
|
|||
end
|
||||
end
|
||||
|
||||
describe '#policy_allowed?' do
|
||||
subject { scope.policy_allowed?(accessed_project, policy) }
|
||||
describe '#policies_allowed?' do
|
||||
subject { scope.policies_allowed?(accessed_project, policies) }
|
||||
|
||||
let(:scope) { described_class.new(target_project) }
|
||||
let_it_be(:target_project) { create(:project) }
|
||||
let_it_be(:allowed_policy) { ::Ci::JobToken::Policies.all_policies.pick(:value) }
|
||||
let_it_be(:policy) { allowed_policy }
|
||||
let_it_be(:accessed_project) { create_inbound_accessible_project_for_policies(target_project, [allowed_policy]) }
|
||||
let(:accessed_project) { create_inbound_accessible_project_for_policies(target_project, [allowed_policy]) }
|
||||
|
||||
context 'when no policies are given' do
|
||||
let_it_be(:policies) { [] }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
context 'when the policies are defined in the scope' do
|
||||
let_it_be(:policies) { [allowed_policy] }
|
||||
|
||||
context 'when the policy is defined in the scope' do
|
||||
it { is_expected.to be(true) }
|
||||
|
||||
context 'when the accessed project is not inbound accessible' do
|
||||
let_it_be(:accessed_project) { create(:project) }
|
||||
let(:accessed_project) { create(:project) }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the policy is not defined in the scope' do
|
||||
let_it_be(:policy) { :not_allowed_policy }
|
||||
context 'when the policy are not defined in the scope' do
|
||||
let_it_be(:policies) { [:not_allowed_policy] }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
|
||||
context 'when the accessed project is the target project' do
|
||||
let_it_be(:accessed_project) { target_project }
|
||||
let(:accessed_project) { target_project }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
|
@ -268,7 +275,7 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
|
|||
end
|
||||
|
||||
context 'when the accessed project has not enabled fine grained permissions' do
|
||||
let_it_be(:accessed_project) { create_inbound_accessible_project(target_project) }
|
||||
let(:accessed_project) { create_inbound_accessible_project(target_project) }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1607,6 +1607,27 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
expect(described_class.by_ids(user_ids)).to contain_exactly(first_user, second_user)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.by_bot_namespace_ids' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project_namespace) { create(:project_namespace) }
|
||||
let_it_be(:other_group) { create(:group) }
|
||||
|
||||
let_it_be(:other_user) { create(:user, user_type: :project_bot) }
|
||||
let_it_be(:user_with_group) { create(:user, user_type: :project_bot) }
|
||||
let_it_be(:user_with_project) { create(:user, user_type: :project_bot) }
|
||||
|
||||
before do
|
||||
user_with_group.update!(bot_namespace: group)
|
||||
user_with_project.update!(bot_namespace: project_namespace)
|
||||
other_user.update!(bot_namespace: other_group)
|
||||
end
|
||||
|
||||
it 'returns users for the given bot_namespace_ids' do
|
||||
expect(described_class.by_bot_namespace_ids([group, project_namespace]))
|
||||
.to contain_exactly(user_with_group, user_with_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'strip attributes' do
|
||||
|
|
|
|||
|
|
@ -24,4 +24,8 @@ RSpec.describe WorkItems::Widgets::Milestone do
|
|||
|
||||
it { is_expected.to eq(work_item.milestone) }
|
||||
end
|
||||
|
||||
describe '.quick_action_commands' do
|
||||
it { expect(described_class.quick_action_commands).to match_array([:milestone, :remove_milestone]) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -241,6 +241,23 @@ RSpec.describe API::Deployments, feature_category: :continuous_delivery do
|
|||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'enforcing job token policies', [:admin_deployments, :admin_environments] do
|
||||
# let(:accessed_project) { project }
|
||||
let(:request) do
|
||||
post(
|
||||
api("/projects/#{project.id}/deployments"),
|
||||
params: {
|
||||
job_token: job.token,
|
||||
environment: 'production',
|
||||
sha: sha,
|
||||
ref: 'master',
|
||||
tag: false,
|
||||
status: 'success'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a maintainer' do
|
||||
it 'creates a new deployment' do
|
||||
post(
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do
|
|||
|
||||
it_behaves_like 'enforcing job token policies', :read_environments do
|
||||
let(:request) do
|
||||
get api("/projects/#{accessed_project.id}/environments"), params: { job_token: job.token }
|
||||
get api("/projects/#{project.id}/environments"), params: { job_token: job.token }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f
|
|||
" ", # Multi-select: __event2
|
||||
"\n", # Submit selections
|
||||
"\n", # Select: Weekly/Monthly count of unique users
|
||||
"aggregate metric description\n", # Submit description
|
||||
"\n" # Accept description for weekly
|
||||
"aggregate metric description\n" # Submit description
|
||||
])
|
||||
|
||||
# Filter down to "dev" options
|
||||
|
|
@ -81,8 +80,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f
|
|||
" ", # Multi-select: __event3
|
||||
"\n", # Submit selections
|
||||
"\n", # Select: Weekly/Monthly count of unique users
|
||||
"aggregate metric description\n", # Submit description
|
||||
"\n" # Accept description for weekly
|
||||
"aggregate metric description\n" # Submit description
|
||||
])
|
||||
|
||||
# Filter down to "dev:create" options
|
||||
|
|
@ -118,7 +116,6 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f
|
|||
"\n", # Select: 00__event1
|
||||
"\n", # Select: Weekly/Monthly count of unique users
|
||||
"aggregate metric description\n", # Submit description
|
||||
"\n", # Accept description for weekly
|
||||
"2\n" # Modify attributes
|
||||
])
|
||||
|
||||
|
|
@ -220,14 +217,9 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f
|
|||
|
||||
# existing metrics which use both events
|
||||
File.write(
|
||||
'config/metrics/counts_7d/' \
|
||||
'count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly.yml',
|
||||
File.read('spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml')
|
||||
)
|
||||
File.write(
|
||||
'config/metrics/counts_28d/' \
|
||||
'count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly.yml',
|
||||
File.read('spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml')
|
||||
'config/metrics/counts_all/' \
|
||||
'count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used.yml',
|
||||
File.read('spec/fixtures/scripts/internal_events/metrics/project_id_multiple_events.yml')
|
||||
)
|
||||
|
||||
# Non-conflicting metric which uses only one of the events
|
||||
|
|
@ -434,4 +426,77 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when succeeded in saving the file' do
|
||||
let(:events) do
|
||||
[{
|
||||
action: 'internal_events_cli_closed', internal_events: true, product_group: 'optimize', tiers: ['ultimate']
|
||||
}, {
|
||||
action: 'internal_events_cli_used', internal_events: true, product_group: 'optimize', tiers: ['ultimate']
|
||||
}]
|
||||
end
|
||||
|
||||
before do
|
||||
events.each do |event|
|
||||
File.write("config/events/#{event[:action]}.yml", event.transform_keys(&:to_s).to_yaml)
|
||||
end
|
||||
end
|
||||
|
||||
context "when creating a single metric" do
|
||||
it 'shows link to the metric dashboard' do
|
||||
queue_cli_inputs([
|
||||
"2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
|
||||
"2\n", # Enum-select: Multiple events -- count occurrences of several separate events or interactions
|
||||
'internal_events_cli', # Filters to the relevant events
|
||||
' ', # Multi-select: internal_events_cli_closed
|
||||
"\e[B", # Arrow down to: internal_events_cli_used
|
||||
' ', # Multi-select: internal_events_cli_used
|
||||
"\n", # Submit selections
|
||||
"\e[B", # Arrow down to: Total count
|
||||
"\n", # Select: Total count
|
||||
"where a definition file was created with the CLI\n", # Input description
|
||||
"1\n", # Select: Copy & continue
|
||||
"\e[B \n", # Skip product categories
|
||||
"y\n" # Create file
|
||||
])
|
||||
|
||||
expected_output = <<~TEXT.chomp
|
||||
- Metric trend dashboard: https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricTrend?Metrics%20Path=counts.count_total_internal_events_cli_closed_and_internal_events_cli_used
|
||||
TEXT
|
||||
|
||||
with_cli_thread do
|
||||
expect { plain_last_lines }.to eventually_include_cli_text(expected_output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when creating a multiple metrics" do
|
||||
it 'shows link to the metric dashboard' do
|
||||
queue_cli_inputs([
|
||||
"2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
|
||||
"2\n", # Enum-select: Multiple events -- count occurrences of several separate events or interactions
|
||||
'internal_events_cli', # Filters to the relevant events
|
||||
' ', # Multi-select: internal_events_cli_closed
|
||||
"\e[B", # Arrow down to: internal_events_cli_used
|
||||
' ', # Multi-select: internal_events_cli_used
|
||||
"\n", # Submit selections
|
||||
"\n", # Select: Weekly/Monthly count
|
||||
"where a definition file was created with the CLI\n", # Input description
|
||||
"1\n", # Select: Copy & continue
|
||||
"\e[B \n", # Skip product categories
|
||||
"y\n" # Create file
|
||||
])
|
||||
|
||||
expected_output = <<-TEXT.chomp # <<- used instead of <<~ to save indentation
|
||||
- Metric trend dashboards:
|
||||
- https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricTrend?Metrics%20Path=counts.count_total_internal_events_cli_closed_and_internal_events_cli_used_monthly
|
||||
- https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricTrend?Metrics%20Path=counts.count_total_internal_events_cli_closed_and_internal_events_cli_used_weekly
|
||||
TEXT
|
||||
|
||||
with_cli_thread do
|
||||
expect { plain_last_lines }.to eventually_include_cli_text(expected_output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -131,6 +131,111 @@ RSpec.describe 'InternalEventsCli::Flows::UsageViewer', :aggregate_failures, fea
|
|||
end
|
||||
end
|
||||
|
||||
context 'for an event with multiple metrics' do
|
||||
let(:expected_rails_example) do
|
||||
<<~TEXT.chomp
|
||||
--------------------------------------------------
|
||||
# RAILS
|
||||
|
||||
include Gitlab::InternalEventsTracking
|
||||
|
||||
track_internal_event(
|
||||
'internal_events_cli_used',
|
||||
project: project,
|
||||
user: user
|
||||
)
|
||||
|
||||
--------------------------------------------------
|
||||
TEXT
|
||||
end
|
||||
|
||||
let(:expected_rspec_example) do
|
||||
<<~TEXT.chomp
|
||||
--------------------------------------------------
|
||||
# RSPEC
|
||||
|
||||
it_behaves_like 'internal event tracking' do
|
||||
let(:event) { 'internal_events_cli_used' }
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
TEXT
|
||||
end
|
||||
|
||||
let(:expected_gdk_example) do
|
||||
<<~TEXT.chomp
|
||||
--------------------------------------------------
|
||||
# RAILS CONSOLE -- generate service ping payload, including most recent usage data
|
||||
|
||||
require_relative 'spec/support/helpers/service_ping_helpers.rb'
|
||||
|
||||
# Get current value of a metric
|
||||
ServicePingHelpers.get_current_usage_metric_value('redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_monthly')
|
||||
ServicePingHelpers.get_current_usage_metric_value('redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_weekly')
|
||||
|
||||
# View entire service ping payload
|
||||
ServicePingHelpers.get_current_service_ping_payload
|
||||
--------------------------------------------------
|
||||
TEXT
|
||||
end
|
||||
|
||||
let(:expected_tableau_example) do
|
||||
<<~TEXT.chomp
|
||||
--------------------------------------------------
|
||||
# GROUP DASHBOARDS -- view all service ping metrics for a specific group
|
||||
|
||||
analytics_instrumentation: https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricExplorationbyGroup?Group%20Name=analytics_instrumentation&Stage%20Name=monitor
|
||||
|
||||
--------------------------------------------------
|
||||
# METRIC TRENDS -- view data for a service ping metric for internal_events_cli_used
|
||||
|
||||
redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_monthly: https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricTrend?Metrics%20Path=redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_monthly
|
||||
redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_weekly: https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricTrend?Metrics%20Path=redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_weekly
|
||||
|
||||
--------------------------------------------------
|
||||
Note: The metric dashboard links can also be accessed from https://metrics.gitlab.com/
|
||||
|
||||
Not what you're looking for? Check this doc:
|
||||
- https://docs.gitlab.com/ee/development/internal_analytics/#data-discovery
|
||||
TEXT
|
||||
end
|
||||
|
||||
before do
|
||||
File.write(event1_filepath, File.read(event1_content))
|
||||
File.write(
|
||||
'config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml',
|
||||
File.read('spec/fixtures/scripts/internal_events/metrics/user_id_single_event.yml')
|
||||
)
|
||||
end
|
||||
|
||||
it 'shows backend examples for all metrics' do
|
||||
queue_cli_inputs([
|
||||
"3\n", # Enum-select: View Usage -- look at code examples for an existing event
|
||||
'internal_events_cli_used', # Filters to this event
|
||||
"\n", # Select: config/events/internal_events_cli_used.yml
|
||||
"\n", # Select: ruby/rails
|
||||
"\e[B", # Arrow down to: rspec
|
||||
"\n", # Select: rspec
|
||||
"7\n", # Select: Manual testing: check current values of metrics from rails console (any data source)
|
||||
"8\n", # Select: Data verification in Tableau
|
||||
"Exit", # Filters to this item
|
||||
"\n" # select: Exit
|
||||
])
|
||||
|
||||
with_cli_thread do
|
||||
expect { plain_last_lines(200) }.to eventually_include_cli_text(
|
||||
expected_example_prompt,
|
||||
expected_rails_example,
|
||||
expected_rspec_example,
|
||||
expected_gdk_example,
|
||||
expected_tableau_example
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for an event without identifiers' do
|
||||
let(:expected_rails_example) do
|
||||
<<~TEXT.chomp
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
context 'when option is for a supported and not yet defined metric' do
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<cyan>time frame</cyan> count of <cyan>unique users</cyan> who triggered a list of events",
|
||||
name: "<cyan>Time frame</cyan> count of <cyan>unique users</cyan> who triggered a list of events",
|
||||
value: metrics
|
||||
})
|
||||
end
|
||||
|
|
@ -51,7 +51,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<cyan>time frame</cyan> count of <cyan>unique users</cyan> who triggered a list of events " \
|
||||
name: "<cyan>Time frame</cyan> count of <cyan>unique users</cyan> who triggered a list of events " \
|
||||
"<cyan>where label/prop/anything</cyan> is...",
|
||||
value: metrics
|
||||
})
|
||||
|
|
@ -64,7 +64,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'formats the option as disabled' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<bright_black>time frame count of unique users who triggered a list of events</bright_black>",
|
||||
name: "<bright_black>Time frame count of unique users who triggered a list of events</bright_black>",
|
||||
value: metrics,
|
||||
disabled: "<bold><bright_black>(already defined)</bright_black></bold>"
|
||||
})
|
||||
|
|
@ -75,7 +75,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<bright_black>time frame count of unique users who triggered " \
|
||||
name: "<bright_black>Time frame count of unique users who triggered " \
|
||||
"a list of events where filtered</bright_black>",
|
||||
value: metrics,
|
||||
disabled: "<bold><bright_black>(already defined)</bright_black></bold>"
|
||||
|
|
@ -89,7 +89,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'formats the option as disabled' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<bright_black>time frame count of unique users who triggered a list of events</bright_black>",
|
||||
name: "<bright_black>Time frame count of unique users who triggered a list of events</bright_black>",
|
||||
value: metrics,
|
||||
disabled: "<bold><bright_black>(user unavailable)</bright_black></bold>"
|
||||
})
|
||||
|
|
@ -100,7 +100,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<bright_black>time frame count of unique users who triggered " \
|
||||
name: "<bright_black>Time frame count of unique users who triggered " \
|
||||
"a list of events where filtered</bright_black>",
|
||||
value: metrics,
|
||||
disabled: "<bold><bright_black>(user unavailable)</bright_black></bold>"
|
||||
|
|
@ -114,7 +114,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<cyan>time frame</cyan> count of <cyan>unique values for 'label'</cyan> " \
|
||||
name: "<cyan>Time frame</cyan> count of <cyan>unique values for 'label'</cyan> " \
|
||||
"from a list of events occurrences",
|
||||
value: metrics
|
||||
})
|
||||
|
|
@ -126,7 +126,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<cyan>time frame</cyan> count of a list of events occurrences",
|
||||
name: "<cyan>Time frame</cyan> count of a list of events occurrences",
|
||||
value: metrics
|
||||
})
|
||||
end
|
||||
|
|
@ -144,7 +144,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego
|
|||
|
||||
it 'highlights key words in the name' do
|
||||
expect(option.formatted).to eq({
|
||||
name: "<cyan>time frame 1/time frame 2</cyan> count of <cyan>unique users</cyan> " \
|
||||
name: "<cyan>Time frame 1/Time frame 2</cyan> count of <cyan>unique users</cyan> " \
|
||||
"who triggered a list of events",
|
||||
value: metrics
|
||||
})
|
||||
|
|
|
|||
|
|
@ -82,14 +82,12 @@ RSpec.describe Cli, feature_category: :service_ping do
|
|||
"\e[B", # Arrow down to: Weekly count of unique projects
|
||||
"\n", # Select: Weekly count of unique projects
|
||||
"where a defition file was created with the CLI\n", # Input description
|
||||
"\n", # Submit weekly description for monthly
|
||||
"2\n", # Select: Modify attributes
|
||||
"\n", # Accept group
|
||||
"\n", # Accept product categories
|
||||
"\n", # Skip URL
|
||||
"1\n", # Select: [free, premium, ultimate]
|
||||
"y\n", # Create file
|
||||
"y\n", # Create file
|
||||
"5\n" # Exit
|
||||
]
|
||||
end
|
||||
|
|
@ -104,11 +102,8 @@ RSpec.describe Cli, feature_category: :service_ping do
|
|||
let(:output_files) do
|
||||
# rubocop:disable Layout/LineLength -- Long filepaths read better unbroken
|
||||
[{
|
||||
'path' => 'config/metrics/counts_28d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly.yml',
|
||||
'content' => 'spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml'
|
||||
}, {
|
||||
'path' => 'config/metrics/counts_7d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly.yml',
|
||||
'content' => 'spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml'
|
||||
'path' => 'config/metrics/counts_all/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used.yml',
|
||||
'content' => 'spec/fixtures/scripts/internal_events/metrics/project_id_multiple_events.yml'
|
||||
}]
|
||||
# rubocop:enable Layout/LineLength
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,13 +34,18 @@ RSpec.describe Ci::Catalog::Resources::AggregateLast30DayUsageService, :clean_gi
|
|||
(1..i + 1).each do |j|
|
||||
component = create(:ci_catalog_resource_component, version: version, name: "component#{j}")
|
||||
|
||||
(1..usage_count).each do |k|
|
||||
(1..usage_count).each do |mock_used_by_project_id|
|
||||
# Inside the usage window
|
||||
create(:ci_catalog_resource_component_usage,
|
||||
component: component, used_date: usage_start_date, used_by_project_id: k)
|
||||
component: component, used_date: usage_start_date, used_by_project_id: mock_used_by_project_id)
|
||||
# Outside the usage window
|
||||
create(:ci_catalog_resource_component_usage,
|
||||
component: component, used_date: usage_start_date - k.days, used_by_project_id: k)
|
||||
component: component, used_date: usage_start_date - mock_used_by_project_id.days,
|
||||
used_by_project_id: mock_used_by_project_id)
|
||||
|
||||
# create new usage records in the window
|
||||
create(:catalog_resource_component_last_usage, component: component, last_used_date: usage_start_date,
|
||||
used_by_project_id: mock_used_by_project_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -49,56 +54,25 @@ RSpec.describe Ci::Catalog::Resources::AggregateLast30DayUsageService, :clean_gi
|
|||
Ci::Catalog::Resource.update_all(last_30_day_usage_count_updated_at: initial_usage_count_updated_at)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
context 'when the aggregator is not interrupted' do
|
||||
shared_examples 'aggregates usage data for all catalog resources' do
|
||||
it 'returns a success response' do
|
||||
response = service.execute
|
||||
context 'when storing usage data in p_catalog_resource_component_usages' do
|
||||
before do
|
||||
stub_feature_flags(ci_catalog_ranking_from_new_usage_table: false)
|
||||
end
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.payload).to eq({
|
||||
total_targets_completed: 4,
|
||||
cursor_attributes: expected_cursor_attributes
|
||||
})
|
||||
end
|
||||
end
|
||||
describe '#execute' do
|
||||
context 'when the aggregator is not interrupted' do
|
||||
shared_examples 'aggregates usage data for all catalog resources' do
|
||||
it 'returns a success response' do
|
||||
response = service.execute
|
||||
|
||||
it_behaves_like 'aggregates usage data for all catalog resources'
|
||||
|
||||
it 'calls BulkUpdate once and updates usage counts for all catalog resources' do
|
||||
expect(Gitlab::Database::BulkUpdate).to receive(:execute).once.and_call_original
|
||||
|
||||
service.execute
|
||||
|
||||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
|
||||
context 'when there are two batches of usage counts' do
|
||||
before do
|
||||
stub_const('Gitlab::Ci::Components::Usages::Aggregator::TARGET_BATCH_SIZE', 2)
|
||||
end
|
||||
|
||||
it_behaves_like 'aggregates usage data for all catalog resources'
|
||||
|
||||
it 'calls BulkUpdate twice and updates usage counts for all catalog resources' do
|
||||
expect(Gitlab::Database::BulkUpdate).to receive(:execute).twice.and_call_original
|
||||
|
||||
service.execute
|
||||
|
||||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when some catalog resources have already been processed today' do
|
||||
before_all do
|
||||
resources.first(2).each do |resource|
|
||||
resource.update!(last_30_day_usage_count_updated_at: Date.today.to_time)
|
||||
expect(response).to be_success
|
||||
expect(response.payload).to eq({
|
||||
total_targets_completed: 4,
|
||||
cursor_attributes: expected_cursor_attributes
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# The cursor has not advanced so it still processes all targets
|
||||
it_behaves_like 'aggregates usage data for all catalog resources'
|
||||
|
||||
it 'calls BulkUpdate once and updates usage counts for all catalog resources' do
|
||||
|
|
@ -109,26 +83,50 @@ RSpec.describe Ci::Catalog::Resources::AggregateLast30DayUsageService, :clean_gi
|
|||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all catalog resources have already been processed today' do
|
||||
before_all do
|
||||
Ci::Catalog::Resource.update_all(last_30_day_usage_count_updated_at: Date.today.to_time)
|
||||
context 'when there are two batches of usage counts' do
|
||||
before do
|
||||
stub_const('Gitlab::Ci::Components::Usages::Aggregator::TARGET_BATCH_SIZE', 2)
|
||||
end
|
||||
|
||||
it_behaves_like 'aggregates usage data for all catalog resources'
|
||||
|
||||
it 'calls BulkUpdate twice and updates usage counts for all catalog resources' do
|
||||
expect(Gitlab::Database::BulkUpdate).to receive(:execute).twice.and_call_original
|
||||
|
||||
service.execute
|
||||
|
||||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not aggregate usage data' do
|
||||
expect(Gitlab::Ci::Components::Usages::Aggregator).not_to receive(:new)
|
||||
context 'when some catalog resources have already been processed today' do
|
||||
before_all do
|
||||
resources.first(2).each do |resource|
|
||||
resource.update!(last_30_day_usage_count_updated_at: Date.today.to_time)
|
||||
end
|
||||
end
|
||||
|
||||
response = service.execute
|
||||
# The cursor has not advanced so it still processes all targets
|
||||
it_behaves_like 'aggregates usage data for all catalog resources'
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.message).to eq("Processing complete for #{Date.today}")
|
||||
expect(response.payload).to eq({})
|
||||
it 'calls BulkUpdate once and updates usage counts for all catalog resources' do
|
||||
expect(Gitlab::Database::BulkUpdate).to receive(:execute).once.and_call_original
|
||||
|
||||
service.execute
|
||||
|
||||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a new catalog resource is added today' do
|
||||
context 'when all catalog resources have already been processed today' do
|
||||
before_all do
|
||||
Ci::Catalog::Resource.update_all(last_30_day_usage_count_updated_at: Date.today.to_time)
|
||||
end
|
||||
|
||||
it 'does not aggregate usage data' do
|
||||
create(:ci_catalog_resource)
|
||||
expect(Gitlab::Ci::Components::Usages::Aggregator).not_to receive(:new)
|
||||
|
||||
response = service.execute
|
||||
|
|
@ -137,49 +135,108 @@ RSpec.describe Ci::Catalog::Resources::AggregateLast30DayUsageService, :clean_gi
|
|||
expect(response.message).to eq("Processing complete for #{Date.today}")
|
||||
expect(response.payload).to eq({})
|
||||
end
|
||||
|
||||
context 'when a new catalog resource is added today' do
|
||||
it 'does not aggregate usage data' do
|
||||
create(:ci_catalog_resource)
|
||||
expect(Gitlab::Ci::Components::Usages::Aggregator).not_to receive(:new)
|
||||
|
||||
response = service.execute
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.message).to eq("Processing complete for #{Date.today}")
|
||||
expect(response.payload).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the aggregator is interrupted' do
|
||||
before do
|
||||
# Sets the aggregator to break after the first iteration on each run
|
||||
stub_const('Gitlab::Ci::Components::Usages::Aggregator::MAX_RUNTIME', 0)
|
||||
stub_const('Gitlab::Ci::Components::Usages::Aggregator::DISTINCT_USAGE_BATCH_SIZE', 2)
|
||||
end
|
||||
|
||||
it 'updates the expected usage counts for each run' do
|
||||
# On 1st run, we get an incomplete usage count for the first catalog resource so it is not saved
|
||||
expect { service.execute }
|
||||
.to not_change { ordered_usage_counts }
|
||||
.and not_change { ordered_usage_counts_updated_at }
|
||||
|
||||
# On 2nd run, we get the complete usage count for the first catalog resource and save it
|
||||
service.execute
|
||||
|
||||
expect(ordered_usage_counts).to eq([expected_ordered_usage_counts.first, 0, 0, 0])
|
||||
expect(ordered_usage_counts_updated_at).to eq([Time.current, [initial_usage_count_updated_at] * 3].flatten)
|
||||
|
||||
# Execute service repeatedly until done
|
||||
30.times do
|
||||
response = service.execute
|
||||
break if response.payload[:cursor_attributes][:target_id] == 0
|
||||
end
|
||||
|
||||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when another instance is running with the same lease key' do
|
||||
it 'returns a success response with the lease key' do
|
||||
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute).tap(&:try_obtain)
|
||||
response = service.execute
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.message).to eq('Lease taken')
|
||||
expect(response.payload).to eq({ lease_key: lease_key })
|
||||
lease.cancel
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the aggregator is interrupted' do
|
||||
before do
|
||||
# Sets the aggregator to break after the first iteration on each run
|
||||
stub_const('Gitlab::Ci::Components::Usages::Aggregator::MAX_RUNTIME', 0)
|
||||
stub_const('Gitlab::Ci::Components::Usages::Aggregator::DISTINCT_USAGE_BATCH_SIZE', 2)
|
||||
end
|
||||
|
||||
it 'updates the expected usage counts for each run' do
|
||||
# On 1st run, we get an incomplete usage count for the first catalog resource so it is not saved
|
||||
expect { service.execute }
|
||||
.to not_change { ordered_usage_counts }
|
||||
.and not_change { ordered_usage_counts_updated_at }
|
||||
|
||||
# On 2nd run, we get the complete usage count for the first catalog resource and save it
|
||||
context 'when storing usage data in catalog_resource_component_last_usages' do
|
||||
describe '#execute' do
|
||||
it 'updates component usage counts' do
|
||||
service.execute
|
||||
|
||||
expect(ordered_usage_counts).to eq([expected_ordered_usage_counts.first, 0, 0, 0])
|
||||
expect(ordered_usage_counts_updated_at).to eq([Time.current, [initial_usage_count_updated_at] * 3].flatten)
|
||||
|
||||
# Execute service repeatedly until done
|
||||
30.times do
|
||||
response = service.execute
|
||||
break if response.payload[:cursor_attributes][:target_id] == 0
|
||||
resources.each do |resource|
|
||||
resource.components.each do |component|
|
||||
expect(component.reload.last_30_day_usage_count).to eq(
|
||||
component.last_usages.select(:used_by_project_id).distinct.count
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
expect(ordered_usage_counts).to eq(expected_ordered_usage_counts)
|
||||
expect(ordered_usage_counts_updated_at).to match_array([Time.current] * 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when another instance is running with the same lease key' do
|
||||
it 'returns a success response with the lease key' do
|
||||
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute).tap(&:try_obtain)
|
||||
it 'updates resource usage counts' do
|
||||
service.execute
|
||||
|
||||
resources.each do |resource|
|
||||
expected_count = resource.components.sum(:last_30_day_usage_count)
|
||||
expect(resource.reload.last_30_day_usage_count).to eq(expected_count)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a success response' do
|
||||
response = service.execute
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.message).to eq('Lease taken')
|
||||
expect(response.payload).to eq({ lease_key: lease_key })
|
||||
lease.cancel
|
||||
expect(response.message).to eq('Usage counts updated for components and resources')
|
||||
end
|
||||
|
||||
context 'when there are no components' do
|
||||
before do
|
||||
Ci::Catalog::Resources::Component.delete_all
|
||||
end
|
||||
|
||||
it 'updates resource counts to zero' do
|
||||
service.execute
|
||||
|
||||
resources.each do |resource|
|
||||
expect(resource.reload.last_30_day_usage_count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -143,11 +143,12 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when work item labels widget is disabled' do
|
||||
context 'when work item labels, assignees & milestone widgets are disabled' do
|
||||
before do
|
||||
widget_definitions = WorkItems::Type.default_by_type(:issue).widget_definitions
|
||||
widget_definitions.find_by_widget_type(:labels).update!(disabled: true)
|
||||
widget_definitions.find_by_widget_type(:assignees).update!(disabled: true)
|
||||
widget_definitions.find_by_widget_type(:milestone).update!(disabled: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'issuable record that does not supports quick actions' do
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'enforcing job token policies' do |policy|
|
||||
RSpec.shared_examples 'enforcing job token policies' do |policies|
|
||||
context 'when authenticating with a CI Job Token from another project' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:job) { create(:ci_build, :running, user: user) }
|
||||
let_it_be(:accessed_project) { create(:project, developers: user) }
|
||||
let_it_be(:allowed_policies) { [policy] }
|
||||
let_it_be(:allowed_policies) { Array(policies) }
|
||||
let_it_be(:default_permissions) { false }
|
||||
|
||||
before do
|
||||
create(:ci_job_token_project_scope_link,
|
||||
source_project: accessed_project,
|
||||
source_project: project,
|
||||
target_project: job.project,
|
||||
direction: :inbound,
|
||||
job_token_policies: allowed_policies,
|
||||
|
|
@ -23,23 +21,32 @@ RSpec.shared_examples 'enforcing job token policies' do |policy|
|
|||
response
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:ok) }
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
|
||||
context 'when the policy is not allowed' do
|
||||
context 'when the policies are not allowed' do
|
||||
let(:allowed_policies) { [] }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:forbidden) }
|
||||
|
||||
it 'returns an error message containing the disallowed policy' do
|
||||
do_request
|
||||
expect(json_response['message']).to eq("403 Forbidden - Insufficient permissions to access this resource " \
|
||||
"in project #{accessed_project.path}. The following token permission is required: #{policy}.")
|
||||
|
||||
expected_message = '403 Forbidden - Insufficient permissions to access this resource ' \
|
||||
"in project #{project.path}. "
|
||||
|
||||
expected_message << if Array(policies).size == 1
|
||||
"The following token permission is required: #{policies}."
|
||||
else
|
||||
"The following token permissions are required: #{Array(policies).to_sentence}."
|
||||
end
|
||||
|
||||
expect(json_response['message']).to eq(expected_message)
|
||||
end
|
||||
|
||||
context 'when fine grained permissions are disabled' do
|
||||
let_it_be(:default_permissions) { true }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:ok) }
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'when the `enforce_job_token_policies` feature flag is disabled' do
|
||||
|
|
@ -47,7 +54,7 @@ RSpec.shared_examples 'enforcing job token policies' do |policy|
|
|||
stub_feature_flags(enforce_job_token_policies: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:ok) }
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -60,5 +60,28 @@ RSpec.shared_examples 'Deploy keys with protected tags' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deploy key is already selected for protected branch' do
|
||||
let(:protected_tag) { create(:protected_tag, :no_one_can_create, project: project, name: 'v1.0.0') }
|
||||
let(:write_access_key) { create(:deploy_key, user: user, write_access_to: project) }
|
||||
|
||||
before do
|
||||
create(:protected_tag_create_access_level, protected_tag: protected_tag, deploy_key: write_access_key)
|
||||
end
|
||||
|
||||
it 'displays a preselected deploy key' do
|
||||
visit project_protected_tags_path(project)
|
||||
|
||||
within(".js-protected-tag-edit-form") do
|
||||
find(".js-allowed-to-create").click
|
||||
wait_for_requests
|
||||
|
||||
within('[data-testid="deploy_key-dropdown-item"]') do
|
||||
deploy_key_checkbox = find('[data-testid="dropdown-item-checkbox"]')
|
||||
expect(deploy_key_checkbox).to have_no_css("gl-invisible")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -118,9 +118,7 @@ RSpec.shared_examples 'issuable record that supports quick actions' do |with_wid
|
|||
it 'assigns and sets milestone to issuable' do
|
||||
expect(issuable.assignees).to eq([assignee])
|
||||
expect(issuable.description).to eq new_descr
|
||||
# WorkItem milestone widget does not support quick the action yet
|
||||
expect(issuable.milestone).to eq(milestone) unless issuable.is_a?(WorkItem)
|
||||
expect(issuable.milestone).to be_nil if issuable.is_a?(WorkItem)
|
||||
expect(issuable.milestone).to eq(milestone)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::Catalog::Resources::AggregateLast30DayUsageWorker, feature_category: :pipeline_composition do
|
||||
subject(:worker) { described_class.new }
|
||||
|
||||
before do
|
||||
stub_feature_flags(ci_catalog_ranking_from_new_usage_table: false)
|
||||
end
|
||||
|
||||
include_examples 'an idempotent worker'
|
||||
|
||||
it 'has the `until_executed` deduplicate strategy' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue