diff --git a/astro.config.mjs b/astro.config.mjs index 0580c7b9..232fbfd9 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -508,6 +508,10 @@ export default defineConfig({ collapsed: true, autogenerate: { directory: '/snowflake/integrations' }, }, + { + label: 'Sample Apps', + slug: 'snowflake/sample-apps', + }, { label: 'Tutorials', collapsed: true, diff --git a/src/assets/images/snowflake/sample-apps/snowflake-polaris-trino-architecture.png b/src/assets/images/snowflake/sample-apps/snowflake-polaris-trino-architecture.png new file mode 100644 index 00000000..384f9a60 Binary files /dev/null and b/src/assets/images/snowflake/sample-apps/snowflake-polaris-trino-architecture.png differ diff --git a/src/assets/images/snowflake/sample-apps/snowflake-smart-factory-app-architecture.png b/src/assets/images/snowflake/sample-apps/snowflake-smart-factory-app-architecture.png new file mode 100644 index 00000000..703ee58f Binary files /dev/null and b/src/assets/images/snowflake/sample-apps/snowflake-smart-factory-app-architecture.png differ diff --git a/src/components/ApplicationsShowcase.astro b/src/components/ApplicationsShowcase.astro index a7d11d2a..28491897 100644 --- a/src/components/ApplicationsShowcase.astro +++ b/src/components/ApplicationsShowcase.astro @@ -2,39 +2,62 @@ import { ApplicationsShowcase } from './applications/ApplicationsShowcase'; import { getImage } from 'astro:assets'; +interface Props { docs?: 'aws' | 'snowflake' } +const { docs = 'aws' } = Astro.props as Props; + // Import data import applicationsData from '../data/developerhub/applications.json'; import services from '../data/developerhub/services.json'; import integrations from '../data/developerhub/integrations.json'; -const applications = applicationsData.applications; +const allApplications = applicationsData.applications; +const applications = allApplications.filter((app: any) => app.docs === docs); -const images = import.meta.glob<{ default: ImageMetadata }>( - '/src/assets/images/aws/sample-apps/*.{jpeg,jpg,png,gif}' +const awsImages = import.meta.glob<{ default: ImageMetadata }>( + '/src/assets/images/aws/sample-apps/*.{jpeg,jpg,png,gif,svg}' +); +const snowflakeImages = import.meta.glob<{ default: ImageMetadata }>( + '/src/assets/images/snowflake/sample-apps/*.{jpeg,jpg,png,gif,svg}' ); const applicationsUpdated = await Promise.all( - applications.map(async (application) => { + applications.map(async (application: any) => { const updatedApplication = { ...application }; - const imagePath = `/src/assets/images/aws/sample-apps/${application.teaser}`; - - if (images[imagePath]) { - const optimizedLeadImage = await getImage({ - src: images[imagePath](), - format: 'png', - width: 800, - quality: 90, - }); - updatedApplication.teaser = optimizedLeadImage.src; + if (docs === 'aws') { + const imagePath = `/src/assets/images/aws/sample-apps/${application.teaser}`; + if (awsImages[imagePath]) { + const optimizedLeadImage = await getImage({ + src: awsImages[imagePath](), + format: 'png', + width: 800, + quality: 90, + }); + updatedApplication.teaser = optimizedLeadImage.src; + } + } else if (docs === 'snowflake') { + const teaserName = String(application.teaser || '').split('/').pop(); + const imagePath = teaserName ? `/src/assets/images/snowflake/sample-apps/${teaserName}` : ''; + if (teaserName && snowflakeImages[imagePath]) { + const optimizedLeadImage = await getImage({ + src: snowflakeImages[imagePath](), + format: 'png', + width: 800, + quality: 90, + }); + updatedApplication.teaser = optimizedLeadImage.src; + } else if (teaserName) { + updatedApplication.teaser = `/images/snowflake/sample-apps/${teaserName}`; + } } return updatedApplication; }) ); ---- +--- diff --git a/src/components/applications/ApplicationsShowcase.tsx b/src/components/applications/ApplicationsShowcase.tsx index 58fcd1a4..dcbd941f 100644 --- a/src/components/applications/ApplicationsShowcase.tsx +++ b/src/components/applications/ApplicationsShowcase.tsx @@ -3,33 +3,40 @@ import React, { useState, useMemo } from 'react'; interface Application { name: string; description: string; - githubUrl: string; + githubUrl?: string; + docsUrl?: string; teaser: string; services: string[]; integrations: string[]; useCases: string[]; + // Snowflake-only + features?: string[]; } interface FilterState { services: string[]; useCases: string[]; integrations: string[]; + // Snowflake-only + features: string[]; } interface ApplicationsShowcaseProps { applications: Application[]; services: Record; integrations: Record; + docs?: 'aws' | 'snowflake'; } const ApplicationCard: React.FC<{ app: Application; services: Record; integrations: Record; -}> = ({ app, services, integrations }) => { + docs?: 'aws' | 'snowflake'; +}> = ({ app, services, integrations, docs }) => { return ( {app.name} - - {app.services.slice(0, 10).map((serviceCode) => ( - - - - ))} - {app.services.length > 10 && ( - +{app.services.length - 10} - )} - + {docs === 'aws' && ( + + {app.services.slice(0, 10).map((serviceCode) => ( + + + + ))} + {app.services.length > 10 && ( + +{app.services.length - 10} + )} + + )} + {docs === 'snowflake' && (app.features?.length ?? 0) > 0 && ( + + {(app.features as string[]).slice(0, 10).map((feature) => ( + {feature} + ))} + {(app.features as string[]).length > 10 && ( + +{(app.features as string[]).length - 10} + )} + + )} {app.description} @@ -73,11 +92,13 @@ export const ApplicationsShowcase: React.FC = ({ applications, services, integrations, + docs, }) => { const [filters, setFilters] = useState({ services: [], useCases: [], integrations: [], + features: [], }); const [searchTerm, setSearchTerm] = useState(''); @@ -89,6 +110,13 @@ export const ApplicationsShowcase: React.FC = ({ return Array.from(allServices).sort((a, b) => (services[a] || a).localeCompare(services[b] || b)); }, [applications, services]); + const uniqueFeatures = useMemo(() => { + const allFeatures = new Set( + applications.flatMap(app => (app.features ?? [])) + ); + return Array.from(allFeatures).sort(); + }, [applications]); + const uniqueUseCases = useMemo(() => { const allUseCases = new Set(applications.flatMap(app => app.useCases)); return Array.from(allUseCases).sort(); @@ -109,13 +137,19 @@ export const ApplicationsShowcase: React.FC = ({ app.name.toLowerCase().includes(searchLower) || app.description.toLowerCase().includes(searchLower) || app.useCases.some(useCase => useCase.toLowerCase().includes(searchLower)) || - app.services.some(service => (services[service] || service).toLowerCase().includes(searchLower)) || + (docs === 'aws' && app.services.some(service => (services[service] || service).toLowerCase().includes(searchLower))) || + (docs === 'snowflake' && (app.features ?? []).some(feature => feature.toLowerCase().includes(searchLower))) || app.integrations.some(integration => (integrations[integration] || integration).toLowerCase().includes(searchLower)); if (!matchesSearch) return false; } // Other filters - if (filters.services.length > 0 && !filters.services.some(service => app.services.includes(service))) return false; + if (docs === 'aws') { + if (filters.services.length > 0 && !filters.services.some(service => app.services.includes(service))) return false; + } else if (docs === 'snowflake') { + const appFeatures = app.features ?? []; + if (filters.features.length > 0 && !filters.features.some(feature => appFeatures.includes(feature))) return false; + } if (filters.useCases.length > 0 && !filters.useCases.some(useCase => app.useCases.includes(useCase))) return false; if (filters.integrations.length > 0 && !filters.integrations.some(integration => app.integrations.includes(integration))) return false; @@ -126,7 +160,7 @@ export const ApplicationsShowcase: React.FC = ({ return filtered.sort((a, b) => { return a.name.localeCompare(b.name); }); - }, [applications, filters, searchTerm, sortBy, services, integrations]); + }, [applications, filters, searchTerm, sortBy, services, integrations, docs]); const isSingleResult = filteredApplications.length === 1; const gridStyle = useMemo(() => ({ @@ -148,6 +182,7 @@ export const ApplicationsShowcase: React.FC = ({ services: [], useCases: [], integrations: [], + features: [], }); setSearchTerm(''); }; @@ -155,6 +190,7 @@ export const ApplicationsShowcase: React.FC = ({ const hasActiveFilters = filters.services.length > 0 || filters.useCases.length > 0 || filters.integrations.length > 0 || + filters.features.length > 0 || searchTerm.length > 0; return ( @@ -401,6 +437,22 @@ export const ApplicationsShowcase: React.FC = ({ font-size: 0.875rem; } + .feature-pills { + display: flex; + gap: 0.375rem; + flex-wrap: wrap; + margin: 0 0 0.75rem 0; + } + + .feature-pill { + padding: 0.25rem 0.5rem; + background: var(--sl-color-bg); + border: 1px solid var(--sl-color-gray-6); + border-radius: 0.25rem; + font-size: 0.75rem; + color: var(--sl-color-gray-3); + } + .card-footer { display: flex; justify-content: flex-start; @@ -557,21 +609,39 @@ export const ApplicationsShowcase: React.FC = ({ )} - setFilters(prev => ({ - ...prev, - services: e.target.value ? [e.target.value] : [] - }))} - className="filter-select" - > - Services - {uniqueServices.map((service) => ( - - {services[service] || service} - - ))} - + {docs === 'aws' ? ( + setFilters(prev => ({ + ...prev, + services: e.target.value ? [e.target.value] : [] + }))} + className="filter-select" + > + Services + {uniqueServices.map((service) => ( + + {services[service] || service} + + ))} + + ) : ( + setFilters(prev => ({ + ...prev, + features: e.target.value ? [e.target.value] : [] + }))} + className="filter-select" + > + Features + {uniqueFeatures.map((feature) => ( + + {feature} + + ))} + + )} = ({ app={app} services={services} integrations={integrations} + docs={docs} /> ))} diff --git a/src/content/docs/aws/sample-apps.mdx b/src/content/docs/aws/sample-apps.mdx index 5a0f92db..f82128a6 100644 --- a/src/content/docs/aws/sample-apps.mdx +++ b/src/content/docs/aws/sample-apps.mdx @@ -8,4 +8,4 @@ sidebar: import ApplicationsShowcase from "../../../components/ApplicationsShowcase.astro"; - + diff --git a/src/content/docs/snowflake/sample-apps.mdx b/src/content/docs/snowflake/sample-apps.mdx new file mode 100644 index 00000000..1ee82116 --- /dev/null +++ b/src/content/docs/snowflake/sample-apps.mdx @@ -0,0 +1,13 @@ +--- +title: Sample Apps +description: Sample Apps to help LocalStack for Snowflake users adopt real-world scenarios to rapidly and conveniently create, configure, and test applications locally. +template: doc +sidebar: + order: 4 +--- + +import ApplicationsShowcase from "../../../components/ApplicationsShowcase.astro"; + + + + diff --git a/src/data/developerhub/applications.json b/src/data/developerhub/applications.json index 60325048..dfd63c50 100644 --- a/src/data/developerhub/applications.json +++ b/src/data/developerhub/applications.json @@ -1,6 +1,7 @@ { "applications": [ { + "docs": "aws", "name": "Chaos Testing a Serverless Product Management App with LocalStack", "description": "Demonstrates chaos engineering in a multi-region serverless application using API Gateway, Lambda, DynamoDB, and Route53. Test resilience, automated failover, and data integrity with LocalStack's Chaos API.", "githubUrl": "https://github.com/localstack-samples/sample-chaos-serverless-multi-region-failover", @@ -10,6 +11,7 @@ "useCases": ["Chaos Engineering", "Route53 DNS Failover", "Multi-Region Resiliency"] }, { + "docs": "aws", "name": "Serverless Image Resizer with Lambda and S3 on LocalStack", "description": "A serverless application that automatically resizes images on S3 upload using Lambda. Showcases Lambda hot reloading for rapid development, integration testing, and S3 event-driven architecture on LocalStack.", "githubUrl": "https://github.com/localstack-samples/sample-lambda-s3-image-resizer-hot-reload", @@ -19,6 +21,7 @@ "useCases": ["Lambda DevX", "Event-Driven Architecture", "Integration Testing"] }, { + "docs": "aws", "name": "Initializing an RDS Database with AWS CDK and LocalStack", "description": "Demonstrates how to initialize and pre-seed an RDS database using AWS CDK and a Lambda custom resource on LocalStack. Highlights Cloud Pods for creating instant, pre-configured database environments.", "githubUrl": "https://github.com/localstack-samples/sample-cdk-rds-database-initialization", @@ -28,6 +31,7 @@ "useCases": ["Pre-seeding Databases", "Cloud Pods"] }, { + "docs": "aws", "name": "Real-time Database Replication with DMS and Kinesis on LocalStack", "description": "Showcases real-time data replication from RDS (MariaDB) to Kinesis using AWS DMS on LocalStack. Implements both full-load and Change Data Capture (CDC) tasks to stream database changes for analytics.", "githubUrl": "https://github.com/localstack-samples/sample-dms-cdc-rds-to-kinesis", @@ -37,6 +41,7 @@ "useCases": ["Change Data Capture (CDC)", "Database Migration"] }, { + "docs": "aws", "name": "Full-Stack Serverless Development with Terraform on LocalStack", "description": "A full-stack shipment management app (React, Spring Boot) using S3, Lambda, and DynamoDB. Demonstrates IaC testing with Terraform & Testcontainers and highlights AWS parity for seamless local development on LocalStack.", "githubUrl": "https://github.com/localstack-samples/sample-terraform-fullstack-serverless-shipment-app", @@ -46,6 +51,7 @@ "useCases": ["IaC Testing", "AWS Parity"] }, { + "docs": "aws", "name": "Developing and Debugging ECS Fargate Apps with LocalStack", "description": "Deploy a containerized Node.js app on ECS Fargate with an ALB using AWS CDK. Showcases ECS code-mounting for hot-reloading and remote debugging capabilities for rapid development cycles on LocalStack.", "githubUrl": "https://github.com/localstack-samples/sample-cdk-ecs-fargate-hot-reloading-debugging", @@ -55,6 +61,7 @@ "useCases": ["ECS Code-Mounting", "ECS Debugging"] }, { + "docs": "aws", "name": "Querying a Data Lake on S3 with Athena & Glue on LocalStack", "description": "Build and test a data analytics pipeline to query structured data in S3 using Athena and Glue on LocalStack. Demonstrates local Big Data testing and using the Resource Browser for interactive SQL queries.", "githubUrl": "https://github.com/localstack-samples/sample-athena-glue-s3-data-lake-query", @@ -64,6 +71,7 @@ "useCases": ["Resource Browsers", "Big Data Testing"] }, { + "docs": "aws", "name": "Developing & testing a Serverless Quiz App with LocalStack", "description": "A complete serverless quiz application showcasing the entire LocalStack platform. Demonstrates Cloud Pods, Chaos Engineering, IAM Policy Stream, Ephemeral Instances, and end-to-end integration testing.", "githubUrl": "https://github.com/localstack-samples/sample-serverless-quiz-app", @@ -71,6 +79,26 @@ "services": ["ddb", "sqs", "lmb", "agw", "sns", "ebr", "stf", "cfr", "s3", "iam"], "integrations": ["cdk", "awscli", "boto3"], "useCases": ["Resource Browsers", "Integration Testing", "Extensions","Cloud Pods", "Chaos Engineering", "IAM Policy Stream", "Ephemeral Instances"] + }, + { + "docs": "snowflake", + "name": "Developing and Testing a Smart Factory Monitoring App with LocalStack", + "description": "Comprehensive smart factory monitoring application specifically designed to demonstrate LocalStack for Snowflake capabilities for local data cloud development, debugging, and testing", + "teaser": "snowflake-smart-factory-app-architecture.png", + "githubUrl": "https://github.com/localstack-samples/snowflake-smart-factory-app/", + "features": ["Native Apps", "External Volumes", "Snowpipe", "User-Defined Functions", "Streams", "Streamlit", "Stages"], + "integrations": ["Snowflake CLI", "dbt", "S3", "Dagster", "GitHub Actions"], + "useCases": ["Data Ingestion", "Dashboards", "Data Transformation", "Data Testing"] + }, + { + "docs": "snowflake", + "name": "Integrating Apache Polaris with LocalStack for Snowflake and Trino", + "description": "Demonstrates how to integrate Apache Polaris with LocalStack for Snowflake and Trino to create a unified data catalog for your data assets for local development and testing.", + "githubUrl": "https://github.com/localstack-samples/polaris-demo", + "teaser": "snowflake-polaris-trino-architecture.png", + "features": ["Polaris Catalog", "Iceberg Tables", "External Volumes"], + "integrations": ["Trino", "Polaris", "S3"], + "useCases": ["Data Catalog", "Data Testing", "Metadata Management"] } ] } diff --git a/src/data/developerhub/integrations.json b/src/data/developerhub/integrations.json index c7795919..82299408 100644 --- a/src/data/developerhub/integrations.json +++ b/src/data/developerhub/integrations.json @@ -12,5 +12,6 @@ "docker-compose": "Docker Compose", "testcontainers": "Testcontainers", "helm-charts": "Helm Charts", - "pulumi": "Pulumi" + "pulumi": "Pulumi", + "snowcli": "Snow CLI" }
{app.description}