[{"data":1,"prerenderedAt":1035},["ShallowReactive",2],{"/en-us/blog/tags/growth/":3,"navigation-en-us":19,"banner-en-us":437,"footer-en-us":449,"growth-tag-page-en-us":660},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"content":8,"config":10,"_id":12,"_type":13,"title":14,"_source":15,"_file":16,"_stem":17,"_extension":18},"/en-us/blog/tags/growth","tags",false,"",{"tag":9,"tagSlug":9},"growth",{"template":11},"BlogTag","content:en-us:blog:tags:growth.yml","yaml","Growth","content","en-us/blog/tags/growth.yml","en-us/blog/tags/growth","yml",{"_path":20,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"data":22,"_id":433,"_type":13,"title":434,"_source":15,"_file":435,"_stem":436,"_extension":18},"/shared/en-us/main-navigation","en-us",{"logo":23,"freeTrial":28,"sales":33,"login":38,"items":43,"search":374,"minimal":405,"duo":424},{"config":24},{"href":25,"dataGaName":26,"dataGaLocation":27},"/","gitlab logo","header",{"text":29,"config":30},"Get free trial",{"href":31,"dataGaName":32,"dataGaLocation":27},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":34,"config":35},"Talk to sales",{"href":36,"dataGaName":37,"dataGaLocation":27},"/sales/","sales",{"text":39,"config":40},"Sign in",{"href":41,"dataGaName":42,"dataGaLocation":27},"https://gitlab.com/users/sign_in/","sign in",[44,88,184,189,295,355],{"text":45,"config":46,"cards":48,"footer":71},"Platform",{"dataNavLevelOne":47},"platform",[49,55,63],{"title":45,"description":50,"link":51},"The most comprehensive AI-powered DevSecOps Platform",{"text":52,"config":53},"Explore our Platform",{"href":54,"dataGaName":47,"dataGaLocation":27},"/platform/",{"title":56,"description":57,"link":58},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":59,"config":60},"Meet GitLab Duo",{"href":61,"dataGaName":62,"dataGaLocation":27},"/gitlab-duo/","gitlab duo ai",{"title":64,"description":65,"link":66},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":67,"config":68},"Learn more",{"href":69,"dataGaName":70,"dataGaLocation":27},"/why-gitlab/","why gitlab",{"title":72,"items":73},"Get started with",[74,79,84],{"text":75,"config":76},"Platform Engineering",{"href":77,"dataGaName":78,"dataGaLocation":27},"/solutions/platform-engineering/","platform engineering",{"text":80,"config":81},"Developer Experience",{"href":82,"dataGaName":83,"dataGaLocation":27},"/developer-experience/","Developer experience",{"text":85,"config":86},"MLOps",{"href":87,"dataGaName":85,"dataGaLocation":27},"/topics/devops/the-role-of-ai-in-devops/",{"text":89,"left":90,"config":91,"link":93,"lists":97,"footer":166},"Product",true,{"dataNavLevelOne":92},"solutions",{"text":94,"config":95},"View all Solutions",{"href":96,"dataGaName":92,"dataGaLocation":27},"/solutions/",[98,123,145],{"title":99,"description":100,"link":101,"items":106},"Automation","CI/CD and automation to accelerate deployment",{"config":102},{"icon":103,"href":104,"dataGaName":105,"dataGaLocation":27},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[107,111,115,119],{"text":108,"config":109},"CI/CD",{"href":110,"dataGaLocation":27,"dataGaName":108},"/solutions/continuous-integration/",{"text":112,"config":113},"AI-Assisted Development",{"href":61,"dataGaLocation":27,"dataGaName":114},"AI assisted development",{"text":116,"config":117},"Source Code Management",{"href":118,"dataGaLocation":27,"dataGaName":116},"/solutions/source-code-management/",{"text":120,"config":121},"Automated Software Delivery",{"href":104,"dataGaLocation":27,"dataGaName":122},"Automated software delivery",{"title":124,"description":125,"link":126,"items":131},"Security","Deliver code faster without compromising security",{"config":127},{"href":128,"dataGaName":129,"dataGaLocation":27,"icon":130},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[132,135,140],{"text":133,"config":134},"Security & Compliance",{"href":128,"dataGaLocation":27,"dataGaName":133},{"text":136,"config":137},"Software Supply Chain Security",{"href":138,"dataGaLocation":27,"dataGaName":139},"/solutions/supply-chain/","Software supply chain security",{"text":141,"config":142},"Compliance & Governance",{"href":143,"dataGaLocation":27,"dataGaName":144},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":146,"link":147,"items":152},"Measurement",{"config":148},{"icon":149,"href":150,"dataGaName":151,"dataGaLocation":27},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[153,157,161],{"text":154,"config":155},"Visibility & Measurement",{"href":150,"dataGaLocation":27,"dataGaName":156},"Visibility and Measurement",{"text":158,"config":159},"Value Stream Management",{"href":160,"dataGaLocation":27,"dataGaName":158},"/solutions/value-stream-management/",{"text":162,"config":163},"Analytics & Insights",{"href":164,"dataGaLocation":27,"dataGaName":165},"/solutions/analytics-and-insights/","Analytics and insights",{"title":167,"items":168},"GitLab for",[169,174,179],{"text":170,"config":171},"Enterprise",{"href":172,"dataGaLocation":27,"dataGaName":173},"/enterprise/","enterprise",{"text":175,"config":176},"Small Business",{"href":177,"dataGaLocation":27,"dataGaName":178},"/small-business/","small business",{"text":180,"config":181},"Public Sector",{"href":182,"dataGaLocation":27,"dataGaName":183},"/solutions/public-sector/","public sector",{"text":185,"config":186},"Pricing",{"href":187,"dataGaName":188,"dataGaLocation":27,"dataNavLevelOne":188},"/pricing/","pricing",{"text":190,"config":191,"link":193,"lists":197,"feature":282},"Resources",{"dataNavLevelOne":192},"resources",{"text":194,"config":195},"View all resources",{"href":196,"dataGaName":192,"dataGaLocation":27},"/resources/",[198,231,254],{"title":199,"items":200},"Getting started",[201,206,211,216,221,226],{"text":202,"config":203},"Install",{"href":204,"dataGaName":205,"dataGaLocation":27},"/install/","install",{"text":207,"config":208},"Quick start guides",{"href":209,"dataGaName":210,"dataGaLocation":27},"/get-started/","quick setup checklists",{"text":212,"config":213},"Learn",{"href":214,"dataGaLocation":27,"dataGaName":215},"https://university.gitlab.com/","learn",{"text":217,"config":218},"Product documentation",{"href":219,"dataGaName":220,"dataGaLocation":27},"https://docs.gitlab.com/","product documentation",{"text":222,"config":223},"Best practice videos",{"href":224,"dataGaName":225,"dataGaLocation":27},"/getting-started-videos/","best practice videos",{"text":227,"config":228},"Integrations",{"href":229,"dataGaName":230,"dataGaLocation":27},"/integrations/","integrations",{"title":232,"items":233},"Discover",[234,239,244,249],{"text":235,"config":236},"Customer success stories",{"href":237,"dataGaName":238,"dataGaLocation":27},"/customers/","customer success stories",{"text":240,"config":241},"Blog",{"href":242,"dataGaName":243,"dataGaLocation":27},"/blog/","blog",{"text":245,"config":246},"Remote",{"href":247,"dataGaName":248,"dataGaLocation":27},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":250,"config":251},"TeamOps",{"href":252,"dataGaName":253,"dataGaLocation":27},"/teamops/","teamops",{"title":255,"items":256},"Connect",[257,262,267,272,277],{"text":258,"config":259},"GitLab Services",{"href":260,"dataGaName":261,"dataGaLocation":27},"/services/","services",{"text":263,"config":264},"Community",{"href":265,"dataGaName":266,"dataGaLocation":27},"/community/","community",{"text":268,"config":269},"Forum",{"href":270,"dataGaName":271,"dataGaLocation":27},"https://forum.gitlab.com/","forum",{"text":273,"config":274},"Events",{"href":275,"dataGaName":276,"dataGaLocation":27},"/events/","events",{"text":278,"config":279},"Partners",{"href":280,"dataGaName":281,"dataGaLocation":27},"/partners/","partners",{"backgroundColor":283,"textColor":284,"text":285,"image":286,"link":290},"#2f2a6b","#fff","Insights for the future of software development",{"altText":287,"config":288},"the source promo card",{"src":289},"/images/navigation/the-source-promo-card.svg",{"text":291,"config":292},"Read the latest",{"href":293,"dataGaName":294,"dataGaLocation":27},"/the-source/","the source",{"text":296,"config":297,"lists":299},"Company",{"dataNavLevelOne":298},"company",[300],{"items":301},[302,307,313,315,320,325,330,335,340,345,350],{"text":303,"config":304},"About",{"href":305,"dataGaName":306,"dataGaLocation":27},"/company/","about",{"text":308,"config":309,"footerGa":312},"Jobs",{"href":310,"dataGaName":311,"dataGaLocation":27},"/jobs/","jobs",{"dataGaName":311},{"text":273,"config":314},{"href":275,"dataGaName":276,"dataGaLocation":27},{"text":316,"config":317},"Leadership",{"href":318,"dataGaName":319,"dataGaLocation":27},"/company/team/e-group/","leadership",{"text":321,"config":322},"Team",{"href":323,"dataGaName":324,"dataGaLocation":27},"/company/team/","team",{"text":326,"config":327},"Handbook",{"href":328,"dataGaName":329,"dataGaLocation":27},"https://handbook.gitlab.com/","handbook",{"text":331,"config":332},"Investor relations",{"href":333,"dataGaName":334,"dataGaLocation":27},"https://ir.gitlab.com/","investor relations",{"text":336,"config":337},"Trust Center",{"href":338,"dataGaName":339,"dataGaLocation":27},"/security/","trust center",{"text":341,"config":342},"AI Transparency Center",{"href":343,"dataGaName":344,"dataGaLocation":27},"/ai-transparency-center/","ai transparency center",{"text":346,"config":347},"Newsletter",{"href":348,"dataGaName":349,"dataGaLocation":27},"/company/contact/","newsletter",{"text":351,"config":352},"Press",{"href":353,"dataGaName":354,"dataGaLocation":27},"/press/","press",{"text":356,"config":357,"lists":358},"Contact us",{"dataNavLevelOne":298},[359],{"items":360},[361,364,369],{"text":34,"config":362},{"href":36,"dataGaName":363,"dataGaLocation":27},"talk to sales",{"text":365,"config":366},"Get help",{"href":367,"dataGaName":368,"dataGaLocation":27},"/support/","get help",{"text":370,"config":371},"Customer portal",{"href":372,"dataGaName":373,"dataGaLocation":27},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":375,"login":376,"suggestions":383},"Close",{"text":377,"link":378},"To search repositories and projects, login to",{"text":379,"config":380},"gitlab.com",{"href":41,"dataGaName":381,"dataGaLocation":382},"search login","search",{"text":384,"default":385},"Suggestions",[386,388,392,394,398,402],{"text":56,"config":387},{"href":61,"dataGaName":56,"dataGaLocation":382},{"text":389,"config":390},"Code Suggestions (AI)",{"href":391,"dataGaName":389,"dataGaLocation":382},"/solutions/code-suggestions/",{"text":108,"config":393},{"href":110,"dataGaName":108,"dataGaLocation":382},{"text":395,"config":396},"GitLab on AWS",{"href":397,"dataGaName":395,"dataGaLocation":382},"/partners/technology-partners/aws/",{"text":399,"config":400},"GitLab on Google Cloud",{"href":401,"dataGaName":399,"dataGaLocation":382},"/partners/technology-partners/google-cloud-platform/",{"text":403,"config":404},"Why GitLab?",{"href":69,"dataGaName":403,"dataGaLocation":382},{"freeTrial":406,"mobileIcon":411,"desktopIcon":416,"secondaryButton":419},{"text":407,"config":408},"Start free trial",{"href":409,"dataGaName":32,"dataGaLocation":410},"https://gitlab.com/-/trials/new/","nav",{"altText":412,"config":413},"Gitlab Icon",{"src":414,"dataGaName":415,"dataGaLocation":410},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":412,"config":417},{"src":418,"dataGaName":415,"dataGaLocation":410},"/images/brand/gitlab-logo-type.svg",{"text":420,"config":421},"Get Started",{"href":422,"dataGaName":423,"dataGaLocation":410},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":425,"mobileIcon":429,"desktopIcon":431},{"text":426,"config":427},"Learn more about GitLab Duo",{"href":61,"dataGaName":428,"dataGaLocation":410},"gitlab duo",{"altText":412,"config":430},{"src":414,"dataGaName":415,"dataGaLocation":410},{"altText":412,"config":432},{"src":418,"dataGaName":415,"dataGaLocation":410},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":438,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"title":439,"button":440,"config":444,"_id":446,"_type":13,"_source":15,"_file":447,"_stem":448,"_extension":18},"/shared/en-us/banner","GitLab Duo Agent Platform is now in public beta!",{"text":67,"config":441},{"href":442,"dataGaName":443,"dataGaLocation":27},"/gitlab-duo/agent-platform/","duo banner",{"layout":445},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":450,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"data":451,"_id":656,"_type":13,"title":657,"_source":15,"_file":658,"_stem":659,"_extension":18},"/shared/en-us/main-footer",{"text":452,"source":453,"edit":459,"contribute":464,"config":469,"items":474,"minimal":648},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":454,"config":455},"View page source",{"href":456,"dataGaName":457,"dataGaLocation":458},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":460,"config":461},"Edit this page",{"href":462,"dataGaName":463,"dataGaLocation":458},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":465,"config":466},"Please contribute",{"href":467,"dataGaName":468,"dataGaLocation":458},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":470,"facebook":471,"youtube":472,"linkedin":473},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[475,498,555,584,618],{"title":45,"links":476,"subMenu":481},[477],{"text":478,"config":479},"DevSecOps platform",{"href":54,"dataGaName":480,"dataGaLocation":458},"devsecops platform",[482],{"title":185,"links":483},[484,488,493],{"text":485,"config":486},"View plans",{"href":187,"dataGaName":487,"dataGaLocation":458},"view plans",{"text":489,"config":490},"Why Premium?",{"href":491,"dataGaName":492,"dataGaLocation":458},"/pricing/premium/","why premium",{"text":494,"config":495},"Why Ultimate?",{"href":496,"dataGaName":497,"dataGaLocation":458},"/pricing/ultimate/","why ultimate",{"title":499,"links":500},"Solutions",[501,506,509,511,516,521,525,528,532,537,539,542,545,550],{"text":502,"config":503},"Digital transformation",{"href":504,"dataGaName":505,"dataGaLocation":458},"/topics/digital-transformation/","digital transformation",{"text":133,"config":507},{"href":128,"dataGaName":508,"dataGaLocation":458},"security & compliance",{"text":122,"config":510},{"href":104,"dataGaName":105,"dataGaLocation":458},{"text":512,"config":513},"Agile development",{"href":514,"dataGaName":515,"dataGaLocation":458},"/solutions/agile-delivery/","agile delivery",{"text":517,"config":518},"Cloud transformation",{"href":519,"dataGaName":520,"dataGaLocation":458},"/topics/cloud-native/","cloud transformation",{"text":522,"config":523},"SCM",{"href":118,"dataGaName":524,"dataGaLocation":458},"source code management",{"text":108,"config":526},{"href":110,"dataGaName":527,"dataGaLocation":458},"continuous integration & delivery",{"text":529,"config":530},"Value stream management",{"href":160,"dataGaName":531,"dataGaLocation":458},"value stream management",{"text":533,"config":534},"GitOps",{"href":535,"dataGaName":536,"dataGaLocation":458},"/solutions/gitops/","gitops",{"text":170,"config":538},{"href":172,"dataGaName":173,"dataGaLocation":458},{"text":540,"config":541},"Small business",{"href":177,"dataGaName":178,"dataGaLocation":458},{"text":543,"config":544},"Public sector",{"href":182,"dataGaName":183,"dataGaLocation":458},{"text":546,"config":547},"Education",{"href":548,"dataGaName":549,"dataGaLocation":458},"/solutions/education/","education",{"text":551,"config":552},"Financial services",{"href":553,"dataGaName":554,"dataGaLocation":458},"/solutions/finance/","financial services",{"title":190,"links":556},[557,559,561,563,566,568,570,572,574,576,578,580,582],{"text":202,"config":558},{"href":204,"dataGaName":205,"dataGaLocation":458},{"text":207,"config":560},{"href":209,"dataGaName":210,"dataGaLocation":458},{"text":212,"config":562},{"href":214,"dataGaName":215,"dataGaLocation":458},{"text":217,"config":564},{"href":219,"dataGaName":565,"dataGaLocation":458},"docs",{"text":240,"config":567},{"href":242,"dataGaName":243,"dataGaLocation":458},{"text":235,"config":569},{"href":237,"dataGaName":238,"dataGaLocation":458},{"text":245,"config":571},{"href":247,"dataGaName":248,"dataGaLocation":458},{"text":258,"config":573},{"href":260,"dataGaName":261,"dataGaLocation":458},{"text":250,"config":575},{"href":252,"dataGaName":253,"dataGaLocation":458},{"text":263,"config":577},{"href":265,"dataGaName":266,"dataGaLocation":458},{"text":268,"config":579},{"href":270,"dataGaName":271,"dataGaLocation":458},{"text":273,"config":581},{"href":275,"dataGaName":276,"dataGaLocation":458},{"text":278,"config":583},{"href":280,"dataGaName":281,"dataGaLocation":458},{"title":296,"links":585},[586,588,590,592,594,596,598,602,607,609,611,613],{"text":303,"config":587},{"href":305,"dataGaName":298,"dataGaLocation":458},{"text":308,"config":589},{"href":310,"dataGaName":311,"dataGaLocation":458},{"text":316,"config":591},{"href":318,"dataGaName":319,"dataGaLocation":458},{"text":321,"config":593},{"href":323,"dataGaName":324,"dataGaLocation":458},{"text":326,"config":595},{"href":328,"dataGaName":329,"dataGaLocation":458},{"text":331,"config":597},{"href":333,"dataGaName":334,"dataGaLocation":458},{"text":599,"config":600},"Sustainability",{"href":601,"dataGaName":599,"dataGaLocation":458},"/sustainability/",{"text":603,"config":604},"Diversity, inclusion and belonging (DIB)",{"href":605,"dataGaName":606,"dataGaLocation":458},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":336,"config":608},{"href":338,"dataGaName":339,"dataGaLocation":458},{"text":346,"config":610},{"href":348,"dataGaName":349,"dataGaLocation":458},{"text":351,"config":612},{"href":353,"dataGaName":354,"dataGaLocation":458},{"text":614,"config":615},"Modern Slavery Transparency Statement",{"href":616,"dataGaName":617,"dataGaLocation":458},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":619,"links":620},"Contact Us",[621,624,626,628,633,638,643],{"text":622,"config":623},"Contact an expert",{"href":36,"dataGaName":37,"dataGaLocation":458},{"text":365,"config":625},{"href":367,"dataGaName":368,"dataGaLocation":458},{"text":370,"config":627},{"href":372,"dataGaName":373,"dataGaLocation":458},{"text":629,"config":630},"Status",{"href":631,"dataGaName":632,"dataGaLocation":458},"https://status.gitlab.com/","status",{"text":634,"config":635},"Terms of use",{"href":636,"dataGaName":637,"dataGaLocation":458},"/terms/","terms of use",{"text":639,"config":640},"Privacy statement",{"href":641,"dataGaName":642,"dataGaLocation":458},"/privacy/","privacy statement",{"text":644,"config":645},"Cookie preferences",{"dataGaName":646,"dataGaLocation":458,"id":647,"isOneTrustButton":90},"cookie preferences","ot-sdk-btn",{"items":649},[650,652,654],{"text":634,"config":651},{"href":636,"dataGaName":637,"dataGaLocation":458},{"text":639,"config":653},{"href":641,"dataGaName":642,"dataGaLocation":458},{"text":644,"config":655},{"dataGaName":646,"dataGaLocation":458,"id":647,"isOneTrustButton":90},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",{"allPosts":661,"featuredPost":1013,"totalPagesCount":1033,"initialPosts":1034},[662,686,711,735,753,773,792,816,839,861,881,900,919,937,955,975,995],{"_path":663,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":664,"content":672,"config":679,"_id":682,"_type":13,"title":683,"_source":15,"_file":684,"_stem":685,"_extension":18},"/en-us/blog/certification-discount-code-debrief",{"title":665,"description":666,"ogTitle":665,"ogDescription":666,"noIndex":6,"ogImage":667,"ogUrl":668,"ogSiteName":669,"ogType":670,"canonicalUrls":668,"schema":671},"Why we ended our free discount code early","Debrief on our certification discount code policy change.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663397/Blog/Hero%20Images/logoforblogpost.jpg","https://about.gitlab.com/blog/certification-discount-code-debrief","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Why we ended our free discount code early\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Christine Yoshida\"}],\n        \"datePublished\": \"2021-05-05\",\n      }",{"title":665,"description":666,"authors":673,"heroImage":667,"date":675,"body":676,"category":677,"tags":678},[674],"Christine Yoshida","2021-05-05","\n\n**This blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2021-05-07.**\n\nWe are amazed at the sheer number of people who particpated in GitLab's 10-day certification offer of a 100% discount. In just two days there were more than 60,000 people who created an account in [GitLab Learn](/learn/), more than 8,000 people enrolled in the GitLab Certified Associate Self-Service pathway, more than 6,000 people started the hands-on labs, and more than 500 submitted your completed hands-on lab for the certification exam. We were able to award certifications to hundreds of people during that time!\n\nWe love your enthusiasm and excitement about earning a GitLab certification, which you expressed on everything from social media posts to video walkthroughs on YouTube. Unfortunately, we had only anticipated about 4,000 users for this 10-day program and the systems behind the scenes supporting the learning experience were unable to keep up with the sudden spike in the number of users. We eventually exceeded the user capacity limit on our third-party learning management platform and our internal hands-on training lab infrastructure. This is separate from our GitLab.com SaaS infrastructure. _GitLab SaaS customers were not impacted._ Due to the user capacity limit, we had to make the difficult decision to end the discount period much sooner than we had planned. We are working to autoscale our training systems to support the demand.\n\nFor those of you who were excited to take advantage of the discount and thought you had more time to take advantage of it, we are very sorry to have built up your excitement and then let you down - please accept our sincere apologies. If you have already redeemed the discount code and hands-on lab invitation code, please continue to complete your coursework in GitLab Learn and submit your project to us.\n\nOver the coming days and weeks we will determine both short-term and long-term solutions to provide improved capacity. As we iterate and scale GitLab Learn, we'll be incorporating the lessons we learned with this event.\n\nThere is still plenty of free learning in GitLab Learn including [Gitlab 101](/handbook/people-group/learning-and-development/gitlab-101/), [GitLab 201](/handbook/people-group/learning-and-development/gitlab-201/), [Remote Work,](/company/culture/all-remote/remote-certification/) and [DIB badges](/company/culture/inclusion/dib-training/). We hope you'll continue to use GitLab Learn and visit often to check out new offerings as they become available.\n\n## Your comments are welcome here!\n\nTell us how we could have done better.\n","news",[266,9,677],{"slug":680,"featured":6,"template":681},"certification-discount-code-debrief","BlogPost","content:en-us:blog:certification-discount-code-debrief.yml","Certification Discount Code Debrief","en-us/blog/certification-discount-code-debrief.yml","en-us/blog/certification-discount-code-debrief",{"_path":687,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":688,"content":694,"config":705,"_id":707,"_type":13,"title":708,"_source":15,"_file":709,"_stem":710,"_extension":18},"/en-us/blog/configuring-your-cluster-with-kubernetes-integration",{"title":689,"description":690,"ogTitle":689,"ogDescription":690,"noIndex":6,"ogImage":691,"ogUrl":692,"ogSiteName":669,"ogType":670,"canonicalUrls":692,"schema":693},"Heroes journey: Working with GitLab's Kubernetes agent","A tutorial on deploying and monitoring an application in Kubernetes without leaving GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682342/Blog/Hero%20Images/treasure.jpg","https://about.gitlab.com/blog/configuring-your-cluster-with-kubernetes-integration","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab Heroes Unmasked - How I became acquainted with the GitLab Agent for Kubernetes\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jean-Philippe Baconnais\"}],\n        \"datePublished\": \"2022-06-08\",\n      }",{"title":695,"description":690,"authors":696,"heroImage":691,"date":698,"body":699,"category":700,"tags":701},"GitLab Heroes Unmasked - How I became acquainted with the GitLab Agent for Kubernetes",[697],"Jean-Philippe Baconnais","2022-06-08","\n\n_A key to GitLab’s success is our vast community of advocates. Here at GitLab, we call these active contributors \"[GitLab Heroes](/community/heroes/).\" Each hero contributes to GitLab in numerous ways, including elevating releases, sharing best practices, speaking at events, and more. Jean-Phillippe Baconnais is an active GitLab Hero, who hails from France. We applaud his contributions, including leading community engagement events. Baconnais shares his interest in Kubernetes and explains how to deploy and monitor an application in Kubernetes without leaving GitLab._ \n\nSince 2007, I’ve been a developer. I’ve learned a lot of things about continuous integration, deployment, infrastructure, and monitoring. In both my professional and personal time, my favorite activity remains software development. After creating a new application with multiple components, I wanted to deploy it on Kubernetes, which has been really famous over the last few years. This allows me to experiment on this platform. This announces a lot of very funny things. I know some terms, I used them in production for five years. But as a user, Kubernetes Administration is not my “cup of tea” 😅.\n\n## My first deployment in Kubernetes\n\nWhen I decided to deploy an application on Kubernetes, I wasn’t sure where to start until I saw, navigating in my project in GitLab, a menu called “Kubernetes.\" I wanted to know what GitLab was hiding behind this. Does this feature link my project’s sources to a Kubernetes cluster? I used the credit offered by Google Cloud to discover and test this platform. \n\nDeploying my application on Kubernetes was easy. I wrote [a blog post](https://dev.to/jphi_baconnais/deploy-an-quarkus-application-on-gke-with-gitlabci-lgp) in 2019 describing how I do this, or rather, how GitLab helped me to create this link so easily. In this blog post I will explain further and talk about what’s changed since then.\n\nBehind the “Kubernetes” menu, GitLab helps you integrate Kubernetes into your project. You can create, from GitLab, a cluster on Google Cloud Platform (GCP), and Amazon Web Services (AWS). If you already have a cluster on this platform or anywhere else, you can connect to it. You just need to specify the cluster name, Kubernetes API UR, and certificate.\n\n![Connect cluster](https://about.gitlab.com/images/blogimages/baconcreatecluster.png){: .shadow}\n\nGitLab is a DevOps platform and in the list of DevOps actions, there is the monitoring part. \n\n![Chart of GitLab stages](https://about.gitlab.com/images/blogimages/baconstreamline.png){: .shadow}\n\nGitLab deploys an instance of Prometheus to get information about your cluster and facilitate the monitoring of your application.\n\nFor example, you can see how many pods are deployed and their states in your environment. You can also view some charts and information about your cluster, like memory and CPU available. All these metrics are available by default without changing the application of your cluster. We can also read the logs directly in GitLab. For a developer, it’s great to have all this information on the same tool and this allows us to save time. \n\n![Pod deployment](https://about.gitlab.com/images/blogimages/baconhealth.png){: .shadow}\n\n\n## A new way to integrate Kubernetes\n\nEverything I explained in the previous chapter doesn’t quite exist anymore. The release of GitLab 14.5 was the beginning of a revolution. The Kubernetes integration with certificates has limitations on security and many issues were created. GitLab teams worked on a new way to rely on your cluster. And in Version 14.5, the [GitLab Agent for Kubernetes](https://docs.gitlab.com/ee/user/clusters/agent/) was released! \n\n## GitLab Agent for Kubernetes\n\nGitLab Agent for Kubernetes is a new way to connect to your cluster. This solution is easy to explain: An agent installed on your cluster communicates with your GitLab instance with [gRPC](https://grpc.io/) protocol. Your agent offers you useful GitOps features I will explain later. The next picture shows you the GitLab Agent for Kubernetes architecture (from GitLab). \n\n![GitLab Agent for Kubernetes flow chart](https://about.gitlab.com/images/blogimages/baconkubernetesflowchart.png){: .shadow}\n\n### GitOps defined\n\nLet’s quickly define the term “[GitOps](/topics/gitops/)”: It’s a way to manage your infrastructure as code, in a Git project. For me, there are two aspects in GitOps: “pull” and “push” mode. \n\n- Push mode is when your Git project activates the upgrade of your infrastructure following a change. \n- Pull mode is when your infrastructure verifies without interruption of your Git project and applies changes automatically.\n\nAnd GitLab chose the latter mode for their solution of GitLab Agent for Kubernetes. Indeed, your agent available on your cluster will check frequently if your project changes. The gRPC protocol is great to respect this intent. When you push a modification on your project, agents detect it automatically, and then your cluster upgrades.\n\n### How the GitLab Agent for Kubernetes works\n\nThere are some actions to do to install and have a GitLab Agent for Kubernetes available on your project. \n\nFirst, if you create a new project on GitLab, you can use the template “Management cluster,” which allows the initialization of files. These files allow you to have examples of: \n- a declaration of an agent\n- a list of starter kits to install DevOps tools\n\nGitLab is a DevOps platform that wants to help you to configure all steps of the lifecycle of your project. You can find the configuration of tools like Prometheus, Sentry, Ingress, etc. I will detail this later.\n\n### The evolution of GitLab Agent for Kubernetes\n\nBefore explaining more details about this agent, you have to know one thing. This product is in constant evolution and your feedback is welcome in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/342696#note_899701396) to improve it. The roadmap is available and each version gives some information about its evolution.\n\n## How to use GitLab Agent for Kubernetes\n\nCreating an agent is simple. You have to create a file in the directory .gitlab/agents/\u003Cnameofyouragent>/config.yaml. \n\n\n![Connect cluster](https://about.gitlab.com/images/blogimages/baconstructure.png)\n\n\nThe default configuration should contain:\n- your project id, represented by your \u003Cuser or group>/project\n- a namespace by default to deploy applications if it’s not present in your yaml files\n- path of your yaml file to apply. This can be a specific file, a directory, or a pattern of files\n- level of debug\n\n```\n\ngitops:\n manifest_projects:\n - id: xxxxx/demo-gitlab-kubernetes-cluster-management\n   default_namespace: gitlab-kubernetes-agent-demo\n   paths:\n   - glob: 'deploy.yaml'\nobservability:\n logging:\n   level: debug\n\n```\n\nYou can add security to this configuration file with the “ci_access” property. For example, it allows developers to avoid destroying the Kubernetes infrastructure 😅. I didn’t explore in detail this part yet. \n\nAll configuration options are available on [this reference page](https://docs.gitlab.com/ee/user/clusters/agent/gitops.html#gitops-configuration-reference). \n\nAfter creating and pushing your file in your project, you have to register your agent. And this action takes two seconds on the GitLab UI. \n\n![Add an agent](https://about.gitlab.com/images/blogimages/baconaddanagent.png){: .shadow}\n\nOn the next step, GitLab gives you the Docker command to install your agent on your cluster. For example:\n\n```\n\ndocker run --pull=always --rm \\\n    registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate \\\n    --agent-token=\u003Cyour token generated by GitLab> \\\n    --kas-address=wss://kas.gitlab.com \\\n    --agent-version stable \\\n    --namespace gitlab-kubernetes-agent | kubectl apply -f -\n\n```\nYou can copy-paste this command on your cluster and your agent will be available in a Kubernetes namespace. You can see on the GitLab UI that the link with the agent is successful.\n\n![Link with agent success](https://about.gitlab.com/images/blogimages/baconagentk.png){: .shadow}\n\n\nYou can also verify this connection in logs of agent container: \n\n```\n\n{\"level\":\"debug\",\"time\":\"2022-xx-xxT14:11:57.517Z\",\"msg\":\"Handled a connection successfully\",\"mod_name\":\"reverse_tunnel\"}  \n\n```\n\n### GitLab cluster management \n\nGitLab is a DevOps platform and uses tiers of applications to manage all the steps of a modern DevOps pipeline. The “Monitor” part in GitLab is based on some tools such as [Prometheus](https://prometheus.io/docs/visualization/grafana/),[Sentry](https://sentry.io/), [Vault](https://www.vaultproject.io/), etc. To help you, GitLab created the template [GitLab Cluster Management]( https://gitlab.com/gitlab-org/project-templates/cluster-management), which gives you a basic configuration of these tools.\n\nTo install these tools, a `.gitlab-ci.yml` file is created and defines a job to deploy them with helmfile configuration. All these tools, contained in the directory named “applications,” can be overridden or customized in `values.yaml` file. \n \nAnd for my experimentation, I used this template and applied a small change to have an external IP address for the Prometheus instance. After registering this external IP in GitLab (Menu Settings > Monitor > Alerts), the Monitor menu has data. We can check information about any pods deployed on my cluster. \n\n![Agent graph](https://about.gitlab.com/images/blogimages/baconagentgraph.png){: .shadow}\n\n## The GitOps aspect \n\nThe GitOps aspect can be verified quickly. If you choose to specify one manifest file defining an application deployment, a modification on this file implies an automatic deployment on your cluster. Without CI! This allows us to have a faster deployment than if we passed with a pipeline. The new features or fixes will be deployed faster on your infrastructures. And if you use the free version of GitLab, your deployment will not count in your CI quota. \n\nAfter a commit, the agent detects it and we can see the commit id in the agent logs.\n\n```\n{\"level\":\"info\",\"time\":\"2022-04-11T15:22:44.049Z\",\"msg\":\"Synchronizing objects\",\"mod_name\":\"gitops\",\"project_id\":\"jeanphi-baconnais/demo-gitlab-kubernetes-cluster-management\",\"agent_id\":12804,\"commit_id\":\"e2a82fe6cc82fa25e8d5a72584774f4751407558\"}\n\n```\n\n## CI/CD tunnel\n\nAnother feature that comes with the GitLab Agent for Kubernetes is the CI/CD tunnel. Your agent facilitates the interaction with your cluster. You just have to define a KUBE_CONTEXT variable referencing the path of your agent. \n\n```\nvariables:\nKUBE_CONTEXT: \"xxxxx/demo-gitlab-kubernetes-cluster-management:agentk\"\n\n```\n\nAnd actions on your cluster are available without secret configuration or anything else. If you want to execute `kubectl` commands, you can easily use this job:\n\n```\n\ntest-cicd-tunnel:\n stage: test\n extends: [.kube-context]\n image:\n   name: bitnami/kubectl:latest\n   entrypoint: [\"\"]\n script:\n  - kubectl get ns\n when: manual\n\n```\n\n## What's next\n\nCurrently, GitLab Agent for Kubernetes doesn’t allow you to get information about the state of pods on your cluster’s environment page.\n\n![Success](https://about.gitlab.com/images/blogimages/baconci.png){: .shadow}\n\nBut GitLab wants to offer the same level of service as the certificate integration. So, check the roadmap ([in this issue](https://gitlab.com/groups/gitlab-org/-/epics/3329)) and the contents of each release. The template Cluster Management is in progress, too. Some issues will give new features for configuration tools.\n\nThis experience was so rewarding for me. I would deploy a project on Google Cloud, and I discovered a new method. I saw this agent described in [GitLab 14.5](/releases/2021/11/22/gitlab-14-5-released/) but I didn’t imagine the impact it can have on a project. \n\nMy colleague [Eric Briand](https://twitter.com/eric_briand) and I had the opportunity to speak about this subject at [Malt Academy sessions](https://www.malt-academy.com/) and [Meetup GitLab France](https://www.meetup.com/GitLab-Meetup-France/events/283917115). I will continue to experiment with this agent and try different options for this wonderful product! \n\n**This blog post and linked pages contain information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. As with all projects, the items mentioned in this video/blog post and linked pages are subject to change or delay. The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc.**\n\nCover image by [Ashin K Suresh](https://unsplash.com/photos/mkxTOAxqTTo) on Unsplash.\n{: .note}\n","open-source",[702,266,703,9,704],"kubernetes","user stories","contributors",{"slug":706,"featured":6,"template":681},"configuring-your-cluster-with-kubernetes-integration","content:en-us:blog:configuring-your-cluster-with-kubernetes-integration.yml","Configuring Your Cluster With Kubernetes Integration","en-us/blog/configuring-your-cluster-with-kubernetes-integration.yml","en-us/blog/configuring-your-cluster-with-kubernetes-integration",{"_path":712,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":713,"content":719,"config":729,"_id":731,"_type":13,"title":732,"_source":15,"_file":733,"_stem":734,"_extension":18},"/en-us/blog/devsecops-platforms-help-smbs-scale-as-they-grow",{"title":714,"description":715,"ogTitle":714,"ogDescription":715,"noIndex":6,"ogImage":716,"ogUrl":717,"ogSiteName":669,"ogType":670,"canonicalUrls":717,"schema":718},"DevSecOps platforms help SMBs scale as they grow","Adopting a comprehensive platform early lets smaller businesses mature with best practices.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668641/Blog/Hero%20Images/smbscale.jpg","https://about.gitlab.com/blog/devsecops-platforms-help-smbs-scale-as-they-grow","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"DevSecOps platforms help SMBs scale as they grow\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sharon Gaudin\"}],\n        \"datePublished\": \"2023-01-17\",\n      }",{"title":714,"description":715,"authors":720,"heroImage":716,"date":722,"body":723,"category":724,"tags":725},[721],"Sharon Gaudin","2023-01-17","\nFor startups and small to medium-sized businesses (SMBs) working to expand their customer base, revenue, and standing in their industries, adopting a [DevSecOps](/topics/devsecops/) platform is one move that can help make all of that growth happen. \n\nThe trick is to migrate to a single, end-to-end platform when the organization is small, so bad habits are avoided early on and constructive processes can be built in and scale as the business grows. A DevSecOps platform enables small businesses to set up an environment and work processes that help them avoid common pitfalls that can come with growth.\n\n## How DevSecOps platforms help SMBs scale\n\nHere are a few ways a DevSecOps platform can help smaller businesses and startups scale:\n\n### Reducing complexity\n\nWhen someone is on a small IT team, the last thing they need is something complicating their job and taking up their precious time. And if they are stitching together multiple tools, they end up creating a [clumsy, ad-hoc toolchain](/blog/battling-toolchain-technical-debt/). That by its very nature forces DevOps professionals to wrestle with a chaotic environment that leads to bottlenecks and requires constant management, tweaking, updating, and switching between interfaces. All of that toolchain care and feeding comes at the expense of simply focusing on delivering code that drives the organization’s bottom line. \n\n### Avoiding silos\n\nMaybe a company is small enough that silos aren’t a problem... right now. But as the business grows, silos likely will grow along with it, causing problems. Silos mean people are heads down working on their own project, or even worse, their own part of a project, without any visibility into the rest of it, or the ability to comment or share their work. It’s easy to create silos if you’re not using a DevSecOps platform because people often naturally separate off into single-minded groups that do not communicate with or understand each other. DevSecOps platforms foster collaboration, making it easier to keep silos from forming in the first place. They create a working environment open to communication and collaboration. A platform will give people the ability to work together, and that collective effort will produce better software. \n\n### Increasing collaboration\n\nAdopting a single, end-to-end platform when a company is small or when a startup is just getting off the ground will enable and encourage everyone in the business (from IT to finance, marketing, and sales) to work together. And it’s easier to create [a collaborative culture](/blog/why-devops-collaboration-continues-to-be-important/) from the very beginning, when working together can become a habit – a normal means of operation. Instilling an environment of communication also is less disruptive and easier to manage in a company of 10, 25, or even 100 employees than in a much larger and complex business. Collaboration also will encourage innovation by bringing in ideas from people in a range of demographics and business interests. Innovative ideas will help businesses grow into more successful and larger companies.\n\n### Decreasing hands-on work\n\nBecause startups and SMBs have fewer IT people, let alone teams of DevOps professionals, the [automation](/blog/how-automation-is-making-devops-pros-jobs-easier/) that is an integral part of a DevSecOps platform eases their burden by decreasing the amount of hands-on work they have to do. With automation for jobs like backup, installation, and security testing built in, people spend less of their already-limited time needlessly repeating time-consuming tasks, or going back in the software lifecycle to find where a security bug was introduced. Automating tasks required for everything from design to build, test, and deployment also can reduce the potential for human error and provide consistency throughout the software lifecycle. By taking those jobs off DevSecOps teams' plates, they have more time to actually build and deploy innovative software and support the business. \n\nLet’s be clear: A startup or SMB isn’t too small for a DevSecOps platform. If an organization is building software, it needs a platform. Business executives don’t want to struggle to grow and look back regretfully and think, “Why didn’t I adopt a DevSecOps platform earlier?”\n\n“If you’re on a small team or even just a team of one, migrating could seem like a lot to take on,” says [Fatima Sarah Khalid](/company/team/#sugaroverflow), a developer evangelist at GitLab. “But it’s worth the effort to set yourself up for growth. With a platform, everyone in the company is able to work in the same environment on the same projects. That means a collaborative environment without silos is formed early and the business can grow with that culture, instead of trying to adopt it years down the road when bad work habits have already formed.”\n\nWith GitLab’s single, end-to-end DevSecOps platform, automation is a system feature and not something that has to be added in. It also helps organizations eliminate or even keep silos from forming, increases collaboration and communication, and decreases the complexities that are born of DIY toolchains.\n\n**Download our [ebook](https://page.gitlab.com/resources-ebook-trading-diy-devops-for-a-single-platform-smb.html)** to learn about the benefits of migrating from a toolchain to GitLab’s DevSecOps platform. \n\n_Cover image by [Markus Spiske](https://unsplash.com/de/@markusspiske?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://www.unsplash.com)_\n","devsecops",[726,727,728,9],"DevOps","DevOps platform","demo",{"slug":730,"featured":6,"template":681},"devsecops-platforms-help-smbs-scale-as-they-grow","content:en-us:blog:devsecops-platforms-help-smbs-scale-as-they-grow.yml","Devsecops Platforms Help Smbs Scale As They Grow","en-us/blog/devsecops-platforms-help-smbs-scale-as-they-grow.yml","en-us/blog/devsecops-platforms-help-smbs-scale-as-they-grow",{"_path":736,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":737,"content":742,"config":747,"_id":749,"_type":13,"title":750,"_source":15,"_file":751,"_stem":752,"_extension":18},"/en-us/blog/everyone-can-get-certified",{"title":738,"description":739,"ogTitle":738,"ogDescription":739,"noIndex":6,"ogImage":667,"ogUrl":740,"ogSiteName":669,"ogType":670,"canonicalUrls":740,"schema":741},"Everyone Can Get Certified!","GitLab Learn learning platform now available to the GitLab wider community","https://about.gitlab.com/blog/everyone-can-get-certified","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Everyone Can Get Certified!\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Christine Yoshida\"}],\n        \"datePublished\": \"2021-04-20\",\n      }",{"title":738,"description":739,"authors":743,"heroImage":667,"date":744,"body":745,"category":677,"tags":746},[674],"2021-04-20","**Please note we have new resources since this article was first published:**\n\n* **[Register for a free GitLab University account](https://university.gitlab.com/) using your Gitlab.com login.**\n* **Find our current exams on our [Certifications page](https://university.gitlab.com/pages/certifications), which are available for purchase.**\n* **Use [GitLab with Git Essentials](https://university.gitlab.com/courses/gitlab-with-git-essentials-s2) as a replacement for GitLab 101 and 201 courses.**\n\n\u003Cbr>\nAt GitLab we believe in an inclusive approach for thriving as team members and contributing as part of the wider community. That’s why we are excited to highlight our [GitLab Learn](https://university.gitlab.com/) learning platform, which is newly available to the GitLab wider community. \n\nNow anyone can learn, and anyone can get certified! To get started visit GitLab Learn and create an account. \n\n## Free Certification Pathways \n\nOn GitLab Learn you’ll find learning paths and certifications that we make available to GitLab team members as well as the wider GitLab community.   \n\nHere are some of the free certification and badge pathways you’re welcome to complete on the site, created by the GitLab [Learning & Development Team](https://university.gitlab.com/):  \n- GitLab 101 certification that covers Git basics and fundamental concepts such as branches, commits, version control, DevOps, GitLab issues, and merge requests.\n- GitLab 201 certification  \n- Diversity, Inclusion, and Belonging Training certification \n- Remote Work Foundations badge \n- Bias Towards Asynchronous Communication badge \n\n## GitLab Technical Certifications \n\nOver the past 12 months GitLab launched 6 [new technical certifications](https://university.gitlab.com/pages/certifications), which focus on everything from continuous integration and continuous delivery (CI/CD) to security and project management.\n\nThese certifications were made available to GitLab Professional Services customers who purchased live instructor-led GitLab training for their teams and [GitLab Commit 2020](/events/commit/) attendees. As a result of our latest iteration efforts we are beginning to roll out self-service, asynchronous versions to make them available for everyone on GitLab Learn!  \n\n### New Async Technical Certification Option \n\nWe’re now bundling together the three main components you need to earn the [GitLab Certified Associate certification](https://university.gitlab.com/courses/gitlab-with-git-essentials-certification-exam) asynchronously: A self-study eLearning preparation course, a certification knowledge exam, and a graded hands-on exam you complete in a GitLab sandbox environment. This self-service GitLab training bundle is available on GitLab Learn and is priced at USD $650.  \n\n#### Amazing Positive Response to Our Promotion! \n\nWe’ve had an overwhelmingly positive response to our certifications on GitLab Learn and have reached our planned user limit on the discount code we offered in just 2 days instead of 10!\n\nThe promotion is over, but if you are interested in hearing from us about future offerings and GitLab Learn activities, please [create an account](https://university.gitlab.com/) to view our certification announcements. \n\n*Steps for Enrolling:*\n\nWe recommend using Google Chrome, clearing your cookies, and ensuring you have cookies enabled. If you prefer to use a mobile device, create your account on a desktop system first and then use the Edcast mobile app on Google Play or the Apple App Store for the best experience. \n\n1. In Google Chrome, navigate to [GitLab Learn](https://university.gitlab.com/) and click \"Login with your email and password\" then click the purple Sign Up button to create an account. If you are a GitLab Team Member you do not need to create an account and you can use the orange SSO button.\n2. Once logged in, locate the GitLab Certified Associate Pathway and click the orange Enroll button. This will take you to a separate webpage to enter your discount code.  \n3. On this webpage click Proceed with Order and fill in your details.\n4. On the Payment Method screen, add your payment method.\n5. Click the Place Order button and then click the Go to My Content Now button to access the content.\n\n## Your comments are welcome here!  \n\nTell us how GitLab certifications have helped you or your team thrive.\n",[266,9,677,549],{"slug":748,"featured":6,"template":681},"everyone-can-get-certified","content:en-us:blog:everyone-can-get-certified.yml","Everyone Can Get Certified","en-us/blog/everyone-can-get-certified.yml","en-us/blog/everyone-can-get-certified",{"_path":754,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":755,"content":761,"config":767,"_id":769,"_type":13,"title":770,"_source":15,"_file":771,"_stem":772,"_extension":18},"/en-us/blog/four-tips-to-increase-your-devops-salary",{"title":756,"description":757,"ogTitle":756,"ogDescription":757,"noIndex":6,"ogImage":758,"ogUrl":759,"ogSiteName":669,"ogType":670,"canonicalUrls":759,"schema":760},"Four tips to increase your DevOps salary","You have a great career with a solid salary, but can you do better? (Hint: of course.) Here's how.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668124/Blog/Hero%20Images/moneyfarm_background.jpg","https://about.gitlab.com/blog/four-tips-to-increase-your-devops-salary","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Four tips to increase your DevOps salary\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sharon Gaudin\"}],\n        \"datePublished\": \"2021-10-20\",\n      }",{"title":756,"description":757,"authors":762,"heroImage":758,"date":763,"body":764,"category":724,"tags":765},[721],"2021-10-20","\n\n_This is the second in an occasional series looking at DevOps salaries and careers. Find out [how your DevOps salary stacks up](/blog/a-look-at-devops-salaries/)._\n\nSalaries for DevOps professionals are strong, despite a pandemic and a global economic crisis. However, you can still command an even higher salary with four straightforward strategies.\n\nVarious surveys have shown the IT industry is thriving right now and DevOps professionals, in particular, are [increasing in demand and value](https://about.gitlab.com/blog/a-look-at-devops-salaries/). DevOps repeatedly ranks well on some reputable lists such as Robert Half’s [15 highest paying IT jobs](https://www.roberthalf.com/blog/salaries-and-skills/the-13-highest-paying-it-jobs-in-2019) and Glassdoor’s 2021 list of [Best Jobs in America](https://www.glassdoor.com/research/best-jobs-in-america-for-2021/). \n\nIn [an August jobs report](https://www.prnewswire.com/news-releases/nationwide-tech-hiring-surges-in-second-quarter-per-dice-q2-tech-job-report-301351520.html), Dice CEO Art Zeile called this “one of the hottest tech job markets since the dot-com era,” and pointed to the upward trend in tech job postings since November 2020.\n\n## How to increase your salary\n\nBy following these strategies, DevOps professionals can take advantage of this strong market to boost your paychecks.\n\n### 1. Gain more experience\n\nExperience level is a big driver when it comes to how much money DevOps professionals will be taking home. [The Randstad 2021 Salary Guide](https://rlc.randstadusa.com/for-business/learning-center/salary-insights/salary-guide/IT-technologies) shows a more than $27,000 difference between the annual salary of a DevOps developer with one year of experience ($112,785) and someone with five years of experience ($140,242). An additional 10 years of experience can garner another $25,000 bump, according to the Randstad Salary Guide.\n\nExperience doesn’t have to happen sequentially, however. In our [2021 Global DevSecOps Survey](/developer-survey/) we found more than  69% of respondents participate in “sideline” open source projects. Those extracurricular efforts can look great on a resume and also are a way to showcase niche skills.\n\n### 2. Expand your education\n\nEmployers also are looking for DevOps professionals to continue to increase their skill set, such as learning new coding languages and scripting skills, according to Glassdoor and Robert Half.\nDevOps professionals also should stay up-to-date on new frameworks, automation, data management and security systems. And don’t forget the importance of analytics skills, configuration management and DevOps platforms. As we all know, technology is a moving target and being able to not only use the latest technology but also explain its importance to executives and other business leaders will make you a more valuable employee.\n\n### 3. Pursue certifications\n\nWant to show your employer - or a future employer - that you have the skills to work on a business-critical DevOps platform? The proof is sometimes in the certification. Think about getting certified in [Kubernetes](https://training.linuxfoundation.org/certification/certified-kubernetes-application-developer-ckad/), [Docker](https://prod.examity.com/docker/), Puppet or [Ansible](https://www.redhat.com/en/services/training/ex407-red-hat-certified-specialist-in-ansible-automation-exam?section=Overview). And of course there’s an option to become a [GitLab Certified Associate](https://about.gitlab.com/services/education/gitlab-certified-associate/). Certifications help an employer understand your functional knowledge of their business systems.\n\n### 4. Improve your soft skills \n\nYes, it’s critical that you know how to make the technology work and how to keep projects running on time and on budget, but you also should concentrate on “soft skills,” like communication, collaboration and leadership, if you’re aiming to qualify for a better salary. In 2020 our survey takers all agreed that soft skills were the most important thing for their future careers, and they remained the second choice of most survey takers this year as well. \n\nCompanies need professionals who understand the business’ needs, can communicate how a DevOps platform can solve key challenges and can explain the competitive advantage gained from a strong DevOps strategy. Soft skills enable professionals to operate as a team, endure stressful moments and work through difficult problems.\n\nDevOps professionals are in demand, putting you in a strong earning position. So make sure you are doing all you can to show you deserve a higher salary.\n\n## Read more on DevOps careers: \t\t\n\n- [Best advice for your DevOps career? Keep on learning](/blog/best-advice-for-your-devops-career-keep-on-learning/)\n\n- [6 tips to make software developer hiring easier](/blog/6-tips-to-make-software-developer-hiring-easier/)\n\n- [DevOps salaries in 2021: Where do you rank?](/blog/a-look-at-devops-salaries/)\n\n- [Have DevOps jobs to fill? Try these 3 strategies to hire and retain](/blog/have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain/)\n\n",[766,726,9],"careers",{"slug":768,"featured":6,"template":681},"four-tips-to-increase-your-devops-salary","content:en-us:blog:four-tips-to-increase-your-devops-salary.yml","Four Tips To Increase Your Devops Salary","en-us/blog/four-tips-to-increase-your-devops-salary.yml","en-us/blog/four-tips-to-increase-your-devops-salary",{"_path":774,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":775,"content":780,"config":786,"_id":788,"_type":13,"title":789,"_source":15,"_file":790,"_stem":791,"_extension":18},"/en-us/blog/have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain",{"title":776,"description":777,"ogTitle":776,"ogDescription":777,"noIndex":6,"ogImage":667,"ogUrl":778,"ogSiteName":669,"ogType":670,"canonicalUrls":778,"schema":779},"Have DevOps jobs to fill? Try these 3 strategies to hire and retain","So many DevOps jobs posted, so few options to fill them. Here's why hiring and retaining developers is tricky, and how 3 thoughtful strategies, including a DevOps platform, can help.","https://about.gitlab.com/blog/have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Have DevOps jobs to fill? Try these 3 strategies to hire and retain\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2021-09-23\",\n      }",{"title":776,"description":777,"authors":781,"heroImage":667,"date":783,"body":784,"category":724,"tags":785},[782],"Valerie Silverthorne","2021-09-23","\nIf every company is a software company, how do you stand out from the crowd when it comes to attracting developer talent and filling DevOps jobs?\n\nThere’s a well-known, and worldwide, shortage of software developers, especially those with expertise in DevOps. Worse still, demand for those roles is accelerating rapidly: The US Bureau of Labor Statistics predicts employment opportunities for devs and testers will [increase 22% between 2020 and 2030](https://www.bls.gov/ooh/computer-and-information-technology/software-developers.htm#tab-6). That growth rate means nearly 190,000 net new developer/QA/test jobs will be opening each year, according to the BLS. \n\nThat’s all a long way of saying things are tough out there. Organizations looking to expand, or even just maintain, their DevOps jobs momentum have to find unique ways to stand out from the crowd because, as [many surveys have shown](https://hired.com/state-of-software-engineers#report), salary alone is often insufficient to both attract and retain developer talent.\n\n**Elevating your DevOps skills? Join us at [Commit at KubeCon - Oct. 11!](/events/commit/)**\n\nHere are 3 ways organizations can create an environment where DevOps can thrive, boosting developer retention, job satisfaction and even “cool place to work” street cred.\n\n## Make (a few) cool tools rule\n\nDevelopers are known for their big love of tools. In our [2021 Global DevSecOps Survey](/developer-survey/), more than one-quarter of respondents said they used between 5 and ten tool chains, and more than half said each tool chain had an average of 5 tools on it. Do the math and it’s clear that’s a lot of tools, and according to [research on software developer job satisfaction](https://link.springer.com/chapter/10.1007/978-1-4842-4221-6_10) too much information (i.e., from **too many tools**) can lead to less productivity and unhappy developers.\n\nThe solution to this very common problem can be found by adopting a DevOps platform, a single application where every stage of DevOps is interconnected, visible and seamless. And make sure that platform can integrate with all the key, cutting edge, “must have” kinds of tools that developers like to put on their resumes, and everyone will benefit from this streamlined approach.\n\n## Pay attention to career education\n\nDevelopers are always willing to DIY career education. The latest Stack Overflow Survey found about 60% of their survey takers [taught themselves coding via an online source](https://insights.stackoverflow.com/survey/2021#developer-profile-experience) – but that doesn’t mean they wouldn’t value (and take advantage of) training opportunities from employers. In our 2021 survey, a majority of developers said they’re most excited to learn about AI/ML, while ops pros were looking for education around advanced programming languages. \n\nBy asking DevOps team members about their interests and needs, organizations can keep a pulse on training opportunities they could offer that will actually matter to their teams and potentially make filling DevOps jobs easier.\n\n## Be flexible about everything\n\nFrom working remotely to working part-time, it’s clear that developers want the option to mix it up if possible. The more options - like having the time to pursue a degree or a passion - given to DevOps team members, the more likely they are to be satisfied with their jobs. \n\nAlso, time to pursue some “off the books” projects is another smart company perk. Don’t forget the role open source projects played in the pandemic (here are [a few examples](https://www.newamerica.org/digital-impact-governance-initiative/reports/building-and-reusing-open-source-tools-government/open-source-project-hubs-for-covid-19/)), making an already important part of a developer’s role even more compelling. In fact, more than 69% of our survey respondents told us they were involved with at least one open source project in 2021, and that number was up 6% from 2020.  \n\n## Don't forget DevOps\n\nIt’s a temperamental DevOps job market, certainly, but organizations with healthy DevOps practices do have one secret weapon: DevOps itself. When we asked our 4,300+ survey takers what the top benefits of DevOps was, “happier developers” was near the top of the list. \n\n## Read more on DevOps careers: \t\t\n\n- [Best advice for your DevOps career? Keep on learning](/blog/best-advice-for-your-devops-career-keep-on-learning/)\n\n- [6 tips to make software developer hiring easier](/blog/6-tips-to-make-software-developer-hiring-easier/)\n\n- [Four tips to increase your DevOps salary](/blog/four-tips-to-increase-your-devops-salary/)\n\n- [DevOps salaries in 2021: Where do you rank?](/blog/a-look-at-devops-salaries/)\n\n",[726,766,9],{"slug":787,"featured":6,"template":681},"have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain","content:en-us:blog:have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain.yml","Have Devops Jobs To Fill Try These 3 Strategies To Hire And Retain","en-us/blog/have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain.yml","en-us/blog/have-devops-jobs-to-fill-try-these-3-strategies-to-hire-and-retain",{"_path":793,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":794,"content":800,"config":810,"_id":812,"_type":13,"title":813,"_source":15,"_file":814,"_stem":815,"_extension":18},"/en-us/blog/how-gitlab-successfully-expanded-our-soc-2-type-ii-trust-services-report-criteria",{"title":795,"description":796,"ogTitle":795,"ogDescription":796,"noIndex":6,"ogImage":797,"ogUrl":798,"ogSiteName":669,"ogType":670,"canonicalUrls":798,"schema":799},"GitLab expands SOC 2 Type II trust services report criteria","Here's how we expanded our SOC 2 Type 2 and SOC 3 reports.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669950/Blog/Hero%20Images/security-cameras.jpg","https://about.gitlab.com/blog/how-gitlab-successfully-expanded-our-soc-2-type-ii-trust-services-report-criteria","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How GitLab successfully expanded our SOC 2 Type II Trust Services Report Criteria\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Julia Lake\"},{\"@type\":\"Person\",\"name\":\"Liz Coleman\"}],\n        \"datePublished\": \"2021-12-14\",\n      }",{"title":801,"description":796,"authors":802,"heroImage":797,"date":805,"body":806,"category":807,"tags":808},"How GitLab successfully expanded our SOC 2 Type II Trust Services Report Criteria",[803,804],"Julia Lake","Liz Coleman","2021-12-14","\nSOC reports are important attestations provided by an independent third party affirming that organizations are in compliance with specific technical and operational requirements defined by the American Institute of Certified Public Accountants (AICPA). GitLab obtained its first [SOC 2 Type 2 and SOC 3 reports in 2020](/press/releases/2021-02-04-soc2-and-3-certifications.html), focused on the Security criteria, for the GitLab software-as-a-service (SaaS) platform. \n\nFor 2021, GitLab’s [Security Assurance](/handbook/security/security-assurance/) team pursued expansion of our SOC 2 Type 2 and SOC 3 reports to include not only the Security, but also the Confidentiality [Trust Services Criteria (TSC)](https://us.aicpa.org/interestareas/frc/assuranceadvisoryservices/trustdataintegritytaskforce). If you are not familiar with the TSCs, here's what they cover:\n\n**Security**. Information and systems are protected against unauthorized access, unauthorized disclosure of information, and damage to systems that could compromise the availability, integrity, confidentiality, and privacy of information or systems and affect the entity’s ability to meet its objectives.\n\n**Confidentiality**. Information designated as confidential is protected to meet the entity’s objectives.\n \nThe work associated with criteria expansion required early preparation and a multi-quarter effort. We verified expansion readiness in phases:\n\n* **Phase 1:** We performed a gap analysis against the [criteria](https://us.aicpa.org/content/dam/aicpa/interestareas/frc/assuranceadvisoryservices/downloadabledocuments/trust-services-criteria-redlined.pdf) to determine existing control coverage and gaps and provide data to make a `go/no-go` decision.\n\n* **Phase 2:** We upgraded our [GitLab Control Framework (GCF)](/handbook/security/security-assurance/security-compliance/security-control-lifecycle.html) to include the new criteria requirements and held control owner deployment sessions, developed test plans, conducted detailed internal control testing, and worked any observations](/handbook/security/security-assurance/observation-remediation-procedure.html) through to closure. \n\n* **Phase 3**: We presented our validated draft controls to our independent third party auditor to confirm scope and readiness.\n\nOnce all 3 phases were complete, the SOC audit was scheduled and executed. The phased preparation allowed for both GitLab and our independent third party auditor to conduct the audit with full [transparency](https://handbook.gitlab.com/handbook/values/#transparency) and alignment. The audit process also revealed no formal exceptions.\n\nHere at GitLab we are always pursuing [the expansion of our security certification portfolio](/handbook/security/security-assurance/security-compliance/certifications.html#planned-roadmap) as we not only want to give our customers and community additional assurance, but also additional transparency into our information security practices. Have a certification you’d like to see us work towards? Let us know by emailing customer-assurance@gitlab.com, we’d love to hear from you!\n\n_Follow GitLab’s [Security Trust Center](/security/) for updates and more details on our certification portfolio. GitLab’s SOC 3 report is now publicly available via GitLab’s [Customer Assurance Package](https://about.gitlab.com/security/cap/)._\n\n\n","security",[807,809,9],"security releases",{"slug":811,"featured":6,"template":681},"how-gitlab-successfully-expanded-our-soc-2-type-ii-trust-services-report-criteria","content:en-us:blog:how-gitlab-successfully-expanded-our-soc-2-type-ii-trust-services-report-criteria.yml","How Gitlab Successfully Expanded Our Soc 2 Type Ii Trust Services Report Criteria","en-us/blog/how-gitlab-successfully-expanded-our-soc-2-type-ii-trust-services-report-criteria.yml","en-us/blog/how-gitlab-successfully-expanded-our-soc-2-type-ii-trust-services-report-criteria",{"_path":817,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":818,"content":824,"config":833,"_id":835,"_type":13,"title":836,"_source":15,"_file":837,"_stem":838,"_extension":18},"/en-us/blog/how-holistic-ux-design-increased-gitlab-free-trial-signups",{"title":819,"description":820,"ogTitle":819,"ogDescription":820,"noIndex":6,"ogImage":821,"ogUrl":822,"ogSiteName":669,"ogType":670,"canonicalUrls":822,"schema":823},"How holistic UX design increased GitLab.com free trial signups","We boosted free trial signups by 141% by focusing on designing whole experiences instead of separate screens, small interactions, or pieces of UI.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681113/Blog/Hero%20Images/user-journey-map.jpg","https://about.gitlab.com/blog/how-holistic-ux-design-increased-gitlab-free-trial-signups","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How holistic UX design increased GitLab.com free trial signups\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Matej Latin\"}],\n        \"datePublished\": \"2020-02-27\",\n      }",{"title":819,"description":820,"authors":825,"heroImage":821,"date":827,"body":828,"category":677,"tags":829},[826],"Matej Latin","2020-02-27","\n\nOur new improved free trial signup flow launched in October 2019 and it reduced the number of interactions a user needed to do to complete the process from around 35 to 15. We reduced the time required to sign up and start a trial from more than five minutes to around 2.5 minutes – less than half of the original. Not surprisingly, our free trial signups soon went from around 400 per week to more than 800. This is the journey of three designers battling the complexity that comes with user experiences that weren’t designed holistically but instead grew “organically.”\n\n## Discovering the problems\n\nI started working on designing a new user onboarding experience sometime in the second quarter of 2019. The first step I took was to map the existing user journey from when users sign up for GitLab to the end of the existing onboarding. I wanted insight into the mindset of users at the moment they finished signing up for a free trial. We wanted our users to be excited and eager about the onboarding experience. I never expected to find what I did by mapping the current user journey.\n\n![GitLab’s marketing page](https://about.gitlab.com/images/blogimages/free-trial-improvements/homepage.jpg){: .large.shadow.center}\n\nI started to map the journey on the homepage of our marketing website and clicked on the big orange “Try GitLab for FREE” button. That took me to our free trial landing page where a user can choose between trialing GitLab as a SaaS (GitLab.com, hosted by GitLab) or self-managed (GitLab Self-Managed) solution. And this is where the problems started to appear.\n\n### Symptoms of a broken user experience\n\nThe two options for trialing GitLab (SaaS or Self-Managed) were presented by two tabs, one of which (Self-Managed) was active by default. To start a Self-Managed trial, the user had to fill in a large form right away. The SaaS option, on the other hand, only required a click on a button. My assumption here was that setting up a Self-Managed GitLab trial takes much longer so I concluded that someone who just stumbled upon GitLab is more likely to try it out as a SaaS. But on this page, few users actually noticed that option.\n\n![Original Free trial landing page](https://about.gitlab.com/images/blogimages/free-trial-improvements/landing-page.jpg){: .large.shadow.center}\n\nProblems identified:\n\n1. Self-Managed is the prioritized option but users need to fill in a large form to get started. Huge drop-off is expected even before the signup flow started.\n2. Affordance issues: the second option (the non-active one) was barely discoverable because of the way it was presented. The contrast was too low and most users missed it.\n3. Even the simpler option for starting a SaaS trial had instructions that needed to be followed. Most users missed these instructions and simply clicked on the big orange button labeled “Start Your Trial.”\n\n![Instructions](https://about.gitlab.com/images/blogimages/free-trial-improvements/instructions.jpg){: .shadow.medium.center}\n\nSigning up for a SaaS GitLab trial required users to complete two separate steps in the correct order. If step 1 wasn't completed, clicking on the “Start Your Trial” button led to a free trial signup flow that couldn’t be completed.\n\nSo a user would either have to fill in a large form and install their own instance of GitLab or follow these instructions to start a trial on GitLab.com. This reminds me of a design joke I heard ages ago but it stuck with me because it’s so true:\n> Design is like a joke: if it needs an explanation, it’s not a good joke.\n\u003C!-- ### Two separate steps to sign up for a free trial -->\n\nI didn’t know this at the time but these instructions where there for a reason. Users needed to complete two separate steps in two different applications to successfully sign up for a free trial – GitLab.com and a tool we call Subscription Manager. That’s why we had these instructions written on this page and that’s why the experience was completely broken if they weren’t followed. The following is the user journey map that I created:\n\n![Original user journey map](https://about.gitlab.com/images/blogimages/free-trial-improvements/original-user-journey-map.jpg){: .large.shadow.center}\n\nAltogether, it took users more than five minutes and almost 40 interactions to complete the process. When I say “interactions” I mean things like clicking a button, landing on a page, filling in a form field and similar. A user who just completed the process of signing up for a free trial of a tool should feel excited, but in our cause they most probably felt exhausted. You can [watch my video walkthrough of the experience](https://www.youtube.com/watch?v=O-zjek64d0g&feature=youtu.be) as it was at the time. Here are the key points of the experience:\n\nUsers had to sign up for a [GitLab.com](http://gitlab.com) account first. After this step, they were shown an “Almost finished” message as they had to confirm their email by clicking on a link in an email message that was automatically sent.\n\n![Registration form](https://about.gitlab.com/images/blogimages/free-trial-improvements/registration-form.jpg){: .large.shadow.center}\n\nProblems discovered:\n\n- We asked for a lot of information, probably too much for simply signing up.\n- We sent the newly signed-up users to their inbox – a huge source of distractions.\n\nAfter they successfully confirmed their email, we showed them the following screen – the beginning of the Free Trial signup:\n\n![Free trial sign up](https://about.gitlab.com/images/blogimages/free-trial-improvements/free-trial-signup.jpg){: .large.shadow.center}\n\nProblems identified:\n\n- Visual style was different.\n- We asked for a lot of information again. A lot of this we already had from their GitLab.com signup but we didn’t use any of it to pre-fill the form.\n\nAfter they filled in and submitted the Free Trial signup form, they were shown the following from the Subscription Manager app. This is when the users started to interact with the second app.\n\n![Subscription manager](https://about.gitlab.com/images/blogimages/free-trial-improvements/subscription-manager.jpg){: .large.shadow.center}\n\nProblems identified:\n\n- We told the users to confirm their email address again. It’s a different app for us, but for them it’s all GitLab.\n- The most obvious next step – confirming the email address – actually led to a broken flow that couldn’t be completed.\n- This screen created a lot of confusion and users didn’t know what they had to do. Sign in, register, or sign in with GitLab.com?\n\nIn the end, signing in with GitLab.com was the only way to successfully complete the process. It took the users to the next screen – activating their free trial.\n\n![Free trial activation](https://about.gitlab.com/images/blogimages/free-trial-improvements/free-trial-activation.jpg){: .large.shadow.center}\n\nProblems identified:\n\n- We asked the users to choose which group their free trial is for. We asked this even if the user had no groups created at all. In that case, the users could only apply the trial to their namespace so the dropdown only had one option. As this was commonly the case, this step was unneeded manual work.\n\nTo add to the confusion, we sent users to the final screen in the flow: the billing overview. The fact that we sent them to this screen wasn’t the problem, it was the information we showed.\n\n![Billing page](https://about.gitlab.com/images/blogimages/free-trial-improvements/billing.jpg){: .large.shadow.center}\n\nProblems identified:\n\n- We told the users they’re on the Gold Plan but we also showed the purchase options right below. Some users were confused about whether their trial was actually activated or not.\n\nWith all this done we could summarize what the main problems that needed to be solved were:\n\n- Two separate apps with different visual styles\n- The two apps didn’t work well with each other\n- We repeatedly asked for information users already provided\n- Poor flow of screens and unclear information architecture led to confusion. Users didn't know where they were and what they were required to do.\n\n## Fixing a broken flow\n\nOk, so at this point I learned that the flow for signing up for a free trial was disjointed and sometimes even broken. I recognized what the main reason for that was – separate applications not communicating with each other through some form of automation – as well as other UI and UX issues of course.  To tackle the main problem, I came up with a vision: *about one minute and no more than 15 interactions required to complete the free trial signup flow.* The main outcome I wanted to achieve with this work was to improve the state of mind a user is in after successfully signing up for a free trial – *excited* instead of *exhausted*.\n\n![Users state of mind](https://about.gitlab.com/images/blogimages/free-trial-improvements/user-state-of-mind.jpg){: .large.center}\n\nBut how do we get there? Well, first of all, we need to move away from forcing users to interact with two separate applications. We do that by moving the second part of the process into the first application (GitLab.com) and making it communicate with the other application in the background. I proposed a unified signup flow that happens in one application but is adapted based on the user’s intent. Is the person an existing GitLab.com user trying to sign up for a free trial? Or are they a new user and they need to sign up for both GitLab.com and Subscription Manager accounts?\n\n![Unified flow](https://about.gitlab.com/images/blogimages/free-trial-improvements/unified-flow.jpg){: .shadow.large.center}\n\nMy colleague [Timothy Noah](/company/team/#timnoah) took over from here as he was the designer working with the team that owned this part of the product. He completed a [UX scorecard](https://gitlab.com/gitlab-org/ux-research/issues/285) and [video-documented](https://www.youtube.com/watch?v=MkTOwTxsoL8) the flow again. The result of his work was a [well structured approach](https://gitlab.com/gitlab-org/ux-research/issues/304) to breaking things down into smaller steps but with a holistic overview. Based on all this work, he then created a proposal of what the user journey should be like.\n\n![Proposed user journey](https://about.gitlab.com/images/blogimages/free-trial-improvements/proposed-user-journey.jpg){: .shadow.large.center}\n\nAnd translated it into actual UI, pages and their flow:\n\n![Proposed flow](https://about.gitlab.com/images/blogimages/free-trial-improvements/proposed-flow.jpg){: .large.center}\n\n[This clickable prototype](https://sketch.cloud/s/v1zJb/a/mgkLnw/play) illustrates perfectly how the new free trial signup flow should behave. It’s immediately clear that it’s much simpler and more cohesive than the original.\n\nWith that we could also improve the Free Trial landing page by removing the instructions (as we didn’t need them anymore) and balancing the two options for starting a free trial:\n\n![Improved free trial landing page](https://about.gitlab.com/images/blogimages/free-trial-improvements/improved-landing-page.jpg){: .large.shadow.center}\n\n## The new free trial signup flow launches\n\nAfter a lot of hard but well coordinated work, the new free trial signup flow launched on October 29, 2019. The results were clear in less than one week. The week before the launch, we had 466 free trial signups. In the week of the launch the number rose to 628, then to 842 in the week after. They remained well above 800 throughout November. We then saw a small dip during December (but it never fell below 600) and the climb resumed in January. We’re now getting more than 900 free trial signups per week.\n\n![Free trial signups chart](https://about.gitlab.com/images/blogimages/free-trial-improvements/chart.jpg){: .large.center}\n\nI quickly crunched the numbers and came to the following conclusion:\n\n> Average signups per week before launch: **330** \u003Cbr>\n> Average signups per week after launch: **794** \u003Cbr>\n> Which results in an improvement of **141%**\n\nSo we more than doubled the amount of free trial signups, but what exactly led to these results? Another colleague, [Kevin Comoli](/company/team/#kcomoli), recently did a follow-up [UX scorecard](https://gitlab.com/gitlab-org/growth/product/issues/166) to rescore the experience. His findings? It now takes around 17 interactions (instead of the original 37) and around 2.5 minutes to complete the process. So we reduced both by more than half and that’s why we’re seeing such an increase in completed signups. Take a look at the latest version of the [user journey](https://app.mural.co/t/gitlab2474/m/gitlab2474/1572360181709/cb4df793a4d4b98395b8c98c6510d21b4a2d6747) mapped by Kevin.\n\n## Organically grown versus holistically designed experiences\n\nExperiences are either intentionally and holistically designed by someone or they get designed by what I call “organically grown” smaller parts of the experience. It’s like cultivating a garden: we start off by planting a few flowers and bushes but leave some empty space around them. Eventually, if we don’t do anything, this empty space will get overgrown with weeds. Our flowers and bushes will also grow in an uncontrolled way. So until a gardener comes around and tidies everything up, our garden will be a mess. It’s the same with our digital products – if a designer with a holistic overview isn’t involved, different parts of our products grow into a mess that doesn’t work as a whole. The *holistic overview* is the key here. It’s not enough to have designers involved if all they do is design separate screens instead of complete experiences. We need to look at how things work as a whole. That’s when designers, and the teams they work with, are most successful.\n\n## Where do we go from here?\n\nWe’re thrilled about the improvements we have already achieved but we also feel there’s a lot more we can do. I personally would still like to see the time required to complete the process be reduced to around a minute. As part of his UX scorecard, Kevin also came up with additional [recommendations for improvements](https://gitlab.com/groups/gitlab-org/growth/-/epics/7). There, he talks about trimming down the information shown in the process, improving the entry points to the flow and tailoring its steps based on the user type. We’re all looking forward to  these improvements being implemented.\n\nPhoto by [Startaê](https://unsplash.com/@startaeteam?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) Team on [Unsplash](https://unsplash.com/s/photos/post-it?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[830,831,9,832],"UX","design","inside GitLab",{"slug":834,"featured":6,"template":681},"how-holistic-ux-design-increased-gitlab-free-trial-signups","content:en-us:blog:how-holistic-ux-design-increased-gitlab-free-trial-signups.yml","How Holistic Ux Design Increased Gitlab Free Trial Signups","en-us/blog/how-holistic-ux-design-increased-gitlab-free-trial-signups.yml","en-us/blog/how-holistic-ux-design-increased-gitlab-free-trial-signups",{"_path":840,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":841,"content":847,"config":855,"_id":857,"_type":13,"title":858,"_source":15,"_file":859,"_stem":860,"_extension":18},"/en-us/blog/key-organizational-models-for-devops-teams",{"title":842,"description":843,"ogTitle":842,"ogDescription":843,"noIndex":6,"ogImage":844,"ogUrl":845,"ogSiteName":669,"ogType":670,"canonicalUrls":845,"schema":846},"5 key organizational models for DevOps teams","DevOps teams can be organized in multiple ways. Identify the one that fits your organization.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667194/Blog/Hero%20Images/2020-11-19-integration-management-header.jpg","https://about.gitlab.com/blog/key-organizational-models-for-devops-teams","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"5 key organizational models for DevOps teams\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Johanna Ambrosio\"}],\n        \"datePublished\": \"2022-03-08\",\n      }",{"title":842,"description":843,"authors":848,"heroImage":844,"date":850,"body":851,"category":852,"tags":853},[849],"Johanna Ambrosio","2022-03-08","\nIf you’re just getting started with DevOps, there are several team organizational models to consider.\n\nA few key points to keep in mind as you design your team structure:\n\n- The organizational model you start with should change as you add more people, [different DevOps roles](/blog/how-to-build-out-your-devops-team/), and more projects. Expect to keep iterating as you go.\n\n- The ultimate goal of DevOps is to spread the message, tools, and processes throughout the company so that, eventually, everyone is working “the DevOps way.” At some point, if your approach is successful, DevOps as a separate group will disappear.\n\n- The model you begin with should depend on how many projects or products you’re working on, the size of your teams, and the size of your company. \n\n- Keep your team size small, with three to eight people max. Some experts say up to 12 is OK, but that’s a bit large for the [“two-pizza” rule](https://landing.directorpoint.com/blog/amazon-two-pizza-rule/). \n\n## Why building a DevOps team is important\n\nEven though DevOps is arguably the most efficient way to get software out the door, no one actually ever said it’s easy. So building the right DevOps team is a critical step in the process. \n\nThe right DevOps team will serve as the backbone of the entire effort and will model what success looks like to the rest of the organization. There is no “one size fits all” however – each team will be different depending on needs and resources.\n\n## 5 examples of DevOps team models\n\nHere are five DevOps organizational models to consider as you get going, according to Matthew Skelton and Manuel Pais, experts who wrote a book called Team Topologies about this topic and then updated the book with a [related microsite](https://web.devopstopologies.com). Their work is a must-read for anyone who’s trying to figure out which DevOps structure is best for their company.\n\n### **1. Dev and ops co-exist, with a “DevOps” group in between**\n\nThis can be a good interim strategy until you can build out a full DevOps program. The DevOps team translates between the two groups, which pretty much stay in place as they currently are, and DevOps facilitates all work on a project. \n\nJust don’t keep this structure in place too long. You don’t want to reinforce the separate silos as they currently exist for any longer than absolutely necessary.\n\n### **2. Dev and ops groups remain separate organizationally but on equal footing**\n \nThis is also a reasonable place to start: Everyone collaborates but can specialize where needed. Common tools will go a long way to helping facilitate good communication. In this model, several dev teams can be working on different products or services. \n\nMake sure teams communicate regularly. Invite a rep from each camp to the other’s meetings, for instance. And appoint a liaison to the rest of the company to make sure executives and line-of-business leaders know how DevOps is going, and so dev and ops can be part of conversations about the top corporate priorities.\n\n### **3. Create one team, maybe “no ops”?**\n\nIn this model, a single team has shared goals with no separate functions. The reason it’s called [“no ops”](https://searchitoperations.techtarget.com/definition/NoOps) is because ops is so automated it’s like it doesn’t actually exist. \n\nThis level of automation is so “aspirational” that many experts express caution about this approach. To eliminate any hands-on tasks, teams would need extensive machine learning and artificial intelligence solutions, and a flat, streamlined organization that prioritizes communication and workflow. TL;DR: [NoOps may not ever be a reality](https://www.cio.com/article/220351/what-is-noops-the-quest-for-fully-automated-it-operations.html).\n\nHowever, don’t use this as an excuse to do away with the ops team. You are going to need those folks. Devs can’t do it all.\n\n### **4. Ops as infrastructure consultants**\n\nThis model works best for companies with a traditional IT group that has multiple projects and includes ops pros. It’s also good for those using a lot of cloud services or expecting to do so. \n\nHere, ops acts as an internal consultant to create scalable web services and cloud compute capacity, a sort of mini-web services provider. In our [2021 Global DevSecOps Survey](/developer-survey/), a plurality of ops pros told us this is _exactly_ how their jobs are evolving — out of wrestling toolchains and into ownership of the team’s cloud computing efforts. Dev teams continue to do their work, with DevOps specialists within the dev group responsible for metrics, monitoring, and communicating with the ops team.\n\n### **5. DevOps-as-a-service**\n\nYou may decide your organization just doesn’t have the internal expertise or resources to create your own DevOps initiative, so you should hire an outside firm or consultancy to get started. This [DevOps-as-a-service (DaaS) model](https://medium.com/swlh/pros-and-cons-of-devops-as-a-service-a40b8796533c) is especially helpful for small companies with limited in-house IT skills.\n\nUsing DaaS in the short term offers another advantage: the opportunity to learn from your outsourcer how to eventually create your own internal DevOps team.\n\nMake sure you understand the outsourcer’s security landscape and your own responsibilities in this area, as you would with any outside firm. The difference here is that the team, processes, and software the outsourcer plans to use will be deeply embedded in your company’s infrastructure — it’s not something you can easily switch from. Also ensure that the outsourcer’s tools will work with what you already have in-house.\n\nFinally, keep a keen eye on costs and understand how the outsourcer will charge for its services.\n\n## Other organizational DevOps schemes include:\n\nA two-tier model, with a business systems team responsible for the end-to-end product cycle and platform teams that manage the underlying hardware, software, and other infrastructure. \nDevOps and SRE groups are separate, with DevOps part of the dev team and Site Reliability Engineers part of ops. This model requires a mature operations and development culture. \n\nWhichever organization model you choose, remember the idea of DevOps is to break down silos, not create new ones. Constantly reevaluate what’s working, what’s not, and how to deliver most effectively what your customers need.\n\n## Key characteristics of a successful DevOps team\n\nHere are some key charecteristics that you can expect to find in a well running DevOps team:\n\n* **Collaboration.** A DevOps team may have as few as 2 members to as many as 12 or more. \n* **Communication.** Nothing creates more bottlenecks on a team than members who don’t talk to each other, and DevOps projects always have a million moving parts. Document progress in a project thread, have regular meeting syncs or check in via Slack to keep team members up to speed and discuss any hurdles to avoid burnout or major delays. \n* **Team autonomy.** Work together, but also be able to work alone.\n* **Willingness to iterate.** Nothing will be perfect the first time, or even the second. In fact, a lot of DevOps work is just about making continuous, as-needed improvements to existing work, or replacing something that is no longer working for the original purpose. Keep on iterating!\n* **Fast feedback, high empathy and trust.** DevOps can feel like a whirlwind. Be mindful and respectful of the difficulties your teammates may be dealing with, be ready to give and receive feedback quickly, and trust each other for an optimal outcome and pleasant work environment.\n\n## Getting started with DevOps\n\nThere are a few steps to follow in order to get started on the planning and development of your DevOps team. Here are some pointers:\n\n**1. Create a roadmap.** Start with the basic goals, add in wish list items, and write it all out attaching a timeframe as needed. The map should include a list of action items broken down by priority and who is responsible for completing each step.\n\n**2. Ensure buy-in, and maybe add a champion.** Evangelize DevOps to the entire organization. Some teams find having a dedicated DevOps champion can help. \n\n**3. Select the solution.** Consider the budget, needs, and knowledge levels to make the best technology choices for the team.\n\n**4. Automate processes where appropriate.** DevOps doesn’t work without automation and for many teams, automation is the top priority. Look at areas where you can reduce manual work.\n\n**5. Set up monitoring.** Have a process for monitoring security, metrics, and everything in between.\nTrack progress. Always be able to give stakeholders a status update.\n\n_Johanna Ambrosio is a technology writer in the greater Boston area._\n","insights",[726,854,9],"collaboration",{"slug":856,"featured":6,"template":681},"key-organizational-models-for-devops-teams","content:en-us:blog:key-organizational-models-for-devops-teams.yml","Key Organizational Models For Devops Teams","en-us/blog/key-organizational-models-for-devops-teams.yml","en-us/blog/key-organizational-models-for-devops-teams",{"_path":862,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":863,"content":869,"config":875,"_id":877,"_type":13,"title":878,"_source":15,"_file":879,"_stem":880,"_extension":18},"/en-us/blog/learn-python-with-pj-part-1",{"title":864,"description":865,"ogTitle":864,"ogDescription":865,"noIndex":6,"ogImage":866,"ogUrl":867,"ogSiteName":669,"ogType":670,"canonicalUrls":867,"schema":868},"Learn Python with Pj! Part 1 - Getting started","Follow along as our education evangelist Pj Metz learns Python, and shares his experiences in the first of a multi-part series.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664962/Blog/Hero%20Images/python.jpg","https://about.gitlab.com/blog/learn-python-with-pj-part-1","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Learn Python with Pj! Part 1 - Getting started\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"PJ Metz\"}],\n        \"datePublished\": \"2022-02-08\",\n      }",{"title":864,"description":865,"authors":870,"heroImage":866,"date":872,"body":873,"category":852,"tags":874},[871],"PJ Metz","2022-02-08","\n\n_Hello World!_ \n\nMy name is Pj Metz and I’m the education evangelist at GitLab. My day job involves working with universities across the globe to help faculty and students learn to use GitLab for educational or research purposes. Currently, my code experience is limited to C# and JavaScript, with some HTML and CSS in there for good measure. However, one of the most popular languages in the education community is Python, so I decided to jump in and teach myself Python to better connect with my community members. \n\nI’ll be learning on [Codecademy](https://www.codecademy.com), an online interactive learning platform that offers a variety of languages and career path curriculums, both free and paid. It’s where I started learning to code back in 2020 so I’m already comfortable with it’s format and curriculum style. \n\nEvery few weeks you’ll see what I’ve learned and how I’ve applied that new knowledge. I’ll discuss the basics of writing in Python and show off some of what I’ve done. I’m still relatively new to writing code in general, so expect to see this through the eyes of a beginner — not just a Python beginner, but coding in general. I might even make a mistake in my descriptions/explanations. Let’s jump in! 🐍\n\n## First lessons\n\nThe first few lessons involved writing a “hello world” and changing the value of a premade variable.\n\n![codecademy screen showing instructions on the left, the IDE in the middle, and the output on the right](https://about.gitlab.com/images/blogimages/helloworld.png)\n\nI moved on to writing my own variables and experimenting with several different types, including ints, strings, and floats. I learned that you can change a variable after defining it, similar to many languages, and that you can even change the type; the most recently defined type will be the one used at run time. Concatenation works similarly to other languages: using a plus sign to combine variables. I did some reading ahead and learned about [f-strings](https://www.geeksforgeeks.org/formatted-string-literals-f-strings-python/) as an easy method of concatenating strings. I’m used to doing something similar in JavaScript for my [Twitter bots,](https://gitlab.com/MetzinAround/DivasLive) so this felt important to know. \n\nI also learned how to do some control flow through `if`, `elif`, and `else`. The logic remains the same, but conventions are a bit different. I’m used to writing an if statement like this in JavScript. \n\n```javascript\nif(partyRock === 'in the house tonight') {\n  everybody = 'have a good time'\n  console.log(`Party rock ${partyRock} everybody just ${everybody}`)\n} else {\n  everybody = 'sad party rock noises'\n  console.log(everybody)\n}\n\n```\nIn Python, there are no curly braces. Rather, a colon and indent takes care of that work. \n\n```python\nif partyRock == 'in the house tonight':\n   everybody = 'have a good time'\n   print(f\"Party Rock is {partyRock} everybody just {everybody}\")\nelse:\n  everybody = 'sad party rock noises'\n  print(everybody)\n```\n\n## Initial thoughts\n\nI like the readability of Python. It’s a little less cluttered, but I remember being very excited about curly braces when I first learned them. Using them for functions and methods and the like always made me feel like a “real programmer” when I was first starting. That being said, Python syntax is coming along naturally for me. \n\nSomething that’s different for me is the way Python has you initialize variables. C# is a statically typed language, meaning that part of defining a variable is saying what type of variable it is (int, string, float, etc.). Python does not require you to define the type, it will simply know at run-time. This is similar to JavaScript, but it does still throw me since I started learning with C#. Additionally, in JavaScript you have to use `let`, `var`, or `const`. In Python you just … name it and give it a value. Felt strange at first, but has become more natural as I progressed. Not having to define the type always strikes me as “weird,” but that’s personal preference, not anything actually verifiably wrong. \n\nAdditionally, the naming convention of variables is different as well. Python convention dictates underscores as spaces, while C# and JavaScript both prefer camel case, which is where each new word is capitalized. \n\n``` cs\n int minLength = 8\n```\n```javascript\nminLength = 8\n```\n\n``` python\nmin_length = 8\n```\n\nThe [naming conventions of Python](https://www.python.org/dev/peps/pep-0008/#naming-conventions) have certain rules for when to use underscores and how, especially double underscores which behave differently in Python depending on where they appear in the name. I only know what I’ve seen so far in Codecademy, but they’ve named all their variables with underscores instead of spaces. \n\n### Favorite new knowledge\n\nI really like being able to create multiple line strings simply by using three quotes, similar to using three backticks for a code block in markdown. Formatting the output has always been frustrating for me; having to remind myself that `\\n` exists and then looking up how exactly I’m supposed to use it is something I’ve spent an embarrassing amount of time on. And likely will do until the day I hang up my keyboard for good. \n\n![a code block showing a multi line sentence and the terminal output after showing correct format as dictated by the code](https://about.gitlab.com/images/blogimages/pythonmultilinestring.png)\n\nThis is nice in that how it looks in the code is how it looks in the output. I love that! \n\nThis is the first installment in the Learn Python with Pj! series. Make sure to read:\n- [Part 2 - Lists and loops](/blog/learn-python-with-pj-part-2/)\n- [Part 3 - Functions and strings](/blog/learn-python-with-pj-part-3/)\n- [Part 4 - Dictionaries and Files](/blog/learn-python-with-pj-part-4-dictionaries-and-files/)\n- [Part 5 - Build a hashtag tracker with the Twitter API](/blog/learn-python-with-pj-part-5-building-something-with-the-twitter-api/)\n\n",[766,726,9],{"slug":876,"featured":6,"template":681},"learn-python-with-pj-part-1","content:en-us:blog:learn-python-with-pj-part-1.yml","Learn Python With Pj Part 1","en-us/blog/learn-python-with-pj-part-1.yml","en-us/blog/learn-python-with-pj-part-1",{"_path":882,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":883,"content":888,"config":894,"_id":896,"_type":13,"title":897,"_source":15,"_file":898,"_stem":899,"_extension":18},"/en-us/blog/learn-python-with-pj-part-2",{"title":884,"description":885,"ogTitle":884,"ogDescription":885,"noIndex":6,"ogImage":866,"ogUrl":886,"ogSiteName":669,"ogType":670,"canonicalUrls":886,"schema":887},"Learn Python with Pj! Part 2 - Lists and loops","Follow along as our education evangelist Pj Metz learns about lists and loops in the second of this multipart series.","https://about.gitlab.com/blog/learn-python-with-pj-part-2","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Learn Python with Pj! Part 2 - Lists and loops\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"PJ Metz\"}],\n        \"datePublished\": \"2022-03-01\",\n      }",{"title":884,"description":885,"authors":889,"heroImage":866,"date":890,"body":891,"category":892,"tags":893},[871],"2022-03-01","\nWe’re back with another article about my journey to learn Python. Check out the [first article](/blog/learn-python-with-pj-part-1/) if you want to see what I’ve already learned. Today we’re talking about lists and loops, two important parts of all programming languages. Let’s check them out. \n\n## Lists\n\nLists are a way to store information that can be accessed later. They are similar to arrays in other languages. A list is a named collection of other elements inside brackets that can be accessed by an index number. \n\n``` python\n#I will be using this list for all our examples, and, yes, these are some of my favorite musical acts pulled directly from my Spotify 2021 wrapped. \nfavorite_music = ['The Midnight', 'Night Tempo', 'St. Lucia'] \n```\n\nIn this list, each element of the list can be accessed by an index number. Like many other languages, python is zero-indexed, meaning the first element is at index 0. So favorite_music[0] is “The Midnight”, favorite_music[1] is “Night Tempo”, and so on. \n\nSomething interesting about lists in Python is that a negative 1 index number will give you the last element in the list. Negative 2 will give you the second to last, and so on. As far as I can tell, this isn’t possible in other languages: Negative 1 indices will return errors or `undefined` in arrays or lists in other languages. I imagine a scenario where we’ve just added something to a list and need to access it immediately. We could use the negative index number to access the most recently added element. \n\nPython comes with several built-in methods to be used with lists. Some of them have the list passed in as an argument, some are added to the list with a `.` so it can be used. These methods will change the list or return some kind of information about the list. Below are a few I found useful, but a more complete explanation of available methods is [available here](https://docs.python.org/3/tutorial/datastructures.html). \n\n### .pop()\n\nPop allows you to remove a specific element in a list as well as return it at the same time, meaning this can be set to a variable. To specify the element, use the desired index number inside the parentheses to remove it. \n\n```python\nbest_synthwave = favorite_music.pop(0)\n\n#returns ‘['Night Tempo', 'St. Lucia']’\nprint(favorite_music)\n\n#returns 'The Midnight'\nprint(best_synthwave)\n```\n### .append() and .insert()\n\nAppend allows you to add an element to a list. Put the element in the parenthesis. The element is added to the end of the list. Insert allows you to say exactly where you would like the element inserted. The first argument is the index you would like to replace, and the second argument is the element to insert. \n\n```python\nfavorite_music.append('Turnstile') \n\n#This will print ['The Midnight', 'Night Tempo', 'St. Lucia', 'Turnstile']\nprint(favorite_music)\n\nfavorite_music.insert(1, 'Kendrick Lamar')\n#This will print 'The Midnight', 'Kendrick Lamar', 'Night Tempo', 'St. Lucia', 'Turnstile'] \n#Turnstile is still there since we appended it before. \nprint(favorite_music)\n```\n\n### len()\n\nLen gets the length of the object passed into it. This is important since you can know exactly how many elements are in a list, which is useful for control flow as well as loops. \n\n```python\nlength_of_music = len(favorite_music)\n\n#working with the original list will print “3”\nprint(length_of_music)\n```\nNotice that it prints how many elements are in the list, not how many indices. I have to work to make sure to keep those two ideas separate. So there are three elements in the list, but the indices are [0], [1], and [2]. \n\n\n## Loops\n\nLoops work very much the same way they do in other languages, but like I’ve seen with the rest of Python, the syntax is more readable and the code just looks a bit cleaner. The two main ways to use loops with Python are `for` and `while`. \n\n### for\n\nFor is used when you want to iterate through each element in an object. The syntax you use here creates a kind of one-time use variable that is then used in the code block in a variety of ways. Let’s say you want to print each band from the favorite_music list from before. \n\n```python\nfor band in favorite_music:\n  print(band)\n```\n\nThis would print each band on its own line. If you call print() on favorite_music, it would print the array inside of brackets. You can perform logic inside of for loops to only return certain items. Say you want to only print bands that have “night” in the name:\n\n```python\nfor band in favorite_music:\n    lower_case_band = band.lower()\n    if lower_case_band.__contains__('night'):\n      print(band)\n```\n\nNote: I put all the strings into lower case so we could match cases. Also, I found the contains method on the internet and the example had two underscores on either side. It made my code work whereas without the underscores it did not work. Like I said in the first article, I’m new here and don’t know why it did that.\n\n**EDIT March 7, 2022:** According to commenter \"Glen666\" in the comments, the easier way to check if something is contained in another object is to use the `in` operator. It would look like this: \n\n```python\nfor band in favorite_music:\n  lower_case_band = band.lower()\n  if \"night\" in lower_case_band:\n    print(band)\n```\nThanks for catching this. I hadn't learned `in` yet so this makes it a bit easier! \n\n### while\n\nWhile creates a loop that goes as long as certain criteria are being satisfied, usually a logic expression. If you want some code to run six times, you could use a while loop. \n\n```python\ni = 0\n#This prints the string below 6 times. \nwhile i \u003C 7:\n  print('The Midnight is my favorite band of all time.')\n  i += 1\n```\n\nThis is useful if you want code to run the whole time some circumstance is true, whether it’s a date, another process is running, or anything of the sort. \n\n**EDIT March 7, 2022:** Thanks to user \"magicolf\" in the comments! They let me know that there's an error here where it prints 7 times instead of 6. Because i is declared as `0` first, the loop will actually print seven times. It's easy to make mistakes like this all the time, so I appreciate you letting me know, magicolf! \n\nLoops are some of my favorite things to write so far. It’s like a little puzzle to figure out when you need to iterate through a list or string to make something happen at a specific time. The hardest part about loops is getting used to the logic of it. Python made this easier for me in that loops feel very natural to read. On top of that, I’m getting used to the indentation that I felt was so strange last time. I’ve spent about 30 or so hours working on it so far, and It’s starting to feel very natural. Hopefully, I can keep this up as we move on to the [next learning modules](https://about.gitlab.com/blog/learn-python-with-pj-part-3/)! \n\n","engineering",[766,726,9],{"slug":895,"featured":6,"template":681},"learn-python-with-pj-part-2","content:en-us:blog:learn-python-with-pj-part-2.yml","Learn Python With Pj Part 2","en-us/blog/learn-python-with-pj-part-2.yml","en-us/blog/learn-python-with-pj-part-2",{"_path":901,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":902,"content":907,"config":913,"_id":915,"_type":13,"title":916,"_source":15,"_file":917,"_stem":918,"_extension":18},"/en-us/blog/learn-python-with-pj-part-3",{"title":903,"description":904,"ogTitle":903,"ogDescription":904,"noIndex":6,"ogImage":866,"ogUrl":905,"ogSiteName":669,"ogType":670,"canonicalUrls":905,"schema":906},"Learn Python with Pj! Part 3 - Functions and strings","Pj shares his experiences learning how to program functions and strings.","https://about.gitlab.com/blog/learn-python-with-pj-part-3","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Learn Python with Pj! Part 3 - Functions and strings\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"PJ Metz\"}],\n        \"datePublished\": \"2022-04-04\",\n      }",{"title":903,"description":904,"authors":908,"heroImage":866,"date":909,"body":910,"category":892,"tags":911},[871],"2022-04-04","\n\n_This is the third installment in the Learn Python with Pj! series. Make sure to read [Part 1](/blog/learn-python-with-pj-part-1/) and [Part 2](/blog/learn-python-with-pj-part-2/)._\n\n\nIn learning Python, I’m happy to have found a language with a straightforward syntax that just seems to make sense. I don’t have to define a type; Python just knows. I don’t have to worry about `let` or `const` or `var` for different use cases; I just make the variable. I’m very glad I learned C# and JavaScript first, as those feel important to understanding exactly what is happening when I write code. In turn, I think it’s made Python easier for me, which is usually true when learning another programming language: Your second and third are easier to learn since your brain understands “programming logic” better now than when you made your first “Hello World”. This week we’re going to talk about what I’ve learned in functions and strings. \n\n## Functions\n\nFunctions are the backbone of any app you write. It’s an important step in learning any language to learn how to put a series of actions inside a single function that can be called later in the code. Python does this simply compared to other languages I’ve learned. \n\n```python\ndef my_first_function(arg1, arg2):\n  print(f”Your input was {arg1} and {arg2}.”)\n\n#prints “Your input was hilarious and unnecessary”\nmy_first_function(“hilarious”, “unnecessary”)\n```\nUsing the keyword `def` lets Python know that you’re about to write a function. Inside the parentheses, you put any parameters that must be included when calling the function. Some people use argument and parameter interchangeably, but technically, when defining a function, it’s a parameter, and when calling a function, it’s an argument. Either way, when defining the function, include some variables that you’ll expect when the function is called later. Finally, put a colon and then move to the next line. All the code for the function to run is indented. Inside the function, you can run loops, logic, or even other functions. Let’s check out a slightly more complex use. \n\n```python\ndef halloween_horror_nights(days, link,):\n    named = input(\"What is your name?\")\n    name = named.capitalize()\n    if isinstance(days, int):\n        if days == 0:\n            print(f\" Hello, {name}. We're ready to see you at HHN. {link}\")\n        elif days \u003C= 30:\n            print(f\"{name}, You have {days} days until the terror is home. {link}\")\n        elif days \u003C= 60:\n            print(f\"{name}, The horror comes home in {days} days. Join us in the dark. {link}\")\n        elif days \u003C 365 and days > 60:\n            print(f\"{name}, Patience is a virtue. You're {days} days away from the top rated Halloween event in the world.{link}\")\n        else:\n            print(f\"{name}, it can't be more than a year away. It's closer than you think... {link}\")\n    else:\n        print(\"Days must be an int\")\n    \n    \n#This will print “{Name input by user}, The horror comes home in 56 days. Join us in the dark. https://orlando.halloweenhorrornights.com/site\"\nhalloween_horror_nights(56, \"https://orlando.halloweenhorrornights.com/site\")\n```\n\nFor context, Halloween Horror Nights in Orlando is my favorite event of the year. This function takes in a number of days and a link (meant to be days until the event and a link to the HHN web page) and outputs a string that says how many days are left until the event. The string also includes a link to the web page and asks for user input to personalize each string. The function `isinstance()` checks if `days` is an int to make sure the sentence makes sense and returns `True` if the first argument is the type of the second argument. \n\nI really found functions in Python to be a lot easier than in other languages, though I still miss the curly brackets of C# and JavaScript. Additionally, the simplicity of `def` followed by the function name and any required parameters is really straightforward and makes reading the code easier. And since code is read more than it’s written, that makes Python pretty awesome in my book. \n\nI also used the f-string format for these print statements, and it’s still one of my favorite ways to concatenate. It feels easier than a lot of the other ways of inserting variables into a string in Python, and a little easier than the way it’s done in JavaScript, at least to me. I use a different method of including variables in a string called `.format()`. \n\nMaking your own functions is important, but there are a bunch of built-in functions in Python. There are also methods, which are similar to functions but are associated with the objects in a class they’re assigned to. Let’s talk about some strings and some methods that come with them.\n\n## Strings\n\nI thought it was strange that I had a whole section on strings in my Codecademy Python curriculum, but I soon realized that it was giving me a lot of very useful methods to use on strings that seemed very versatile. The most interesting thing to me is that strings are an object and act like a list of characters. I’m not entirely sure how strings are treated in other languages, but this really struck me as a cool idea. You can even call specific characters using the same syntax you would for a list. \n\n```python\nspooky = “Halloween Horror Nights is my favorite thing about Autumn.”\n\n#the following prints “l” since it’s the 3rd char in the string `spooky`. \nprint(spooky[2]) \n```\n\nOr you can use a for loop on a string.\n\n```python\n#This prints each letter on a new line and capitalizes it. The message now reads vertical in the output.\nfor letter in spooky:\n  print(letter.upper())\n```\n\n### String methods\t\n\nA few built-in methods exist for strings in Python, like `capitalize()` and `upper()`, two I used in the above examples. In addition to those, there are many more that can do things like remove the whitespace or noise from the beginning and end of a string, tell you the index of the first appearance of something, or join a list of strings into a single string. There are lots of great included methods. Here’s an exercise I took from Codecademy and changed the content to fit this article's theme. \n\n```python\n#given a string that contains a ton of information separated by semicolons and commas. Each part is a haunted house name, Universal Studios location, and the year the house appeared at the event.\nhhn_houses_location_year = \"Chucky;Japan;2016, Run;Orlando;2001, The Orfanage: Ashes to Ashes;Orlando;2010, The Real: Haunted Village;Japan;2021, The Undertaker: No Mercy;Hollywood;2000, Welcome to Silent Hill;Hollywood;2012, American Werewolf in London;Orlando;2013\"   \n\n#this splits the string up into a list where each element of the list is the section separated by a comma.\nhhn_houses_list = hhn_houses_location_year.split(\",\")\n\n#empty list for the next step\nhhn_houses_stripped = []\n\n#this strips any whitespace from the element in the list and adds it to the empty list from before\nfor house in hhn_houses_list:\n    hhn_houses_stripped.append(house.strip())\n\n#empty list for next step\nhhn_house_details = []\n\n#the next few lines split the details into their own list. \n#first, each house, with the details, is split along the semicolons to make a list of lists, with each house being its own element in the larger list\n#next, empty lists are made for each detail\n#finally, using index numbers, each detail is placed in it’s own list so all the houses, locations, and years are separated. \nfor info in hhn_houses_stripped:\n    hhn_house_details.append(info.split(\";\"))\n\nhouse = []\nlocation = []\nyear = []\n\nfor stuff in hhn_house_details:\n    house.append(stuff[0])\n    location.append(stuff[1])\n    year.append(stuff[2])\n\n#loops through and using .format() prints a sentence that tells about each house. \nfor num in range(0, len(house)):\n  print(\"{} was located in {} for the {} event\".format(house[num], location[num], year[num]))\n```\n\nAs you can see, I am obsessed with Halloween horror nights… er, wait, not the point of the article. As you can see, Python’s built-in methods for strings can be pretty useful, especially if you end up with a bunch of data sitting around in unformatted strings. Next time, we’re going to talk about Dictionaries and how they are used in Python! \n",[766,9,912],"tutorial",{"slug":914,"featured":6,"template":681},"learn-python-with-pj-part-3","content:en-us:blog:learn-python-with-pj-part-3.yml","Learn Python With Pj Part 3","en-us/blog/learn-python-with-pj-part-3.yml","en-us/blog/learn-python-with-pj-part-3",{"_path":920,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":921,"content":926,"config":931,"_id":933,"_type":13,"title":934,"_source":15,"_file":935,"_stem":936,"_extension":18},"/en-us/blog/learn-python-with-pj-part-4-dictionaries-and-files",{"title":922,"description":923,"ogTitle":922,"ogDescription":923,"noIndex":6,"ogImage":866,"ogUrl":924,"ogSiteName":669,"ogType":670,"canonicalUrls":924,"schema":925},"Learn Python with Pj! Part 4 - Dictionaries and Files","Our education evangelist Pj Metz continues his journey to learn how to code in Python.","https://about.gitlab.com/blog/learn-python-with-pj-part-4-dictionaries-and-files","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Learn Python with Pj! Part 4 - Dictionaries and Files\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"PJ Metz\"}],\n        \"datePublished\": \"2022-05-05\",\n      }",{"title":922,"description":923,"authors":927,"heroImage":866,"date":928,"body":929,"category":766,"tags":930},[871],"2022-05-05","\n\nThis is the fourth installment in the Learn Python with Pj! series. Make sure to read:\n- [Part 1 - Getting started](/blog/learn-python-with-pj-part-1/)\n- [Part 2 - Lists and loops](/blog/learn-python-with-pj-part-2/)\n- [Part 3 - Functions and strings](/blog/learn-python-with-pj-part-3/)\n- [Part 5 - Build a hashtag tracker with the Twitter API](/blog/learn-python-with-pj-part-5-building-something-with-the-twitter-api/)\n\nI’ve learned a lot with Python so far, but when I learned dictionaries (sometimes shortened to dicts), I was really excited about what could be done. A dictionary in Python is a series of keys and values stored inside a single object. This is kind of like a super array; one that allows you to connect keys and values together in a single easily accessible source. Creating dictionaries from arrays can actually be very simple, too.\n\nIn this blog, I'll dig into how to create dictionaries and how to read and write files in the code.\n\n## Dictionaries\n\nDictionaries in Python are indicated by using curly braces, or as I like to call them, mustaches. `{ }` indicates that the list you’re looking at isn’t a list at all, but a dictionary. \n\n```python\nshows_and _characters = {\n    \"Bojack Horseman\": \"Todd\",\n    \"My Hero Academia\": \"Midoriya\"\n    \"Ozark\": \"Ruth\"\n    \"Arrested Development\": \"Tobias\",\n    \"Derry Girls\": \"Sister Michael\",\n    \"Tuca & Bertie\": \"Bertie\"\n    }\n```\n\nThis is a dictionary of my favorite TV shows and my favorite characters in that show. In this example, the key is on the left and the value is on the right. To access dictionaries, you use a similar call like you would for a list, except instead of an element number, you would put the key. `print(shows_and_characters[“Ozark”])` would print `Ruth` to the console. Additionally, both the key and value in this example are strings, but that’s not a requirement. Keys can be any immutable type, like strings, ints, floats, and tuples. Values don’t have this same restriction, therefore values can be a nested dictionary or a list, in addition to the types mentioned for keys. For instance, the following dictionary is a valid dictionary.\n\n```python\nshows_with_lists = {\n    \"Bojack Horseman\": [\"Todd\", \"Princess Carolyn\", \"Judah\", \"Diane\"],\n    \"My Hero Academia\": [\"Midoriya\", \"Shoto\", \"All Might\", \"Bakugo\", \"Kirishima\"],\n    \"Ozark\": [\"Ruth\", \"Jonah\", \"Wyatt\"],\n    \"Arrested Development\": [\"Tobias\", \"Gob\", \"Anne\", \"Maeby\"],\n    \"Derry Girls\": [\"Sister Michael\", \"Orla\", \"Erin\", \"Claire\", \"James\"],\n    \"Tuca & Bertie\": [\"Bertie\", \"Speckle\", \"Tuca\", \"Dakota\"]\n    }\n```\nIn this example, each value is a list. So if we tried to print the value for the key `”Derry Girls”`, we would see `[“Sister Michael”, “Orla”, “Erin”, “Claire”, “James”]` printed to the console. However, if we wanted the last element in the value list, we’d write `shows_with_lists[“Derry Girls”] [-1]`. This would print the last element in the list, which in this case is `James`. \n\nDictionaries can be written manually, or, if you have two lists, you can combine the `dict()` and `zip()` methods to make the lists into a dictionary. \n\n```python\nlist_of_shows = [\"Bojack Horseman\",\n                 \"My Hero Academia\",\n                 \"Ozark\",\n                 \"Arrested Development\",\n                 \"Derry Girls\",\n                 \"Tuca & Bertie\"]\nlist_of_characters = [[\"Todd\", \"Princess Carolyn\", \"Judah\", \"Diane\"],\n                      [\"Midoriya\", \"Shoto\", \"All Might\", \"Bakugo\", \"Kirishima\"],\n                      [\"Ruth\", \"Jonah\", \"Wyatt\"],\n                      [\"Tobias\", \"Gob\", \"Anne\", \"Maeby\"],\n                      [\"Sister Michael\", \"Orla\", \"Erin\", \"Claire\", \"James\"],\n                      [\"Bertie\", \"Speckle\", \"Tuca\", \"Dakota\"]]\n\ncombined_shows_characters = dict(zip(list_of_shows, list_of_characters))\n\nprint(combined_shows_characters)\n```\n\nThis is one way to create a dictionary. Another is called Dictionary Comprehension. This one is a little more work, but can be used in a variety of different ways, including using a bit of logic on a single list to generate a dictionary using that original list. Here’s how with two examples: one based on the above lists, and one with a single list and some logic. \n\n```python\nimport math\n\n#This is doing the same work as the above example, but using Dict Comprehension instead. \ncomprehension_shows_characters = { shows:characters for shows, characters in zip(list_of_shows, list_of_characters)  }\n\nhip_to_be_square = [4, 9, 16, 25, 36, 49]\n\nno_longer_hip_to_be_square = { key:math.sqrt(key) for key in hip_to_be_square }\n\nprint(no_longer_hip_to_be_square)\n```\n\nIn the `no_longer_hip_to_be_square` dictionary, the key is found in the `hip_to_be_square` list. The value for each key is its own square root, brought in with the import math function. There are plenty more useful methods for dealing with dictionaries [here](https://realpython.com/python-dicts/). \n\n## Reading and writing files\n\nThis one is a pretty cool part of Python: reading and writing other files right in the code. With Python, you’re able to take the contents of certain types of files and use it in your code, or even create a new file based on some input. This is useful for data handling and can be used with a  variety of file types. The two I’ll be covering here are .csv and .txt.\n\n### Reading from a file\n\nImagine a .txt file named `best-ever.txt` containing the line `My favorite tv show is Derry Girls`. We can use Python to take that line and turn it into a variable. Running the following code would print the contents of the .txt file to the terminal. \n\n```python\nwith open(\"best-ever.txt\") as text_file:\n  text_data = text_file.read()\n\n#This will print the contents of the .txt file. \nprint(text_data)\n```\n\nBy using `with open(NAME OF FILE) as VARIABLE_NAME:`, we can examine the contents of files as a single string. If the document has multiple lines, you can even separate those by iterating over them by using a for loop and the `.readlines()` method. Using an imaginary .txt document called `buncha-lines` we could use the following to print out each line individually.\n\n```python\nwith open(\"buncha-lines.txt\") as lines_doc:\n  for line in lines_doc.readlines():\n    print(line)\n``` \n### Writing a new file\n\nCreating a new file is also easy with Python. The `open()` function can take an additional argument in order to create a new file. In fact, there’s a default argument that’s been being passed each time without us knowing! `r` is the default argument for `open()` and puts it in read mode. To turn on write mode, pass in a `w` as the second argument. The following code will write a brand-new file called `best_tv_character.txt` with the contents `Peggy Olson from Mad Men`. \n\n```python\nwith open(\"best_tv_character.txt\", \"w\") as best_character:\n  best_character.write(\"Peggy Olson from Mad Men\")\n```\n### Working with .csv files\n\nYou can read a .csv file with Python by using `import csv` at the beginning of the file, and then using some of its built-in methods in the code. However, even though .csv files are plain text, treating a .csv file the same as you treat .txt files can lead to difficult to read outputs; after all, the point of a spreadsheet is to table information. Without that table, the output can be chaotic. A way around this is to use the `dictreader()` method. This method allows you to map the information in each row to a dictionary with field names you can create. The default field names are collected from the first row of the .csv if no field names are given. Imagine a .csv file with columns labeled, “Network”, “Show name”, “Seasons”. Maybe we just want to print the number of seasons from this .csv. \n\n```python\nimport csv \n\nwith open(\"shows.csv\") as shows_csv:\n  shows_dict = csv.DictReader(shows_csv)\n  for row in shows_dict:\n    print(row[\"Seasons\"])\n```\n\nThis would print to the console, on a new line, the number of seasons for each row that exists in the .csv. \n\nJust like with .txt files, you can also create .csv files with Python. It’s a bit more complicated since you need to define the headers, or column names, but it is still a quick process. This can be used to take lists and turn them into .csv files. Let’s check out the following example:\n\n```python\nimport csv\n\nworking_list = [{\"Network\": \"Netflix\", \"Show Name\":\"Bojack Horseman\", \"Seasons\":6}, {\"Network\":\"Channel 4\",\"Show Name\":\"Derry Girls\", \"Seasons\": 3}, {\"Network\":\"HBO Max\", \"Show Name\":\"Our Flag Means Death\", \"Seasons\": 1}]\n\n\nwith open(\"shows.csv\", \"w\") as shows_csv:\n    fields = [\"Network\", \"Show Name\", \"Seasons\"]\n    shows_w = csv.DictWriter(shows_csv, fieldnames = fields)\n\n    shows_w.writeheader()\n    for item in working_list:\n        shows_w.writerow(item)\n```\n\nThis previous code block creates a brand-new csv file by using the `”w”` parameter in `open()`. We manually name the fields in the order they appear in a separate list, then pass that list into the `DictWriter` parameter `fieldnames`. Finally, we use the `writeheader()` and a for loop with the `writerow()` methods to create a header row and to iterate over the `working_list` and turn each entry into a row in the .csv. \n\nThese are only a few ways to work with .csv and .txt files; Python is very versatile and more information [can be found here](https://realpython.com/working-with-files-in-python/).\n",[766,912,9],{"slug":932,"featured":6,"template":681},"learn-python-with-pj-part-4-dictionaries-and-files","content:en-us:blog:learn-python-with-pj-part-4-dictionaries-and-files.yml","Learn Python With Pj Part 4 Dictionaries And Files","en-us/blog/learn-python-with-pj-part-4-dictionaries-and-files.yml","en-us/blog/learn-python-with-pj-part-4-dictionaries-and-files",{"_path":938,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":939,"content":944,"config":949,"_id":951,"_type":13,"title":952,"_source":15,"_file":953,"_stem":954,"_extension":18},"/en-us/blog/learn-python-with-pj-part-5-building-something-with-the-twitter-api",{"title":940,"description":941,"ogTitle":940,"ogDescription":941,"noIndex":6,"ogImage":866,"ogUrl":942,"ogSiteName":669,"ogType":670,"canonicalUrls":942,"schema":943},"Learn Python with Pj! Part 5 - Build a hashtag tracker with the Twitter API","Our Education Evangelist Pj Metz wraps up his five-part series with this penultimate tutorial.","https://about.gitlab.com/blog/learn-python-with-pj-part-5-building-something-with-the-twitter-api","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Learn Python with Pj! Part 5 - Build a hashtag tracker with the Twitter API\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"PJ Metz\"}],\n        \"datePublished\": \"2022-06-01\",\n      }",{"title":940,"description":941,"authors":945,"heroImage":866,"date":946,"body":947,"category":766,"tags":948},[871],"2022-06-01","\nThis is the fifth and final installment in the Learn Python with Pj! series. Make sure to read:\n- [Part 1 - Getting started](/blog/learn-python-with-pj-part-1/)\n- [Part 2 - Lists and loops](/blog/learn-python-with-pj-part-2/)\n- [Part 3 - Functions and strings](/blog/learn-python-with-pj-part-3/)\n- [Part 4 - Dictionaries and Files](/blog/learn-python-with-pj-part-4-dictionaries-and-files/)\n\n## Putting it all together\nI’ve completed my Python course on [Codecademy](https://codecademy.com/), and am excited to put the skills I learned into building something practical. I’ve worked with the Twitter API before; I wrote a few bots in Node.js to make them tweet and respond to tweets they’re tagged in. I thought it’d be fun to work with the API again, but this time do it in Python. I didn’t just want to make another bot, so I had to figure out something else. In this case, I made a bot that can track hashtags being used in real time on Twitter.\n\nHere’s [my repo](https://gitlab.com/MetzinAround/python-hashtagger) containing a few different files, but `live_tweets.py` is what we’ll focus on for this blog. Let’s talk about how I built it and what it does. \n\n```python\nimport tweepy\nimport config\n\nauth = tweepy.OAuth1UserHandler(config.consumer_key, config.consumer_secret, config.access_token, config.access_token_secret\n)\n\napi = tweepy.API(auth) \n\n#prints the text of the tweet using hashtag designated in stream.filter(track=[])\nclass LogTweets(tweepy.Stream):\n        def on_status(self, status):\n                date = status.created_at\n                username = status.user.screen_name\n                \n                try:\n                        tweet = status.extended_tweet[\"full_text\"]\n                except AttributeError:\n                        tweet = status.text\n\n                print(\"**Tweet info**\")\n                print(f\"Date: {date}\")\n                print(f\"Username: {username}\")\n                print(f\"Tweet: {tweet}\")\n                print(\"*********\")\n                print(\"********* \\n\")\n              \n\nif __name__ == \"__main__\":         \n        #creates instance of LogTweets with authentication\n        stream = LogTweets(config.consumer_key, config.consumer_secret, config.access_token, config.access_token_secret)\n\n\n        #hashtags as str in list will be watched live on twitter. \n        hashtags = []\n        print(\"Looking for Hashtags...\")\n        stream.filter(track=hashtags)\n\n\n```\n\nHere’s how this all works. First, we import two modules: [Tweepy](https://www.tweepy.org/) and config. Tweepy is a wrapper that makes using the Twitter API very easy. Config allows us to use config files and keep our secrets safe. This is important since using the Twitter API involves four keys that are specific to your Twitter developer account. Getting these keys is covered in this Twitter [documentation](https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api). We’ll talk about what’s in the config file and how it works later. \n\nThe next line defines the variable `auth` using tweepy’s built in authorization handler. Normally, you’d put in the keys directly here, but since we’re trying to keep secrets safe, we handle those through the config file. In order to call those variables hosted in the config file, we type `config.variable_name`. Finally, in order to access the tweepy api, we create the variable `api` with the auth variable from the line above passed into `tweepy.API()`. Now, the variable `api` will give us access to all the features in Tweepy’s Twitter API library. \n\n> You’re invited! Join us on June 23rd for the [GitLab 15 launch event](https://page.gitlab.com/fifteen) with DevOps guru Gene Kim and several GitLab leaders. They’ll show you what they see for the future of DevOps and The One DevOps Platform.\n\nFor our purposes, we want to find a hashtag being used, then collect the tweet that used it and print some information about the tweet to the console. To make this happen, we’ve created a class called `LogTweets` that takes an input `tweepy.Stream`. Stream is a Twitter API term that refers to all of the tweets being posted on Twitter at any given moment. Think of it as opening a window looking out onto every single tweet as it’s posted. We have to make this open connection in order to be able to find tweets that are using our hashtag. Inside `LogTweets`, we define a function called `on_status` with the parameters `self` and `status`. `On_status` will be called when a status is detected in the stream. `Self` is required as the first parameter in any class function, and `status` in this function will be referring to the status posted by a Twitter user, often called a tweet.\n\nIn our case, we’re going with status because `tweet` will represent the text of the status itself. We define `date` and `username` using Tweepy documentation: `created_at` is the date and `user.screen_name` is the username of the person who posted the status.\n\nNext is a `try/except` block. Try/except is a concept that works similarly to an if statement, but it allows for error handling a little bit better. It essentially says, “Try this, but if there’s a problem, do this instead.” In this case, we try to define the variable `tweet` as `.extended_tweet[“full_text”]`. This checks if the status we’re working with has the `extended_tweet` attribute. Twitter used to be limited to 140 characters, and when they increased the limit to 280, the `extended_tweet` became necessary.\n\nNow, if you want to capture the full tweet, you need the `extended_tweet` attribute. Inside of that attribute is the key `full_text`. Longer tweets will need that full_text or it will cut off at the 140 character limit. This `try` command checks if that key exists; if it does, `tweet` is equal to that full text.\n\nHowever, if an `AttributeError` happens, we just grab the regular text and set it equal to the variable `tweet`. Next, we print some info to the terminal. Whenever this function is called, the six lines will print to the console with the variables created above replaced by whatever status info was passed in. This makes it easier to keep track of what we’re looking at in the terminal. \n\nNext, we have an important if statement: `if __name__ == \"__main__\":`. This is used to indicate what happens when the file is run. Basically, files in Python receive a property called `__name__` from the compiler. The file that is called to be run directly is called `__main__`. Other files not run are given names equal to the file name. Therefore, anything under this if statement will only run if the file is being called directly by the compiler. \n\nNext, we create an instance of `LogTweets` called `stream`. We pass in the authentication information from the config file just like we did for the `auth` variable in the beginning of the code. This “opens up” the stream and we are now looking at all the tweets being sent in real time. In order to narrow our search, we need something to look for. The variable `hashtags` is an empty list that must be populated with strings of the hashtags we’re looking to track. This list will be put into the keyword `track` in a few lines. \n\n`Track` is an important keyword for the stream. It tells the instance what word we are looking for, input as a list of strings. These words can show up in any form, so it’s very broad.  If we didn’t put the hashtag in front of it, it would simply look for that word no matter where it showed up, so we might have too many results. By looking for hashtags, we narrow our search only to people using that specific hashtag, not just the word wherever it is. To search for terms, you have to put them into the list as a string before running the code. \n\nWhen the code is run by typing `python3 live_tweets.py` into the terminal, this is what the output looks like in the terminal.\n\n![Output in terminal](https://about.gitlab.com/images/blogimages/pythonwithpj5.png){: .shadow}\n\n\nThat’s it! That’s how the bot works, but we still need to talk about `config.py` and why we used it before. Here’s the contents of the file: \n\n```python\nimport os\nfrom dotenv import load_dotenv\n\nload_dotenv()\nconsumer_key = os.getenv(\"consumer_key\")\nconsumer_secret = os.getenv(\"consumer_secret\")\naccess_token = os.getenv(\"access_token\")\naccess_token_secret = os.getenv(\"access_token_secret\")\n```\n\nI tricked you! This doesn’t have the keys there either! Using `import os` and `import dotenv import load_dotenv` gives us access to something very important to keep secret keys safe: environmental variables. An environmental variable can be set in many different places, but in this case, our local repo has a file called `.env` that holds the actual keys.\n\nThis is there so I can test the app and run it on my machine. To use it somewhere else, you’d have to have environmental variables set up to hold the keys for the Twitter API. When I run my bots on Heroku, I keep the keys in the settings so it has access to the keys it needs to run. I use a `.gitignore` file that keeps my `.env` file from being committed to GitLab. \n\nAs you can see, the variables in `config.py` are set to `os.getenv(“name_of_key”)`. When we import `config.py` as `import config`, we gain access to these variables by calling `config.name_of_variable` in our main file. \n\nSo, for now, that’s what I built! It’s not much and I pieced it together using a lot of documentation from Twitter and Tweepy as well as a few tutorials and plenty of Stackoverflow, but it got built and it works the way I want it to!\n\nI’ve really enjoyed learning Python online and writing about it for everyone who has been reading it. I encourage anyone learning a new language or skill to write about it; it has really helped solidify my learning, and who knows, maybe I’ve helped someone else understand something in Python as well. \n\n",[766,912,9],{"slug":950,"featured":6,"template":681},"learn-python-with-pj-part-5-building-something-with-the-twitter-api","content:en-us:blog:learn-python-with-pj-part-5-building-something-with-the-twitter-api.yml","Learn Python With Pj Part 5 Building Something With The Twitter Api","en-us/blog/learn-python-with-pj-part-5-building-something-with-the-twitter-api.yml","en-us/blog/learn-python-with-pj-part-5-building-something-with-the-twitter-api",{"_path":956,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":957,"content":963,"config":969,"_id":971,"_type":13,"title":972,"_source":15,"_file":973,"_stem":974,"_extension":18},"/en-us/blog/less-headaches",{"title":958,"description":959,"ogTitle":958,"ogDescription":959,"noIndex":6,"ogImage":960,"ogUrl":961,"ogSiteName":669,"ogType":670,"canonicalUrls":961,"schema":962},"Two DevOps platform superpowers: Visibility and actionability","Migrating to a DevOps platform helps organizations better understand and improve their development lifecycle.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668622/Blog/Hero%20Images/group-rowing-collaboration.jpg","https://about.gitlab.com/blog/less-headaches","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Two DevOps platform superpowers: Visibility and actionability\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sharon Gaudin\"}],\n        \"datePublished\": \"2022-09-26\",\n      }",{"title":958,"description":959,"authors":964,"heroImage":960,"date":965,"body":966,"category":724,"tags":967},[721],"2022-09-26","\nA [DevOps platform](/blog/the-journey-to-a-devops-platform/) deployed as a single application takes DevOps gains to the next level, enabling teams to deliver more value to their organization with fewer headaches. A platform, which includes the ability to plan, develop, test, secure, and operate software, empowers teams to deliver software faster, more efficiently, and more securely. And that [makes the business more competitive and more agile](/blog/the-devops-platform-series-building-a-business-case/).\n\n## DevOps visability and actionability\n\nA complete DevOps platform gives organizations everything they need to turn ideas into valuable and secure software without the time-consuming and costly headaches that multiple tools and multiple UXes bring. A single, end-to-end platform also gives teams one data store sitting underneath everything they do, and, regardless of the interface they are using, allows them to easily surface insights about developer productivity, workflow efficiency, and DevOps practice adoption.\n\nThere are many benefits to a DevOps platform, including visibility and actionability.\n\n### Gain visibility and context\n\nA DevOps platform enables DevOps teams to see and understand what’s happening in their organization, and provide context for those events. With insights that go much deeper than what a simple report or dashboard can offer, DevOps teams can better understand the status of projects, as well as their impact.\n\n### Take action more easily\n\nActionability means users can take that contextual information and efficiently and quickly do something with it at the point of understanding. Users can move a project ahead more quickly because they don’t have to wait to have a synchronous conversation or meeting to review the new information.\n\nHere are a few ways that an end-to-end platform provides visibility and actionability.\n\n### Track projects with epics and issues\n\nIn a DevOps platform, users are better able to communicate, plan work, and collaborate by using epics and issues. [Epics](https://docs.gitlab.com/ee/user/group/epics/) are an overview of a project, idea, or workflow. Issues are used to organize and list out what needs to be done to complete the larger goal, to track tasks and work status, or work on code implementations.\n\nFor instance, if managers want an overview of how multiple projects, programs, or products are progressing, they can get that kind of visibility by checking an epic, which will give them a high-level rollup view of what is being worked on, what has been completed, and what is on schedule or delayed. Users can call up an epic to quickly see what’s been accomplished and what is still under way, and then they can dig deeper into sub-epics and related issues for more information.\n\n[Issues](https://docs.gitlab.com/ee/user/project/issues/) offer details about implementation of specific goals, trace collaboration on that topic, and show which parts of the initiative team members are taking on. Users also can see whether due dates have been met or not. Issues can be used to reassign pieces of work, give updates, make comments or suggestions, and see how the nuts and bolts are being created and moved around.\n\n### Labels help track and search projects\n\nLabels are classification tags, which are often assigned colors and descriptive titles like \"bug\", \"feature request\", or \"docs\" to make them easy to understand. They are used in epics, issues, and merge requests to help users organize their work and ideas. They give users at-a-glance insight about what teams are working on a project, the focus of the work, and where it stands in the development lifecycle. Labels can be added and removed as work progresses to enable better tracking and searching.\n\n### Dashboards help track metrics\n\nDashboards are reporting tools that pull together metrics from multiple tools to create an at-a-glance view of projects, [security issues](/blog/secure-stage-for-appsec/), the health of different environments, or requests coming in for specific departments or teams, for instance. DevOps platform users can set up live dashboards to see trends in real time, map processes, and track response times, [errors](/blog/iteration-on-error-tracking/), and deployment speed. Dashboards also can be used to see alert statuses and the effect on specific applications and the business overall.\n\n### Value stream analytics\n\nFor visibility without any customization required, there are [value stream analytics](/blog/gitlab-value-stream-analytics/). This interface automatically pulls in data to show users how long it takes the team to complete each stage in their workflow – across planning, development, deployment, and monitoring. This gives developers or product owners – or anyone who wants information on workflow efficiency –  [a look at high-level metrics](/solutions/value-stream-management/), like deployment frequency. This is actionable information so it also shows what part of the project is taking the most time or what is holding up progress. Based on this information, the user can suggest changes, like moving milestones or assigning the work to someone new, and enact those changes with just one click.\n\nWith a DevOps platform, teams have end-to-end visibility that also is actionable. By enabling users to find the information they need with the context they need and giving them the ability to make immediate changes, data becomes actionable. Using a single platform, teams can move projects along more quickly, iterate faster, and create more value and company agility.\n\nCheck out our [Migrating to a DevOps platform eBook](https://page.gitlab.com/migrate-to-devops-guide.html?_gl=1*6p1rz*_ga*MTI3MzMwNjYwMi4xNjYyOTg0OTAw*_ga_ENFH3X7M5Y*MTY2Mzk0NDY1Mi4zOS4xLjE2NjM5NDQ2NjEuMC4wLjA.) for even more useful information about how to complete a successful DevOps platform migration\n\n",[726,9,968],"performance",{"slug":970,"featured":6,"template":681},"less-headaches","content:en-us:blog:less-headaches.yml","Less Headaches","en-us/blog/less-headaches.yml","en-us/blog/less-headaches",{"_path":976,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":977,"content":983,"config":989,"_id":991,"_type":13,"title":992,"_source":15,"_file":993,"_stem":994,"_extension":18},"/en-us/blog/small-experiments-significant-results-and-learnings",{"title":978,"description":979,"ogTitle":978,"ogDescription":979,"noIndex":6,"ogImage":980,"ogUrl":981,"ogSiteName":669,"ogType":670,"canonicalUrls":981,"schema":982},"Small experiments, significant results and learnings","How our Growth team validates design solutions with the smallest experiments possible","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666775/Blog/Hero%20Images/cover.jpg","https://about.gitlab.com/blog/small-experiments-significant-results-and-learnings","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Small experiments, significant results and learnings\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Matej Latin\"}],\n        \"datePublished\": \"2021-04-07\",\n      }",{"title":978,"description":979,"authors":984,"heroImage":980,"date":985,"body":986,"category":987,"tags":988},[826],"2021-04-07","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nA while ago, I worked closely with the Growth:Expansion team on improving the experience of inviting users to GitLab. I first went through the existing experience of a user that’s inviting their team to GitLab and found a few opportunities for improvements. The work could have ended there, but I felt uneasy about it in the days after completing it. I felt that there was more to it, so I dug in again. This time, I wanted to explore what the experience was across many users involved in the process, instead of just the inviting user.\n\n## Multi-user journey map\n\nSo instead of mapping out a single-user journey map, I mapped out the journey as it was then for all the users involved. I came up with the following:\n\n![Multi-user journey map](https://about.gitlab.com/images/blogimages/small-experiments/multi-user-journey.png)\n\nTake a look at the [multi-user journey map on Mural](https://app.mural.co/t/gitlab2474/m/gitlab2474/1588920686905/a2982098783c967cee6f7e656fffe574dec0777b).\n\nI wanted to see what it was like for non-admin users to invite their team to GitLab or assign some work to them (not all managers and leads are admin users). So a non-admin user wants to assign an issue to a team member that isn’t on GitLab yet. There are three users involved: the non-admin user trying to assign some work, the admin user who is the only one who can invite new users and the user that is being invited.\nThe main conclusion of this multi-user journey map? There are many interruptions and a lot of waiting time between the steps. Such a simple task as assigning an issue to a team member can span across days because of these interruptions.\n\nThe other conclusion of this work was that it was hard to find out how to invite users to GitLab, especially for new teams trying to adopt GitLab.\n\nSo we came up with a problem to solve:\n\n> Can we make it easier for new teams to invite their team members?\n\nand a question to answer:\n\n> Would non-admin users request invitations to their team members if they could?\n\n## Making it easier for new teams to invite their team members\nWe started with the smaller problem as it was a great candidate to do a MVC (minimal viable change) experiment and learn a lot from it. The concept of the solution was simple: increase the discoverability of the *invite members* feature. After some thought, I realized that the best place for this was the Assignee dropdown that we use on issues and merge requests. It’s at the top of the right sidebar, which means it’s quite prominent, but more importantly, it is a commonly used feature related to team management.\n\nSo we decided that the most minimal experiment we could do was to add the “Invite members” link to the bottom of that dropdown and link directly to the *Settings* → *Members* page of the project. That’s the page where admin users can invite new users.\n\n![Assignee dropdown](https://about.gitlab.com/images/blogimages/small-experiments/assignee-dropdown.png){: .small.center}\n\n[Here’s the prototype](https://www.sketch.com/s/e96544d1-f2c7-45d5-a968-23e63064432d/a/bD9885/play) of the experience we tested. After crunching the numbers of the experiment we saw the following results:\n\n- Only a 0.16% click-through rate on the “Invite members” link in the dropdown\n- But a 2% increase in namespaces with two or more users\nThis was significant because we only showed the new “Invite members” link to admin users. So the low click-through rate makes sense as the majority of users viewing the assignee are not admins and therefore did not see our test \"invite members\" option. However, even with a low click-through rate, the change in the metric that mattered most saw a 2% increase. Which is a considerable increase on its own! But we’re just getting started.\n\n## Do non-admin users want to invite their team members?\n\nNow we come to the question that we uncovered during the mapping of the multi-user journey. There’s a lot of waiting time and disruptions in the process of inviting a new user to GitLab. Especially when a non-admin user wants to do it. So we decided to run a similar experiment where we show the “Invite members” link in the assignee dropdown to non-admin users too.\nFollowing our MVC approach to conducting experiments, we wanted to run a minimal experiment that would help us answer this question. Instead of taking the time to build a complete experience for non-admin users requesting invitations from admin users, we decided to show a modal explaining that this feature isn’t available yet. We also added a link that would take the non-admin user to the *Settings* → *Members* page, where they can see who the admin is and contact them outside of GitLab (for now).\n\n![Modal not ready](https://about.gitlab.com/images/blogimages/small-experiments/modal-not-ready.png)\nIt’s not the ideal experience, but the potential for learning justified it. Plus, we only show an experiment like this to a fraction of our users. The experiment has only been running for a few weeks, so it’s too early for conclusions. But we’re seeing encouraging results already, some suggest even up to a 20% increase in namespaces with two or more users so yes, it seems that non-admin users do want to invite their team members.\n\n## Other improvements and follow-up experiments\nOne major improvement that our engineers have been working on is the “Invite members” modal. Instead of taking the user out of their workflow and into the *Settings* → *Members* page, they’ll be able to invite team members within their current workflow.\n\n![Modal invite form](https://about.gitlab.com/images/blogimages/small-experiments/modal-invite.jpg)\n\n[This is a prototype](https://www.sketch.com/s/e96544d1-f2c7-45d5-a968-23e63064432d/a/wmOa8m/play) of what the experience would be like with the invite modal.\n\n## Conclusion\n\nThese experiments are the first among many that we want to conduct. We’re also thinking about allowing non-admin users to request a free trial, activation of a feature, switching to a higher plan from their admin all while potentially giving the admin the ability to turn this functionality off and on as needed. The experiments we conducted so far are indicating that there’s a demand for non-admin users to be able to request things limited to admins. And most importantly, they were minimal experiments that led to significant results and great learnings.\n\nFor more details about these experiments check\n* [the original experiment design issue](https://gitlab.com/gitlab-org/gitlab/-/issues/217921)\n* [the follow-up experiment design issue](https://gitlab.com/gitlab-org/gitlab/-/issues/235979)\n* [the video recording of experiment and results discussion between me and Sam Awezec](https://www.youtube.com/watch?v=J5h_SNH3Nt8&ab_channel=GitLabUnfiltered) (the Product Manager of Growth:Expansion)\n\nPhoto by [Evgeni Tcherkasski](https://unsplash.com/@evgenit?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/small?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n","unfiltered",[9,832,830],{"slug":990,"featured":6,"template":681},"small-experiments-significant-results-and-learnings","content:en-us:blog:small-experiments-significant-results-and-learnings.yml","Small Experiments Significant Results And Learnings","en-us/blog/small-experiments-significant-results-and-learnings.yml","en-us/blog/small-experiments-significant-results-and-learnings",{"_path":996,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":997,"content":1002,"config":1007,"_id":1009,"_type":13,"title":1010,"_source":15,"_file":1011,"_stem":1012,"_extension":18},"/en-us/blog/why-its-crucial-to-break-things-down-into-smallest-iterations",{"title":998,"description":999,"ogTitle":998,"ogDescription":999,"noIndex":6,"ogImage":980,"ogUrl":1000,"ogSiteName":669,"ogType":670,"canonicalUrls":1000,"schema":1001},"Why iterative software development is critical","How we learned from our mistakes and adopted an iterative software development mentality to reduce the likelihood of shipping something that doesn't add value.","https://about.gitlab.com/blog/why-its-crucial-to-break-things-down-into-smallest-iterations","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Why iterative software development is critical\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Matej Latin\"}],\n        \"datePublished\": \"2021-04-30\",\n      }",{"title":998,"description":999,"authors":1003,"heroImage":980,"date":1004,"body":1005,"category":892,"tags":1006},[826],"2021-04-30","\n\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2021-05-05.\n{: .note .alert-info .text-center}\n\nIn a previous blog post called [Small experiments, significant results](/blog/small-experiments-significant-results-and-learnings/) I shared our recent success with conducting small experiments, but, in reality, we didn't start with the most iterative software development approach. It was the Growth team's early failures to iterate that helped us embrace launching smaller experiments with measurable results.\n\nWhen the [Growth team](/handbook/engineering/development/growth/) formed at GitLab in late 2019, we had little experience with designing, implementing, and shipping experiments intended to accelerate the growth of our user base. We hired experienced people but it was still hard to predict [how long it would take to implement and ship an experiment](/handbook/engineering/development/growth/#running-experiments). The \"Suggest a pipeline\" experiment was the first one I worked on with the Growth:Expansion team. The idea was simple: Guide users through our UI to help them set up a [CI/CD pipeline](/blog/guide-to-ci-cd-pipelines/).\n\n![The guided tour entry](https://about.gitlab.com/images/blogimages/smallest-iterations/suggest.png)\nThe first iteration of the \"suggest a pipeline\" guided tour.\n{: .note.text}\n\n[See the original prototype of the \"suggest a pipeline\" guided tour.](https://www.sketch.com/s/1794d37d-c722-4d32-862e-9c6c5d831149/a/zn1Z9o/play)\n\nThe guided tour would start on the merge request page and ask the user if they want to learn how to set up a CI/CD pipeline. Those who opted in would be led through the three steps required to complete the setup. The team saw this as a simple three-step guide, so we committed ourselves to ship it without first considering if it was the smallest experiment we could complete. We wanted to create a guided tour because it hadn't been done yet at GitLab, but in the end, this wasn't the most iterative software development approach. Today, our thinking is: \"What's the smallest thing we can test and learn from?\"\n\nOne of GitLab's company values is [iteration](https://handbook.gitlab.com/handbook/values/#iteration) which means that we strive to do *the smallest thing possible and get it out as quickly as possible*. The concept of [MVC (minimal viable change)](https://handbook.gitlab.com/handbook/values/#minimal-viable-change-mvc) guides this philosophy:\n\n> We encourage MVCs to be as small as possible. Always look to make the quickest change possible to improve the user's outcome.\n\nWhile looking back, I realized we failed to embrace the MVC with the \"suggest a pipeline\" experiment, but I'm grateful for that mistake because it provided us with one of the most valuable lessons: Always strive to complete the smallest viable change first. The idea of iterative software development is valuable even, or maybe especially, with experiments.\n\nBelow are five reasons why it's important to break development down. Small iterations:\n\n- Gets value to the user faster.\n- Decreases the risk of shipping something that doesn't add value.\n- Are easier to isolate and understand the impact of the changes.\n- Ship faster so the team starts learning sooner.\n- Allow teams to begin thinking about further iterations sooner or decide to abandon the experiment earlier (saving both time and resources).\n\n![Small vs large iterations](https://about.gitlab.com/images/blogimages/smallest-iterations/chart.jpg)\nThe power of iterative software development is clear by the two workflows.\n{: .note.text}\n\n\nIn the \"non-experimental work\" figure above, team one shipped a smaller iteration quickly and updated it twice, while team two only shipped one large iteration in the same time. Team one learned from their first small iteration and adapted their solution twice in the time team two shipped a larger iteration. It took team two longer to ship the large iteration and they sacrificed earlier findings they could have used to optimize their solution.\n\nIn the \"experimental work\" figure, team one shipped a smaller first iteration and reviewed early results, which helped them make an evidence-based decision as to iterate further on their first idea, or abandon it and move on to a new idea. Through this iterative software development process, they could either ship three iterations of their first idea or abandon it and start working on the first iteration of idea two. Team one could accomplish all this development in the same amount of time it took team 2 to ship a larger first iteration of idea one. Team one is much more likely to come to successful results and learnings faster than team two.\n\n## How the \"suggest a pipeline\" experiment _should_ have been done\n\nIt's easy to reflect on our project today and see what we did wrong, but such reflection allows us to avoid repeating mistakes. The GitLab guided tour looked like a simple experiment to build and ship, but in the end it wasn't and took months to complete. Overall, the experiment was successful, but after it was implemented we took a second look and saw the project could be improved. We decided to implement some improvements by iterating on the copy in our first nudge to users to encourage more users to opt-in. Had we shipped a smaller experiment sooner, we could have iterated earlier and delivered an optimal version of the first nudge, allowing more users to benefit from the guided tour.\n\n![Had we shipped a smaller iteration, we would have improved the copy of our opt-in nudge to users sooner.](https://about.gitlab.com/images/blogimages/smallest-iterations/copy-changes.jpg)\nThe second iteration of our opt-in copy is much stronger. Shipping a smaller iteration would have encouraged more users to opt-in to our experimental \"guided tour\" feature.\n{: .note.text}\n\nBecause it took us months to complete the implementation of the experiment, it also took us months to iterate on it.\n\nIf I had to do a similar experiment now, I'd start much smaller, with something that could be built and shipped in less than a month, ideally even faster. For example, we could have shipped an iteration with that first nudge linking to an existing source that explains how to set up a pipeline. That would have enabled us to validate the placement of the nudge, its content, and its effectiveness. It would have significantly reduced the risk of the experiment.\n\nOr maybe we could have [shortened the guided tour to be just two steps](https://gitlab.com/gitlab-org/growth/product/-/issues/1662/), which is exactly what [Kevin Comoli](/company/team/#kcomoli), product designer on Growth: Conversion, did. But because our idea already seemed like a small iteration, we never felt the urgency to reduce it further. So here's another reason why it's important to really think about the smallest possible iteration first: you can never be sure that what you're aiming to do will actually be as quick and simple as expected. So even when you think that your idea is the smallest possible iteration, *think again*.\n\n## How we're applying lessons on iteration to future experiments\n\nWhen I started working on the [\"invite members\" experiment](/blog/small-experiments-significant-results-and-learnings/), my vision of how the experience should be was more complex than the \"suggest a pipeline\" guided tour experience. The idea behind the \"invite members\" experiment was that any user could invite their team members to a project and an admin user would have to approve the invitation. But because of our learnings from the pipeline tour we decided to simplify the first experiment. Instead of designing and building a whole experience, we decided to use a [painted door test](https://crstanier.medium.com/a-product-managers-guide-to-painted-door-tests-a1a5de33b473), which essentially means we are focusing on tracking the main call-to-action to gauge user interest. For the \"invite members\" experiment, the painted door test involved displaying an invite link that, once clicked, displayed a message to users that the feature wasn't ready and suggested a temporary solution. This allowed us to validate the riskiest part of the experiment: Do non-admin users even _want_ to invite their colleagues?\n\n![Modal showing \"invite members\" feature isn't ready yet](https://about.gitlab.com/images/blogimages/smallest-iterations/modal-not-ready.png)\nThe \"invite members\" painted door experiment involved displaying a modal showing that the feature wasn't ready yet, but helped us still gauge user interest in the feature before investing resources in developing the feature.\n{: .note.text}\n\n## Why iterative software development matters\n\nWe were lucky with the \"suggest a pipeline\" experiment. It was the first experiment we worked on, and it was \"low hanging fruit\", meaning it was a solution that required limited investment but still delivered big returns, which made the chance of failure lower. As we move away from obvious improvements and start exploring riskier experiments, we won't be able to rely on luck. We need to be diligent about iteration and break things down into MVCs and smaller experiments to reduce the risk of investing development time on projects that don't add value to the user experience, or fail to have a positive impact on GitLab's growth.\n\nPhoto by [Markus Spiske](https://unsplash.com/@markusspiske?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/pieces?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[9,832,830],{"slug":1008,"featured":6,"template":681},"why-its-crucial-to-break-things-down-into-smallest-iterations","content:en-us:blog:why-its-crucial-to-break-things-down-into-smallest-iterations.yml","Why Its Crucial To Break Things Down Into Smallest Iterations","en-us/blog/why-its-crucial-to-break-things-down-into-smallest-iterations.yml","en-us/blog/why-its-crucial-to-break-things-down-into-smallest-iterations",{"_path":1014,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1015,"content":1021,"config":1027,"_id":1029,"_type":13,"title":1030,"_source":15,"_file":1031,"_stem":1032,"_extension":18},"/en-us/blog/benefits-of-corporate-shadow-programs",{"title":1016,"description":1017,"ogTitle":1016,"ogDescription":1017,"noIndex":6,"ogImage":1018,"ogUrl":1019,"ogSiteName":669,"ogType":670,"canonicalUrls":1019,"schema":1020},"Shadow programs give employees a peek into leadership roles","Shadow programs are a great resource if you’re looking to explore new roles, expand your skill set, or learn how decisions are made.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683055/Blog/Hero%20Images/ideaabstract.jpg","https://about.gitlab.com/blog/benefits-of-corporate-shadow-programs","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Shadow programs give employees a peek into leadership roles\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fatima Sarah Khalid\"}],\n        \"datePublished\": \"2023-07-17\",\n      }",{"title":1016,"description":1017,"authors":1022,"heroImage":1018,"date":1024,"body":1025,"category":766,"tags":1026},[1023],"Fatima Sarah Khalid","2023-07-17","\nHave you ever wished you could see into your manager's mind or understand the nitty-gritty details that make your organization run smoothly? Well, that's exactly what corporate shadow programs can do for you. They enable you to tag along with senior colleagues during regular workdays. You can witness how they tackle tasks, make decisions, and interact with various stakeholders. It's like getting a peek behind the scenes of your organization.\n\nAt GitLab, we host several shadow programs, including one that lets you [learn from GitLab CEO and co-founder Sid Sijbrandij](https://about.gitlab.com/handbook/ceo/shadow/). Each program aims to give team members a deeper understanding of different parts of our company's operations and processes. The experience empowers individuals to connect their work to the company's broader goals and gain valuable skills for their professional growth. Shadow programs benefit mentees, mentors, and organizations.\n\nThe following are some benefits of a shadow program:\n* [Insight into decision-making processes](#insight-into-decision-making-processes)\n* [Collaboration with leadership teams](#collaboration-with-leadership-teams) \n* [Personalized learning opportunities](#personalized-learning-opportunities) \n* [Cross-functional interactions](#cross-functional-interactions) \n\n## Insight into decision-making processes\nShadow programs can act as your backstage pass to the operational aspects of your organization. As you gain more insight into how your organization works and how decisions are made, you may start to appreciate the complexity that goes into keeping things running. Knowing how all the pieces work together will improve how you collaborate. At GitLab, shadows are able to observe [GitLab values](https://handbook.gitlab.com/handbook/values/) in action: Collaboration, Results, Efficiency, Diversity, Inclusion & Belonging, and Transparency.\n\n> From the perspective of operating the company on a daily basis, I was witness to how decisions were made at the leadership level of the organization. There were e-group meetings in which different topics were discussed, from mergers and acquisitions and sales compensation plans to hiring and team member morale. I was impressed by the camaraderie, collaboration, and great rapport among members of the e-group. Decisions were taken only after a thorough discussion had taken place and everyone was encouraged to participate.\n- *[Cesar Saavedra](https://gitlab.com/csaavedra1), [Being a GitLab CEO Shadow](https://www.linkedin.com/pulse/being-gitlab-ceo-shadow-cesar-saavedra/)*\n\n## Collaboration with leadership teams\nMembers of your executive team can sometimes feel like distant figures in your organization. Shadow programs put employees together with executives and other leaders, making them feel more approachable. This connection not only strengthens bonds across various departments and cultivates a positive work environment, but it can also inspire the shadowing employees to feel confident enough to pursue leadership positions themselves.\n\n> The CEO shadow program is such a great way to give team members insight into how the company works, while also making the company feel more inclusive, and its top-level team members feel more approachable. While I’ve always found Sid to be friendly and down to earth, I know that some people are afraid of approaching their manager with something, let alone someone at or near the top. I somewhat jokingly said to someone that it’s a good reminder that our executives are 'real' people.\n- *[Cynthia Ng](https://gitlab.com/cynthia), [Reflection on my CEO shadow rotation at GitLab](https://cynthiang.ca/2022/01/07/reflection-on-my-ceo-shadow-rotation-at-gitlab/)*\n\n## Personalized learning opportunities \nShadow programs at GitLab offer personalized learning opportunities. You can learn on the job from experienced team members, oftentimes from those with roles above yours. The experience is incredibly valuable, as seen from many of the reflections that GitLab team members have written about their shadow program experiences. The program fosters open communication, creating a pathway for better knowledge sharing across teams.\n\nShadowing also provides an opportunity for employees to be exposed to new situations and learn new skills.\n\n> In this meeting, a variety of different vice presidents and engineering managers discussed error budgets, reliability, and security. The [service-level agreement] requirements, security issues, and corrective actions were discussed. We went over what issues are currently affecting our error budgets and must be remediated. Root causes were analyzed and then a plan was made for remediation. This has shown me the efficiency of having all the information on a single document and then discussing proposals to correct. This makes the meeting flow much easier than not having any data beforehand. \n- *[Fernando Diaz](https://gitlab.com/fjdiaz), [What I learned as a Development Director Shadow at GitLab](https://awkwardferny.medium.com/what-i-learned-as-an-engineering-director-shadow-at-gitlab-1a783cb564d0)*\n\n## Cross-functional interactions \nWith a shadow program in another department or role, you will get the opportunity to experience work outside of your immediate team circle. The exposure can help broaden your understanding of the overall organization and how other teams work. You can develop new skills that prepare you for future opportunities. The relationships you build as a shadow across different groups will also stay with you after the program.\n\n> As an open-source contributor, I had some understanding of how GitLab worked. But during the shadowing week, I got to see the inner workings of the company, how teams collaborate, and how the company operates at scale.\n- *[Siddharth Asthana](https://gitlab.com/edith007), [My experience as a GitLab Hero in Developer Director Shadow Program](https://www.linkedin.com/pulse/my-experience-gitlab-hero-developer-director-shadow-program-asthana/)*\n\n## How to start a shadow program\nHere are some tips to help you start a shadow program at your organization:\n* Set clear expectations and guidelines for the program\n* Arrange flexible schedules that suit both the mentee and the mentor\n* Encourage open discussion and feedback\n* Ensure the program is tailored to meet the needs of participants\n* Implement confidentiality guidelines to protect sensitive information\n* Define the tasks for the participants, such as note-taking or updating the handbook\n* Determine the time commitment of rotations\n* Create opportunities for shadows to contribute such as helping to complete tasks\n\nCheck out our shadow programs for examples of how to structure them:\n* [CEO Shadow Program](https://about.gitlab.com/handbook/ceo/shadow/)\n* [Support Shadow Program](https://about.gitlab.com/handbook/support/#support-shadow-program) \n* [Director of Development Shadow Program](https://about.gitlab.com/handbook/engineering/development/shadow/director-shadow-program.html)\n* [CFO Shadow Program](https://about.gitlab.com/handbook/finance/growth-and-development/cfo-shadow-program/)\n\nMore resources:\n* [15 tips to succeed at GitLab's CEO Shadow program](https://about.gitlab.com/blog/get-the-most-out-of-a-ceo-shadow-program/)\n* [CEO Shadow program impressions and takeaways](https://about.gitlab.com/blog/ceo-shadow-impressions-takeaways/)\n* [The engineering director shadow experience at GitLab](https://about.gitlab.com/blog/engineering-director-shadow/)\n\n\n\n\n",[766,9,832],{"slug":1028,"featured":6,"template":681},"benefits-of-corporate-shadow-programs","content:en-us:blog:benefits-of-corporate-shadow-programs.yml","Benefits Of Corporate Shadow Programs","en-us/blog/benefits-of-corporate-shadow-programs.yml","en-us/blog/benefits-of-corporate-shadow-programs",2,[662,686,711,735,753,773,792,816,839],1753475285957]