Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions website/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
const _ = require('lodash')
const parseFilePath = require('parse-filepath')
const path = require('path')
import slugify from 'slugify'

function kebabcase(str) {
result = str.toLowerCase()
// Convert non-alphanumeric characters to hyphens
result = result.replace(/[^-'a-z0-9]+/g, '-')
result = result.replace(/'/, '')
// Remove hyphens from both ends
result = result.replace(/^-+/, '').replace(/-$/, '')
result = result.replace(/(-)\1{1,}/, '-')

return result
}
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions

Expand Down Expand Up @@ -108,13 +98,13 @@ exports.onCreateNode = ({ node, actions, getNode }) => {
if (fileNode.relativePath) {
const parsedFilePath = parseFilePath(fileNode.relativePath)
const name = node.frontmatter.title
const kebabName = kebabcase(name)
const slug = slugify(name)
if (parsedFilePath.name !== `index` && parsedFilePath.dirname !== '' && !node.frontmatter.permalink) {
slug = `/${parsedFilePath.dirname}/${kebabName}/`
slug = `/${parsedFilePath.dirname}/${slug}/`
} else if (parsedFilePath.name !== `index` && node.frontmatter.slug && !node.frontmatter.permalink) {
slug = `${node.frontmatter.slug}`
} else if (parsedFilePath.name !== `index` && !node.frontmatter.permalink) {
slug = `/${kebabName}/`
slug = `/${slug}/`
} else if (parsedFilePath.name !== `index`) {
slug = node.frontmatter.permalink
} else {
Expand Down
2 changes: 2 additions & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"lodash": "^4.17.4",
"mdi-react": "^5.3.0",
"node-sass": "^4.9.4",
"parse-filepath": "^1.0.2",
"prismjs": "^1.14.0",
"react": "^16.5.2",
"react-bootstrap": "^1.0.0-beta.8",
Expand Down Expand Up @@ -68,6 +69,7 @@
"gatsby-plugin-tslint": "^0.0.2",
"prettier": "^1.17.0",
"sharp": "^0.22.1",
"slugify": "^1.3.4",
"stylelint": "^10.0.1",
"stylelint-scss": "^3.6.0",
"tslint": "^5.16.0",
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/Jumbotron.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'

const COLORS = {
export const COLORS = {
dark: 'bg-black text-light',
purple: 'bg-purple text-light',
}
Expand Down
79 changes: 79 additions & 0 deletions website/src/components/content/CaseStudyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react'
import { COLORS } from '../Jumbotron'
import { pbkdf2 } from 'crypto'
import slugify from 'slugify'

interface Props {
title: string
logo: string
pdf?: string
className: string
children: React.ReactNode
}

export const CaseStudyPage: React.FunctionComponent<Props> = ({
title,
logo,
className = slugify(title),
pdf,
children,
}) => (
<div className={className}>
<CaseStudyJumbotron className="mb-5" title={title} logo={logo} pdf={pdf}></CaseStudyJumbotron>
{children}
</div>
)

export const CaseStudyJumbotron: React.FunctionComponent<{
logo: string
title: string
className?: string
pdf?: string
color?: keyof typeof COLORS
titleClassName?: string
children: React.ReactNode
}> = ({ logo, title, className = '', color = 'dark', titleClassName = 'display-3', pdf, children }) => (
<div className={`jumbotron rounded-0 ${COLORS[color]} ${className}`}>
<div className="container text-center pt-6 pb-5">
<img className="case-studies__logo" src={logo} />
<h1 className={titleClassName}>{title}</h1>
{pdf && (
<a href={pdf} class="btn btn-primary mt-4" rel="nofollow">
<i class="fa fa-file-pdf pr-2"></i>
Download PDF
</a>
)}
{children}
</div>
</div>
)

export const MediaQuote: React.FunctionComponent<{
image: string
quote: string
author: string
}> = ({ image, quote, author }) => (
<div className="container">
<div class="case-studies__quote row">
<div class="col-sm-12 col-md-2">
<img className="rounded-circle img-fluid mx-auto d-block py-4" src={image} alt={author} />
</div>
<div class="col">
<blockquote class="blockquote">
<p>{quote}</p>
<footer class="blockquote-footer">{author}</footer>
</blockquote>
</div>
</div>
</div>
)

export const InContentBlockquote: React.FunctionComponent<{
quote: string
author: string
}> = ({ quote, author }) => (
<blockquote class="blockquote case-studies__quote case-studies__quote--in-content">
<p>{quote}</p>
{author && <footer class="blockquote-footer">{author}</footer>}
</blockquote>
)
4 changes: 1 addition & 3 deletions website/src/components/product/CustomerLogosSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ export const CustomerLogosSection: React.FunctionComponent<{ trustWhat?: string;
className="customer-logos-section__item mx-2 d-flex justify-content-center flex-column"
>
<div
className={`customer-logos-section__item-logo customer-logos-section__item-logo-synthesized mx-auto border rounded px-3 font-weight-bold d-flex align-items-center ${
logo.className
}`}
className={`customer-logos-section__item-logo customer-logos-section__item-logo-synthesized mx-auto border rounded px-3 font-weight-bold d-flex align-items-center ${logo.className}`}
>
Top {logo.topN}
<br />
Expand Down
71 changes: 71 additions & 0 deletions website/src/css/pages/__case_studies.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.case-studies {
h2 {
font-size: 1.6rem;
}

&__wrapper {
width: 66.667%;
margin: auto;
max-width: 740px; // Copied from medium.com
}

&__title {
&--rule {
margin: 25px 0;
}
}

&__logo {
margin: 0 0 2rem 0;
height: 3rem;
}

&__quote {
img {
width: 100%;
max-width: 200px;

}

p {
text-indent: 0.4em;
font-weight: 600;
color: #212529;
font-size: 1.5rem;
line-height: 1.2;
letter-spacing: -0.015em;

&:before {
text-indent: -0.4em;
content: "“";
position: absolute;
margin-left: -2px;
}

@include media-breakpoint-up(lg) {
font-size: 2.2rem;
}

&:after {
content: "”";
margin-left: 2px;
}
}

footer {
margin-top: 2rem;
font-size: 1.2rem;
font-weight: 400;
}

&--in-content {
margin: 2rem 0;

p {
font-weight: 500;
font-size: 1.5rem;
line-height: 1.3;
}
}
}
}
1 change: 1 addition & 0 deletions website/src/css/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ img.icon-inline,
@import 'pages/browser';
@import 'pages/blog';
@import 'pages/blog-post';
@import 'pages/_case_studies';
@import 'pages/content';
@import 'pages/index';
@import 'pages/jobs';
Expand Down
165 changes: 165 additions & 0 deletions website/src/pages/case-studies/we-are-thorn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import * as React from 'react'
import Helmet from 'react-helmet'
import { CaseStudyPage, InContentBlockquote, MediaQuote } from '../../components/content/CaseStudyPage'
import { ContentPage } from '../../components/content/ContentPage'
import { ContentSection } from '../../components/content/ContentSection'
import Layout from '../../components/Layout'
import { ContactPresalesSupportAction } from '../../css/components/actions/ContactPresalesSupportAction'
import { RequestDemoAction } from '../../css/components/actions/RequestDemoAction'
import { ViewDeveloperDocumentationAction } from '../../css/components/actions/ViewDeveloperDocumentationAction'

export default ((props: any) => (
<Layout location={props.location}>
<Helmet>
<title>Sourcegraph Case study - Thorn</title>
<meta
name="twitter:title"
content="How Thorn uses Sourcegraph to sunset legacy applications with zero downtime"
/>
<meta
property="og:title"
content="How Thorn uses Sourcegraph to sunset legacy applications with zero downtime"
/>
<meta
name="twitter:description"
content="Learn how Sourcegraph code search enabled Thorn to systematically sunset legacy systems, removing huge amounts of tech debt in the process."
/>
<meta
property="og:description"
content="Learn how Sourcegraph code search enabled Thorn to systematically sunset legacy systems, removing huge amounts of tech debt in the process."
/>
<meta
name="description"
content="Learn how Sourcegraph code search enabled Thorn to systematically sunset legacy systems, removing huge amounts of tech debt in the process."
/>
<link rel="icon" type="image/png" href="https://about.sourcegraph.com/favicon.png" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css"
></link>
</Helmet>

<CaseStudyPage
title="How Thorn uses Sourcegraph to sunset legacy applications with zero downtime."
logo="/case-studies/thorn-logo.png"
pdf="https://storage.googleapis.com/sourcegraph-assets/Thorn%20Sourcegraph%20case%20study.pdf"
className="thorn-case-study"
>
<ContentSection color="white" className="pt-5 pb-3">
<MediaQuote
image="/case-studies/jacob-gillespie-thorn-square.jpg"
quote="It was time-consuming for developers and reviewers to ensure that changes to legacy systems didn’t affect our production stability."
author="Thorn Software Engineer Jacob Gilesspie"
/>
</ContentSection>

<ContentSection color="white" className="col-md-6">
<h2>Thorn's mission</h2>

<p>
Thorn builds technology to defend children from sexual abuse. Their work focuses on finding victims
of child sex trafficking faster, and eliminating child sexual abuse from the internet.Thorn partners
with tech companies, law enforcement, as well as other NGOs, to build products that provide the
front lines with the latest technology to find the most vulnerable child victims faster.
</p>
<p>
Thorn’s life changing work and not-for-profit status made it a simple decision to provide
Sourcegraph’s enterprise features free of charge. For many children, Thorn plays a critical role in
speeding up the time it takes for them to be identified and we’re proud to be supporting them with
their mission.
</p>

<h2 class="pt-5 pb-1">
Sunsetting deprecated systems was previously costly and risked production stability
</h2>
<p>
As Thorn’s products evolved, it became difficult to trace the impact of code changes to core
application components, as it was difficult to determine what code might rely on legacy
architecture, risking instability and greatly increasing the demand on development and review.
</p>

<InContentBlockquote quote="It was time-consuming for developers and reviewers to ensure that changes to legacy systems didn’t affect our production stability." />

<p>
Over 9,000 officers in 38 countries rely on Thorn’s systems to identify child victims of sexual
abuse. Any downtime of these services has a negative impact on Thorn’s ability to identify children
globally.
</p>

<h2 class="pt-5 pb-1">Existing tooling was not sufficient</h2>

<p>
Tech debt, and the upkeep of legacy code was becoming increasingly problematic. Previous attempts,
such as cloning all repositories locally and using grep to find references, were inadequate,
especially when considering simultaneous development by multiple teams across many different
projects, repositories, and branches. It was costly to determine if all the different microservices
were properly in sync when removing legacy application code.
</p>

<p>
Sourcegraph’s multi-repository code search, was able to prove that no code referencing legacy
systems exists organization-wide
</p>

<p>
Thorn Software Engineer Jacob Gillespie deployed Sourcegraph and synced Thorn’s entire list of
repositories within minutes.
</p>

<InContentBlockquote quote="With Sourcegraph, we could search over the contents of every repository, in any or all branches in seconds." />

<p>
Sourcegraph code search gave Thorn the ability to find references to deprecated systems. But more
importantly, it proved itself to be an invaluable new part of their code review process.
</p>

<InContentBlockquote quote="In pull requests, team members would include links to Sourcegraph code search, in order to prove all references to a deprecated system had been removed. This gave the reviewer confidence that the code was safe to merge." />

<h2 class="pt-5 pb-1">As a result, deprecated systems were taken offline without downtime.</h2>
<p>
Thorn’s developers could then systematically remove or modify deprecated systems, removing huge
amounts of tech debt in the process. This benefited all areas of the architecture, including not
only application code, but also build, deployment, logging, and monitoring systems—any tool that
supported the deployment and uptime of the application.
</p>

<p>
Modern microservice architecture makes the application deprecation process more challenging than
ever.{' '}
</p>

<p>
<strong>
Sourcegraph’s code search enables developers and DevOps teams to find dead code, unused
packages, and references to deprecated systems, organization wide across tens of thousands of
repositories.
</strong>
</p>

<p class="pb-4">
Being able to take advantage of industry leading solutions like Sourcegraph provides critical
support to Thorn’s mission. Every start-up has to make choices about when to rebuild their systems
and when to move forward accruing technical debt, Thorn is no different, but here at Sourcegraph,
we’re proud that our tools could be used to make the Thorn team a little more successful, a little
faster.
</p>
</ContentSection>
</CaseStudyPage>
<ContentPage
title="Code search and navigation"
description="Code search helps you grok code so you can write better code more quickly. Sourcegraph's code search is used by elite software teams."
mainActions={
<div className="d-flex flex-column align-items-center">
<RequestDemoAction className="mt-3" />
<ContactPresalesSupportAction className="mt-3 text-light" />
<ViewDeveloperDocumentationAction
className="text-light mt-2"
url="https://docs.sourcegraph.com/#quickstart"
>
Documentation &amp; self-service install
</ViewDeveloperDocumentationAction>
</div>
}
></ContentPage>
</Layout>
)) as React.FunctionComponent<any>
Loading