[{"data":1,"prerenderedAt":729},["ShallowReactive",2],{"/en-us/blog/upgrading-bootstrap-vue/":3,"navigation-en-us":33,"banner-en-us":462,"footer-en-us":479,"Enrique Alcántara-Paul Gascou-Vaillancourt":689,"next-steps-en-us":714},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":23,"_id":26,"_type":27,"title":28,"_source":29,"_file":30,"_stem":31,"_extension":32},"/en-us/blog/upgrading-bootstrap-vue","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Upgrading bootstrap-vue in gitlab-ui","How we upgraded BootstrapVue to v2 stable in GitLab UI, our UI library, and what challenges we encountered in the process","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664102/Blog/Hero%20Images/gitlab-values-cover.png","https://about.gitlab.com/blog/upgrading-bootstrap-vue","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Upgrading bootstrap-vue in gitlab-ui\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Enrique Alcántara\"},{\"@type\":\"Person\",\"name\":\"Paul Gascou-Vaillancourt\"}],\n        \"datePublished\": \"2020-01-24\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":20,"body":21,"category":22},[18,19],"Enrique Alcántara","Paul Gascou-Vaillancourt","2020-01-24","{::options parse_block_html=\"true\" /}\n\n\n\n\n\u003C!-- Content start here -->\n\n\nThree months ago, we started a process to upgrade bootstrap-vue in gitlab-ui\nfrom `2.0.0-rc.27` to the\n\nlatest stable version. You may wonder: Why has it taken so long? We found\nseveral impediments along the\n\nway caused by backward compatibility issues widespread across GitLab’s\ncodebase. In this blog post\n\nwe’ll cover the following topics:\n\n\n1. [How GitLab CI helped us during the\nmigration](#1-how-gitlab-ci-helped-us-during-the-migration)\n\n1. [Bootstrap-vue breaking changes](#2-bootstrap-vue-breaking-changes)\n\n\n## 1. How GitLab CI helped us during the migration\n\n\nWhen dealing with a situation like this, where one of your libraries\nintroduces a lot of breaking\n\nchanges that need to be iteratively fixed in the products that depend on\nthem, having access to\n\ngreat tools like GitLab CI can be a huge time-saver, especially in a\ndistributed company like GitLab.\n\n\nIt goes without saying that GitLab itself is heavily tested, with tens or\neven hundreds of CI jobs\n\nrun against every commit. While this is extremely useful, GitLab depends on\nan official\n\nrelease of GitLab UI which is pinned in its `package.json`. This isn't\nhelpful in our case\n\nbecause we don't want to release a new version of GitLab UI with\nBootstrapVue 2.0.0 stable unless we're\n\nabsolutely certain that it won’t cause adverse effects in GitLab.\n\n\nWhile there are several ways to test an unreleased version of an NPM package\nin a project, we would\n\nlike to briefly explain how we did it thanks to a few GitLab CI jobs that\ndemonstrate how you can\n\ntake advantage of this powerful feature for things that go beyond running\ntest suites in your projects.\n\n\n### npm publish\n\n\nLet's talk briefly about the [`npm\npublish`](https://docs.npmjs.com/cli/publish) command: it is the\n\ncommand that you would run to publish an NPM package to NPM's registry. When\nrunning the command, a\n\ntarball of your package is created and uploaded to the registry where it is\ntagged with the `version`\n\nthat's defined in your `package.json`. This is great, but once you run the\npublish command, a new\n\nversion of your package becomes publicly available. Of course you could\nrelease beta versions of your\n\npackage but then again, you would most likely bloat the registry with\nversions of your package that\n\nyou know won't work. So, is there a way to publish a test npm package\noutside the npm registry?\n\n\n### yarn pack\n\n\nWhat now? Another CLI command? Okay so what does this one do? According to\nthe doc,\n\n[`yarn pack`](https://yarnpkg.com/en/docs/cli/pack)\n\n\n> Creates a compressed gzip archive of package dependencies.\n\n\nSo this means that it archives your package, like `npm publish` does, except\nthat the archive will\n\nstay on your computer and won't be uploaded or published to any registry.\nAnd you can even give your\n\narchive a name using the `--filename` flag.\n\n\n### Okay, so how does that help us?\n\n\nAll right, let's see how this is useful in GitLab UI's CI setup. If you look\nat the `.gitlab-ci.yml`\n\nfile in GitLab UI, you'll see an\n[`upload_artifacts`](https://gitlab.com/gitlab-org/gitlab-ui/blob/3e8fd5e7f54542b7534203d65097039a3e731fd7/.gitlab-ci.yml#L183-201)\n\njob.\n\n\n```\n\nupload_artifacts:\n  extends: .node\n  stage: deploy\n  variables:\n    TAR_ARCHIVE_NAME: gitlab-ui.$CI_COMMIT_REF_SLUG.tgz\n  needs:\n    - build\n  script:\n    - yarn pack --filename $TAR_ARCHIVE_NAME\n    - DEPENDENCY_URL=\"$CI_PROJECT_URL/-/jobs/$CI_JOB_ID/artifacts/raw/$TAR_ARCHIVE_NAME\"\n    - echo \"The package.json dependency URL is $DEPENDENCY_URL\"\n    - echo $DEPENDENCY_URL > .dependency_url\n  artifacts:\n    when: always\n    paths:\n      - $TAR_ARCHIVE_NAME\n      - .dependency_url\n  only:\n    - /.+/@gitlab-org/gitlab-ui\n```\n\n\nNotice how this job depends on the `build` job via the `needs` option, this\nmeans that it will\n\nonly run after `build` has completed, and it will have access to `build`'s\nartifacts, which, among\n\nother things, contain the production-ready compiled version of GitLab UI.\nNow `upload_artifacts`\n\ndoes 3 important things:\n\n\n- It declares a `TAR_ARCHIVE_NAME` envrionment variable which is a\ncombination of `gitlab-ui` and the\n\ncurrent branch's name, followed by the `.tgz` extension.\n\n- It calls `yarn pack --filename $TAR_ARCHIVE_NAME` which, as we've seen\nabove, will create an\n\narchive of the package's dependencies, including the production-ready bundle\nfrom the `build` job.\n\n- And finally, it lists `$TAR_ARCHIVE_NAME` as one the job's artifacts\npaths.\n\n\nThe last point is where all the magic happens, because the package's archive\nnow becomes a\n\ndownloadable artifact, which means that we have a kind of simple and private\nregistry for all of our\n\ndevelopment branches from which we can install GitLab UI's development\nbuilds to test them out in\n\nGitLab. Notice how the job also prints a URL which is constructed this way:\n\n`$CI_PROJECT_URL/-/jobs/$CI_JOB_ID/artifacts/raw/$TAR_ARCHIVE_NAME`. This\ngives us a direct download\n\nlink to the archived package that we can use to install the build in GitLab:\n\n\n```sh\n\nyarn add @gitlab/ui@$DEPENDENCY_URL\n\n```\n\n\nWhere `$DEPENDENCY_URL` is the artifact's URL.\n\n\nThis has helped us a lot during this big migration because we were able to\nopen a merge request in\n\nGitLab where the `package.json` would point to our GitLab UI development\nbuild. This allowed us to\n\nbenefit from the huge CI pipeline could benefit from the huge CI pipeline in\nGitLab to run every test\n\nsuite, thus giving us a nice overview of what needed to be fixed. It was\nalso very useful in terms of\n\ncollaboration, because we were able to involve more engineers in the\nmigration process without\n\nrequiring them to setup their local environment in any particular way. They\nwould simply checkout the\n\ndevelopment branch, run `yarn install`, and they would be good to go.\n\n\nThis is only one of the endless possibilities that GitLab CI offers.\nHopefully it gave you some\n\nideas for your own special CI job!\n\n\n## 2. Bootstrap-vue breaking changes\n\n\n### Different import statements\n\n\nImporting bootstrap-vue components requires a different syntax. In\nbootstrap-vue 2.0.0-rc.27 (previous version in production), we imported\ncomponents directly from the source path:\n\n\n```javascript\n\nimport BDropdown from 'bootstrap-vue/src/components/dropdown/dropdown';\n\n```\n\n\nIn bootstrap-vue 2.0, the component source file does not have a default\n\n[export statement\nanymore](https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/src/components/dropdown/dropdown.js#L85).\n\nTo circumvent this issue, we changed all import statements to reference\nbootstrap-vue main import file.\n\n\n```javascript\n\nimport { BDropdown } from 'bootstrap-vue';\n\n```\n\n\n### A new slot syntax for BVTable and BVTab components\n\n\n#### Tables\n\n\nBootstrap-vue BTable dynamically generates Vue template slots based on the\ntable’s\n\n[column definitions](https://bootstrap-vue.js.org/docs/components/table)\n\nto customize the presentation of\n\n[content](https://bootstrap-vue.js.org/docs/components/table#custom-data-rendering).\n\nIn bootstrap-vue 2.0, the naming syntax for these slots changed:\n\n\n```html\n\n\u003Ctemplate #cell(project)=\"data\">\n\n```\n\n\nGitLab uses BTable widely, and we found several approaches to declare these\nslots across the codebase:\n\n\n```html\n\n\u003C!-- Version 1 -->\n\n\u003Ctemplate #HEAD_changeInPercent=\"{ label }\">\n\n\n\u003C!-- Version 2 -->\n\n\u003Ctemplate #name=\"items\">\n\n\n\u003C!-- Version 3 -->\n\n\u003Ctemplate slot=\"project\" slot-scope=\"data\">\n\n```\n\n\nGitLab test suite detected the components broken by this change when running\nthe tests using the artifact generated by gitlab-ui.\n\nWe fixed these failures in the [upgrade bootstrap-vue merge\nrequest](https://gitlab.com/gitlab-org/gitlab/merge_requests/18913).\n\n\n#### Tabs\n\n\nThe BTabs component does not have the `tabs` slot anymore (BV deprecated\nthis slot in previous versions). You should use `tabs-end` instead.\n\n\n```html\n\n\u003Ctemplate #tabs> \u003C!-- Deprecated version -->\n\n\u003Ctemplate #tabs-end> \u003C!-- Use tabs-end instead -->\n \u003Cli class=\"nav-item align-self-center\">\n Contentless tab\n \u003C/li>\n\u003C/template>\n\n```\n\n\n### Heavily refactored tooltip and popover components\n\n\nThe changes introduced in the tooltip component generated the most\nsignificant number of side-effects in gitlab-ui and GitLab.\n\nFrom the bootstrap-vue changelog entry for 2.0.0:\n\n\n> Tooltips and popovers have been completely re-written for better\nreactivity and stability. The directive versions are now reactive to trigger\nelement title attribute changes and configuration changes.\n\n\nSince the API for these components didn’t change, our codebase shouldn’t\nhave been affected by their\n\nrefactoring. That wasn’t the case. When we ran the GitLab test suite, all\ntest specs for components\n\nthat have the tooltip as a dependency failed. As you may realize, those are\nmany Karma, Jest,\n\nand unit tests. You’ll wonder... what happened?\n\n\n#### Problem 1: The tooltip directive expects to be attached to the DOM\ndocument\n\n\nOne of the behaviors introduced by the tooltip component refactoring is that\nwe can’t initialize the\n\ncomponent unless it is attached to the\n\n[document\nobject](https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/src/components/tooltip/helpers/bv-tooltip.js#L218).\nIf that condition is not satisfied, bootstrap-vue logs the following\n\nwarning message, and won’t initialize the component:\n\n\n```log\n\n[BootstrapVue warn]: tooltip unable to find target element in document\"\n\n```\n\n\nNone of our unit tests attach the component under test to the\n[document](https://developer.mozilla.org/en-US/docs/Web/API/Window/document)\nobject. This action could cause memory leaks in Karma unit tests\n\nbecause test suite environments are not isolated.\n\n\nHow did we solve this problem?\n\n\nFirst, we migrated all the failing Karma tests to Jest in separate merge\nrequests (see [group\n1](https://gitlab.com/gitlab-org/gitlab/merge_requests/18913?diff_id=70349853#1-karma)\nand [group\n2](https://gitlab.com/gitlab-org/gitlab/merge_requests/18913?diff_id=70349853#note_249314702)).\n\nWe also rewrote those tests to use vue-test-utils instead of a legacy vue\ntest utility we wrote.\n\nThis step was essential because Karma doesn’t have dependency mocking\ncapabilities.\n\nAlso, when mounting the component under test using vue-test-utils, we can\neasily attach it to the document using the\n[`attachToDocument`](https://vue-test-utils.vuejs.org/api/options.html#attachtodocument)\nflag.\n\n\nThe next steps were a mix of migrating Jest specs to vue-test-utils if\nneeded and setting the `attachToDocument` flag to `true`.\n\nThe work ahead was staggering: We had to fix 74 test suites before calling a\nvictory. Many of those specs required significant changes. To avoid\nincreasing the size of the upgrade MR, we opened separate\n\nMRs for each spec file. Another advantage of using different MRs is that it\nenabled us to distribute\n\nthe migration effort among several hands.\n\n\n![upgrading\nbootstrap-vue](https://about.gitlab.com/images/upgrading-bootstrap-vue/failing-spec-list.png)\n\n\nOver time, this strategy became an uphill battle as more failing specs\npopped-up. We were pursuing a moving target, so we decided to\n\nfind an alternative. The reason behind adapting the failing tests to work\nwith the rewritten tooltip is because tests were coupled to the\n\ntooltip implementation. Instead of honoring that coupling, we mocked the\ntooltip dependency to eliminate it. As explained\n\nby [vue test\nutils](https://vue-test-utils.vuejs.org/guides/#shallow-rendering):\n\n\n> In unit tests, we typically want to focus on the component being tested as\nan isolated unit and avoid indirectly asserting the behavior of its child\ncomponents.\n\n\nWe used Jest [manual\nmock](https://jestjs.io/docs/en/manual-mocks#mocking-node-modules) feature\nto replace the tooltip directive\n\nwith a shallow version that only provides essential functionality to avoid\nbreaking tests.\n\n\n```javascript\n\nexport * from '@gitlab/ui';\n\n\nexport const GlTooltipDirective = {\n bind() {},\n};\n\n\nexport const GlTooltip = {\n render(h) {\n return h('div', this.$attrs, this.$slots.default);\n },\n};\n\n```\n\n\n#### Problem 2: Asserting tooltip directive’s internals\n\n\nWe found many unit tests that contained the following assertion pattern:\n\n\n```javascript\n\nexpect(wrapper.attributes('data-original-title')).toContain(statusMessage);\n\n```\n\n\nThe purpose of this assertion is verifying that the content of a tooltip\nattached to a component\n\nis correct. The tooltip directive expects to find its content in the `title`\nattribute of an element.\n\nWhy were we verifying the `data-original-title` attribute then? Before\nbootstrap-vue 2.0, the tooltip\n\ndirective unset the `title` attribute and set `data-original-title` with the\nformer’s value. All\n\nthe specs that asserted the tooltip’s content were coupled to this internal\nbehavior. Once we\n\nupgraded to a version that didn’t follow this behavior, these specs failed.\n\n\nThe solution to this problem is very similar to problem 1’s. First, mocking\nthe tooltip directive\n\nallowed us to decouple unit tests from BV implementation. Afterward, We just\nneeded to replace all\n\n`data-original-title` references with the `title`.\n\n\n## Lessons learned\n\n\n### 1. Technical debt impacts significantly our ability to upgrade\ndependencies\n\n\nThis upgrade was costly because it required fixing hundreds of unit and\nfeature tests. The costs\n\ncould’ve been much lower if we hadn’t had to migrate as many tests to use\njest and vue-test-utils. This\n\nis a palpable reason to schedule time in each milestone to migrate specs\nwritten with legacy\n\npractices to modern ones.\n\n\nThe FE department has organized an effort to migrate legacy specs to use\njest and vue-test-utils in this\n\n[epic](https://gitlab.com/groups/gitlab-org/-/epics/895). The epic also\ncontains guidelines about how to do it.\n\nAll contributions are appreciated.\n\n\n\n### 2. Avoid testing component’s dependencies internals\n\n\nUnit tests should focus as much as possible in the component under test and\nrefrain from making\n\nassumptions about how the component’s dependencies work. The other factor\nthat increased costs\n\ndrastically was finding specs that made assumptions about how the tooltip\ndirective worked under\n\nthe hood. We should strive to mock component’s dependencies to keep unit\ntests focused.\n\n\nThe FE department has opened a [merge\nrequest](https://gitlab.com/gitlab-org/gitlab/merge_requests/18296)\n\nthat proposes using higher-level selectors in unit tests. There are\nscenarios where an integration\n\ntest is more suited, though. We are also [discussing\napproaches](https://gitlab.com/gitlab-org/gitlab/issues/26982)\n\nto have a better distinction between unit and integration tests in the\nfrontend.\n\n\n## Useful resources\n\n\n[Bootstrap-vue 2.0 migration guide and\nchangelogs](https://bootstrap-vue.js.org/docs/misc/changelog)\n\n\n\u003C!-- Content ends here -->\n","unfiltered",{"slug":24,"featured":6,"template":25},"upgrading-bootstrap-vue","BlogPost","content:en-us:blog:upgrading-bootstrap-vue.yml","yaml","Upgrading Bootstrap Vue","content","en-us/blog/upgrading-bootstrap-vue.yml","en-us/blog/upgrading-bootstrap-vue","yml",{"_path":34,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"data":36,"_id":458,"_type":27,"title":459,"_source":29,"_file":460,"_stem":461,"_extension":32},"/shared/en-us/main-navigation","en-us",{"logo":37,"freeTrial":42,"sales":47,"login":52,"items":57,"search":389,"minimal":420,"duo":439,"pricingDeployment":448},{"config":38},{"href":39,"dataGaName":40,"dataGaLocation":41},"/","gitlab logo","header",{"text":43,"config":44},"Get free trial",{"href":45,"dataGaName":46,"dataGaLocation":41},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":48,"config":49},"Talk to sales",{"href":50,"dataGaName":51,"dataGaLocation":41},"/sales/","sales",{"text":53,"config":54},"Sign in",{"href":55,"dataGaName":56,"dataGaLocation":41},"https://gitlab.com/users/sign_in/","sign in",[58,102,200,205,310,370],{"text":59,"config":60,"cards":62,"footer":85},"Platform",{"dataNavLevelOne":61},"platform",[63,69,77],{"title":59,"description":64,"link":65},"The most comprehensive AI-powered DevSecOps Platform",{"text":66,"config":67},"Explore our Platform",{"href":68,"dataGaName":61,"dataGaLocation":41},"/platform/",{"title":70,"description":71,"link":72},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":73,"config":74},"Meet GitLab Duo",{"href":75,"dataGaName":76,"dataGaLocation":41},"/gitlab-duo/","gitlab duo ai",{"title":78,"description":79,"link":80},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":81,"config":82},"Learn more",{"href":83,"dataGaName":84,"dataGaLocation":41},"/why-gitlab/","why gitlab",{"title":86,"items":87},"Get started with",[88,93,98],{"text":89,"config":90},"Platform Engineering",{"href":91,"dataGaName":92,"dataGaLocation":41},"/solutions/platform-engineering/","platform engineering",{"text":94,"config":95},"Developer Experience",{"href":96,"dataGaName":97,"dataGaLocation":41},"/developer-experience/","Developer experience",{"text":99,"config":100},"MLOps",{"href":101,"dataGaName":99,"dataGaLocation":41},"/topics/devops/the-role-of-ai-in-devops/",{"text":103,"left":104,"config":105,"link":107,"lists":111,"footer":182},"Product",true,{"dataNavLevelOne":106},"solutions",{"text":108,"config":109},"View all Solutions",{"href":110,"dataGaName":106,"dataGaLocation":41},"/solutions/",[112,137,161],{"title":113,"description":114,"link":115,"items":120},"Automation","CI/CD and automation to accelerate deployment",{"config":116},{"icon":117,"href":118,"dataGaName":119,"dataGaLocation":41},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[121,125,129,133],{"text":122,"config":123},"CI/CD",{"href":124,"dataGaLocation":41,"dataGaName":122},"/solutions/continuous-integration/",{"text":126,"config":127},"AI-Assisted Development",{"href":75,"dataGaLocation":41,"dataGaName":128},"AI assisted development",{"text":130,"config":131},"Source Code Management",{"href":132,"dataGaLocation":41,"dataGaName":130},"/solutions/source-code-management/",{"text":134,"config":135},"Automated Software Delivery",{"href":118,"dataGaLocation":41,"dataGaName":136},"Automated software delivery",{"title":138,"description":139,"link":140,"items":145},"Security","Deliver code faster without compromising security",{"config":141},{"href":142,"dataGaName":143,"dataGaLocation":41,"icon":144},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[146,151,156],{"text":147,"config":148},"Application Security Testing",{"href":149,"dataGaName":150,"dataGaLocation":41},"/solutions/application-security-testing/","Application security testing",{"text":152,"config":153},"Software Supply Chain Security",{"href":154,"dataGaLocation":41,"dataGaName":155},"/solutions/supply-chain/","Software supply chain security",{"text":157,"config":158},"Software Compliance",{"href":159,"dataGaName":160,"dataGaLocation":41},"/solutions/software-compliance/","software compliance",{"title":162,"link":163,"items":168},"Measurement",{"config":164},{"icon":165,"href":166,"dataGaName":167,"dataGaLocation":41},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[169,173,177],{"text":170,"config":171},"Visibility & Measurement",{"href":166,"dataGaLocation":41,"dataGaName":172},"Visibility and Measurement",{"text":174,"config":175},"Value Stream Management",{"href":176,"dataGaLocation":41,"dataGaName":174},"/solutions/value-stream-management/",{"text":178,"config":179},"Analytics & Insights",{"href":180,"dataGaLocation":41,"dataGaName":181},"/solutions/analytics-and-insights/","Analytics and insights",{"title":183,"items":184},"GitLab for",[185,190,195],{"text":186,"config":187},"Enterprise",{"href":188,"dataGaLocation":41,"dataGaName":189},"/enterprise/","enterprise",{"text":191,"config":192},"Small Business",{"href":193,"dataGaLocation":41,"dataGaName":194},"/small-business/","small business",{"text":196,"config":197},"Public Sector",{"href":198,"dataGaLocation":41,"dataGaName":199},"/solutions/public-sector/","public sector",{"text":201,"config":202},"Pricing",{"href":203,"dataGaName":204,"dataGaLocation":41,"dataNavLevelOne":204},"/pricing/","pricing",{"text":206,"config":207,"link":209,"lists":213,"feature":297},"Resources",{"dataNavLevelOne":208},"resources",{"text":210,"config":211},"View all resources",{"href":212,"dataGaName":208,"dataGaLocation":41},"/resources/",[214,247,269],{"title":215,"items":216},"Getting started",[217,222,227,232,237,242],{"text":218,"config":219},"Install",{"href":220,"dataGaName":221,"dataGaLocation":41},"/install/","install",{"text":223,"config":224},"Quick start guides",{"href":225,"dataGaName":226,"dataGaLocation":41},"/get-started/","quick setup checklists",{"text":228,"config":229},"Learn",{"href":230,"dataGaLocation":41,"dataGaName":231},"https://university.gitlab.com/","learn",{"text":233,"config":234},"Product documentation",{"href":235,"dataGaName":236,"dataGaLocation":41},"https://docs.gitlab.com/","product documentation",{"text":238,"config":239},"Best practice videos",{"href":240,"dataGaName":241,"dataGaLocation":41},"/getting-started-videos/","best practice videos",{"text":243,"config":244},"Integrations",{"href":245,"dataGaName":246,"dataGaLocation":41},"/integrations/","integrations",{"title":248,"items":249},"Discover",[250,255,259,264],{"text":251,"config":252},"Customer success stories",{"href":253,"dataGaName":254,"dataGaLocation":41},"/customers/","customer success stories",{"text":256,"config":257},"Blog",{"href":258,"dataGaName":5,"dataGaLocation":41},"/blog/",{"text":260,"config":261},"Remote",{"href":262,"dataGaName":263,"dataGaLocation":41},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":265,"config":266},"TeamOps",{"href":267,"dataGaName":268,"dataGaLocation":41},"/teamops/","teamops",{"title":270,"items":271},"Connect",[272,277,282,287,292],{"text":273,"config":274},"GitLab Services",{"href":275,"dataGaName":276,"dataGaLocation":41},"/services/","services",{"text":278,"config":279},"Community",{"href":280,"dataGaName":281,"dataGaLocation":41},"/community/","community",{"text":283,"config":284},"Forum",{"href":285,"dataGaName":286,"dataGaLocation":41},"https://forum.gitlab.com/","forum",{"text":288,"config":289},"Events",{"href":290,"dataGaName":291,"dataGaLocation":41},"/events/","events",{"text":293,"config":294},"Partners",{"href":295,"dataGaName":296,"dataGaLocation":41},"/partners/","partners",{"backgroundColor":298,"textColor":299,"text":300,"image":301,"link":305},"#2f2a6b","#fff","Insights for the future of software development",{"altText":302,"config":303},"the source promo card",{"src":304},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":306,"config":307},"Read the latest",{"href":308,"dataGaName":309,"dataGaLocation":41},"/the-source/","the source",{"text":311,"config":312,"lists":314},"Company",{"dataNavLevelOne":313},"company",[315],{"items":316},[317,322,328,330,335,340,345,350,355,360,365],{"text":318,"config":319},"About",{"href":320,"dataGaName":321,"dataGaLocation":41},"/company/","about",{"text":323,"config":324,"footerGa":327},"Jobs",{"href":325,"dataGaName":326,"dataGaLocation":41},"/jobs/","jobs",{"dataGaName":326},{"text":288,"config":329},{"href":290,"dataGaName":291,"dataGaLocation":41},{"text":331,"config":332},"Leadership",{"href":333,"dataGaName":334,"dataGaLocation":41},"/company/team/e-group/","leadership",{"text":336,"config":337},"Team",{"href":338,"dataGaName":339,"dataGaLocation":41},"/company/team/","team",{"text":341,"config":342},"Handbook",{"href":343,"dataGaName":344,"dataGaLocation":41},"https://handbook.gitlab.com/","handbook",{"text":346,"config":347},"Investor relations",{"href":348,"dataGaName":349,"dataGaLocation":41},"https://ir.gitlab.com/","investor relations",{"text":351,"config":352},"Trust Center",{"href":353,"dataGaName":354,"dataGaLocation":41},"/security/","trust center",{"text":356,"config":357},"AI Transparency Center",{"href":358,"dataGaName":359,"dataGaLocation":41},"/ai-transparency-center/","ai transparency center",{"text":361,"config":362},"Newsletter",{"href":363,"dataGaName":364,"dataGaLocation":41},"/company/contact/","newsletter",{"text":366,"config":367},"Press",{"href":368,"dataGaName":369,"dataGaLocation":41},"/press/","press",{"text":371,"config":372,"lists":373},"Contact us",{"dataNavLevelOne":313},[374],{"items":375},[376,379,384],{"text":48,"config":377},{"href":50,"dataGaName":378,"dataGaLocation":41},"talk to sales",{"text":380,"config":381},"Get help",{"href":382,"dataGaName":383,"dataGaLocation":41},"/support/","get help",{"text":385,"config":386},"Customer portal",{"href":387,"dataGaName":388,"dataGaLocation":41},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":390,"login":391,"suggestions":398},"Close",{"text":392,"link":393},"To search repositories and projects, login to",{"text":394,"config":395},"gitlab.com",{"href":55,"dataGaName":396,"dataGaLocation":397},"search login","search",{"text":399,"default":400},"Suggestions",[401,403,407,409,413,417],{"text":70,"config":402},{"href":75,"dataGaName":70,"dataGaLocation":397},{"text":404,"config":405},"Code Suggestions (AI)",{"href":406,"dataGaName":404,"dataGaLocation":397},"/solutions/code-suggestions/",{"text":122,"config":408},{"href":124,"dataGaName":122,"dataGaLocation":397},{"text":410,"config":411},"GitLab on AWS",{"href":412,"dataGaName":410,"dataGaLocation":397},"/partners/technology-partners/aws/",{"text":414,"config":415},"GitLab on Google Cloud",{"href":416,"dataGaName":414,"dataGaLocation":397},"/partners/technology-partners/google-cloud-platform/",{"text":418,"config":419},"Why GitLab?",{"href":83,"dataGaName":418,"dataGaLocation":397},{"freeTrial":421,"mobileIcon":426,"desktopIcon":431,"secondaryButton":434},{"text":422,"config":423},"Start free trial",{"href":424,"dataGaName":46,"dataGaLocation":425},"https://gitlab.com/-/trials/new/","nav",{"altText":427,"config":428},"Gitlab Icon",{"src":429,"dataGaName":430,"dataGaLocation":425},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":427,"config":432},{"src":433,"dataGaName":430,"dataGaLocation":425},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":435,"config":436},"Get Started",{"href":437,"dataGaName":438,"dataGaLocation":425},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":440,"mobileIcon":444,"desktopIcon":446},{"text":441,"config":442},"Learn more about GitLab Duo",{"href":75,"dataGaName":443,"dataGaLocation":425},"gitlab duo",{"altText":427,"config":445},{"src":429,"dataGaName":430,"dataGaLocation":425},{"altText":427,"config":447},{"src":433,"dataGaName":430,"dataGaLocation":425},{"freeTrial":449,"mobileIcon":454,"desktopIcon":456},{"text":450,"config":451},"Back to pricing",{"href":203,"dataGaName":452,"dataGaLocation":425,"icon":453},"back to pricing","GoBack",{"altText":427,"config":455},{"src":429,"dataGaName":430,"dataGaLocation":425},{"altText":427,"config":457},{"src":433,"dataGaName":430,"dataGaLocation":425},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":463,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"title":464,"button":465,"image":470,"config":474,"_id":476,"_type":27,"_source":29,"_file":477,"_stem":478,"_extension":32},"/shared/en-us/banner","is now in public beta!",{"text":466,"config":467},"Try the Beta",{"href":468,"dataGaName":469,"dataGaLocation":41},"/gitlab-duo/agent-platform/","duo banner",{"altText":471,"config":472},"GitLab Duo Agent Platform",{"src":473},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":475},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":480,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"data":481,"_id":685,"_type":27,"title":686,"_source":29,"_file":687,"_stem":688,"_extension":32},"/shared/en-us/main-footer",{"text":482,"source":483,"edit":489,"contribute":494,"config":499,"items":504,"minimal":677},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":484,"config":485},"View page source",{"href":486,"dataGaName":487,"dataGaLocation":488},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":490,"config":491},"Edit this page",{"href":492,"dataGaName":493,"dataGaLocation":488},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":495,"config":496},"Please contribute",{"href":497,"dataGaName":498,"dataGaLocation":488},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":500,"facebook":501,"youtube":502,"linkedin":503},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[505,528,584,613,647],{"title":59,"links":506,"subMenu":511},[507],{"text":508,"config":509},"DevSecOps platform",{"href":68,"dataGaName":510,"dataGaLocation":488},"devsecops platform",[512],{"title":201,"links":513},[514,518,523],{"text":515,"config":516},"View plans",{"href":203,"dataGaName":517,"dataGaLocation":488},"view plans",{"text":519,"config":520},"Why Premium?",{"href":521,"dataGaName":522,"dataGaLocation":488},"/pricing/premium/","why premium",{"text":524,"config":525},"Why Ultimate?",{"href":526,"dataGaName":527,"dataGaLocation":488},"/pricing/ultimate/","why ultimate",{"title":529,"links":530},"Solutions",[531,536,538,540,545,550,554,557,561,566,568,571,574,579],{"text":532,"config":533},"Digital transformation",{"href":534,"dataGaName":535,"dataGaLocation":488},"/topics/digital-transformation/","digital transformation",{"text":147,"config":537},{"href":149,"dataGaName":147,"dataGaLocation":488},{"text":136,"config":539},{"href":118,"dataGaName":119,"dataGaLocation":488},{"text":541,"config":542},"Agile development",{"href":543,"dataGaName":544,"dataGaLocation":488},"/solutions/agile-delivery/","agile delivery",{"text":546,"config":547},"Cloud transformation",{"href":548,"dataGaName":549,"dataGaLocation":488},"/topics/cloud-native/","cloud transformation",{"text":551,"config":552},"SCM",{"href":132,"dataGaName":553,"dataGaLocation":488},"source code management",{"text":122,"config":555},{"href":124,"dataGaName":556,"dataGaLocation":488},"continuous integration & delivery",{"text":558,"config":559},"Value stream management",{"href":176,"dataGaName":560,"dataGaLocation":488},"value stream management",{"text":562,"config":563},"GitOps",{"href":564,"dataGaName":565,"dataGaLocation":488},"/solutions/gitops/","gitops",{"text":186,"config":567},{"href":188,"dataGaName":189,"dataGaLocation":488},{"text":569,"config":570},"Small business",{"href":193,"dataGaName":194,"dataGaLocation":488},{"text":572,"config":573},"Public sector",{"href":198,"dataGaName":199,"dataGaLocation":488},{"text":575,"config":576},"Education",{"href":577,"dataGaName":578,"dataGaLocation":488},"/solutions/education/","education",{"text":580,"config":581},"Financial services",{"href":582,"dataGaName":583,"dataGaLocation":488},"/solutions/finance/","financial services",{"title":206,"links":585},[586,588,590,592,595,597,599,601,603,605,607,609,611],{"text":218,"config":587},{"href":220,"dataGaName":221,"dataGaLocation":488},{"text":223,"config":589},{"href":225,"dataGaName":226,"dataGaLocation":488},{"text":228,"config":591},{"href":230,"dataGaName":231,"dataGaLocation":488},{"text":233,"config":593},{"href":235,"dataGaName":594,"dataGaLocation":488},"docs",{"text":256,"config":596},{"href":258,"dataGaName":5,"dataGaLocation":488},{"text":251,"config":598},{"href":253,"dataGaName":254,"dataGaLocation":488},{"text":260,"config":600},{"href":262,"dataGaName":263,"dataGaLocation":488},{"text":273,"config":602},{"href":275,"dataGaName":276,"dataGaLocation":488},{"text":265,"config":604},{"href":267,"dataGaName":268,"dataGaLocation":488},{"text":278,"config":606},{"href":280,"dataGaName":281,"dataGaLocation":488},{"text":283,"config":608},{"href":285,"dataGaName":286,"dataGaLocation":488},{"text":288,"config":610},{"href":290,"dataGaName":291,"dataGaLocation":488},{"text":293,"config":612},{"href":295,"dataGaName":296,"dataGaLocation":488},{"title":311,"links":614},[615,617,619,621,623,625,627,631,636,638,640,642],{"text":318,"config":616},{"href":320,"dataGaName":313,"dataGaLocation":488},{"text":323,"config":618},{"href":325,"dataGaName":326,"dataGaLocation":488},{"text":331,"config":620},{"href":333,"dataGaName":334,"dataGaLocation":488},{"text":336,"config":622},{"href":338,"dataGaName":339,"dataGaLocation":488},{"text":341,"config":624},{"href":343,"dataGaName":344,"dataGaLocation":488},{"text":346,"config":626},{"href":348,"dataGaName":349,"dataGaLocation":488},{"text":628,"config":629},"Sustainability",{"href":630,"dataGaName":628,"dataGaLocation":488},"/sustainability/",{"text":632,"config":633},"Diversity, inclusion and belonging (DIB)",{"href":634,"dataGaName":635,"dataGaLocation":488},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":351,"config":637},{"href":353,"dataGaName":354,"dataGaLocation":488},{"text":361,"config":639},{"href":363,"dataGaName":364,"dataGaLocation":488},{"text":366,"config":641},{"href":368,"dataGaName":369,"dataGaLocation":488},{"text":643,"config":644},"Modern Slavery Transparency Statement",{"href":645,"dataGaName":646,"dataGaLocation":488},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":648,"links":649},"Contact Us",[650,653,655,657,662,667,672],{"text":651,"config":652},"Contact an expert",{"href":50,"dataGaName":51,"dataGaLocation":488},{"text":380,"config":654},{"href":382,"dataGaName":383,"dataGaLocation":488},{"text":385,"config":656},{"href":387,"dataGaName":388,"dataGaLocation":488},{"text":658,"config":659},"Status",{"href":660,"dataGaName":661,"dataGaLocation":488},"https://status.gitlab.com/","status",{"text":663,"config":664},"Terms of use",{"href":665,"dataGaName":666,"dataGaLocation":488},"/terms/","terms of use",{"text":668,"config":669},"Privacy statement",{"href":670,"dataGaName":671,"dataGaLocation":488},"/privacy/","privacy statement",{"text":673,"config":674},"Cookie preferences",{"dataGaName":675,"dataGaLocation":488,"id":676,"isOneTrustButton":104},"cookie preferences","ot-sdk-btn",{"items":678},[679,681,683],{"text":663,"config":680},{"href":665,"dataGaName":666,"dataGaLocation":488},{"text":668,"config":682},{"href":670,"dataGaName":671,"dataGaLocation":488},{"text":673,"config":684},{"dataGaName":675,"dataGaLocation":488,"id":676,"isOneTrustButton":104},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[690,703],{"_path":691,"_dir":692,"_draft":6,"_partial":6,"_locale":7,"content":693,"config":697,"_id":699,"_type":27,"title":700,"_source":29,"_file":701,"_stem":702,"_extension":32},"/en-us/blog/authors/enrique-alcntara","authors",{"name":18,"config":694},{"headshot":695,"ctfId":696},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669746/Blog/Author%20Headshots/ealcantara-headshot.jpg","3E3c30ZWRUTq6rlFiYrjtq",{"template":698},"BlogAuthor","content:en-us:blog:authors:enrique-alcntara.yml","Enrique Alcntara","en-us/blog/authors/enrique-alcntara.yml","en-us/blog/authors/enrique-alcntara",{"_path":704,"_dir":692,"_draft":6,"_partial":6,"_locale":7,"content":705,"config":709,"_id":710,"_type":27,"title":711,"_source":29,"_file":712,"_stem":713,"_extension":32},"/en-us/blog/authors/paul-gascou-vaillancourt",{"name":19,"config":706},{"headshot":707,"ctfId":708},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","6Yg7HWPoy2E5vwudH0EZja",{"template":698},"content:en-us:blog:authors:paul-gascou-vaillancourt.yml","Paul Gascou Vaillancourt","en-us/blog/authors/paul-gascou-vaillancourt.yml","en-us/blog/authors/paul-gascou-vaillancourt",{"_path":715,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"header":716,"eyebrow":717,"blurb":718,"button":719,"secondaryButton":723,"_id":725,"_type":27,"title":726,"_source":29,"_file":727,"_stem":728,"_extension":32},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":43,"config":720},{"href":721,"dataGaName":46,"dataGaLocation":722},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":48,"config":724},{"href":50,"dataGaName":51,"dataGaLocation":722},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1758326229952]