Skip to content

Commit 9647011

Browse files
authored
add new snowflake samples page (#295)
1 parent a5ebd0c commit 9647011

File tree

9 files changed

+191
-51
lines changed

9 files changed

+191
-51
lines changed

astro.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,10 @@ export default defineConfig({
488488
collapsed: true,
489489
autogenerate: { directory: '/snowflake/features' },
490490
},
491+
{
492+
label: 'Sample Apps',
493+
slug: 'snowflake/sample-apps',
494+
},
491495
{
492496
label: 'Capabilities',
493497
collapsed: true,
336 KB
Loading
377 KB
Loading

src/components/ApplicationsShowcase.astro

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,62 @@
22
import { ApplicationsShowcase } from './applications/ApplicationsShowcase';
33
import { getImage } from 'astro:assets';
44
5+
interface Props { docs?: 'aws' | 'snowflake' }
6+
const { docs = 'aws' } = Astro.props as Props;
7+
58
// Import data
69
import applicationsData from '../data/developerhub/applications.json';
710
import services from '../data/developerhub/services.json';
811
import integrations from '../data/developerhub/integrations.json';
912
10-
const applications = applicationsData.applications;
13+
const allApplications = applicationsData.applications;
14+
const applications = allApplications.filter((app: any) => app.docs === docs);
1115
12-
const images = import.meta.glob<{ default: ImageMetadata }>(
13-
'/src/assets/images/aws/sample-apps/*.{jpeg,jpg,png,gif}'
16+
const awsImages = import.meta.glob<{ default: ImageMetadata }>(
17+
'/src/assets/images/aws/sample-apps/*.{jpeg,jpg,png,gif,svg}'
18+
);
19+
const snowflakeImages = import.meta.glob<{ default: ImageMetadata }>(
20+
'/src/assets/images/snowflake/sample-apps/*.{jpeg,jpg,png,gif,svg}'
1421
);
1522
1623
const applicationsUpdated = await Promise.all(
17-
applications.map(async (application) => {
24+
applications.map(async (application: any) => {
1825
const updatedApplication = { ...application };
19-
const imagePath = `/src/assets/images/aws/sample-apps/${application.teaser}`;
20-
21-
if (images[imagePath]) {
22-
const optimizedLeadImage = await getImage({
23-
src: images[imagePath](),
24-
format: 'png',
25-
width: 800,
26-
quality: 90,
27-
});
28-
updatedApplication.teaser = optimizedLeadImage.src;
26+
if (docs === 'aws') {
27+
const imagePath = `/src/assets/images/aws/sample-apps/${application.teaser}`;
28+
if (awsImages[imagePath]) {
29+
const optimizedLeadImage = await getImage({
30+
src: awsImages[imagePath](),
31+
format: 'png',
32+
width: 800,
33+
quality: 90,
34+
});
35+
updatedApplication.teaser = optimizedLeadImage.src;
36+
}
37+
} else if (docs === 'snowflake') {
38+
const teaserName = String(application.teaser || '').split('/').pop();
39+
const imagePath = teaserName ? `/src/assets/images/snowflake/sample-apps/${teaserName}` : '';
40+
if (teaserName && snowflakeImages[imagePath]) {
41+
const optimizedLeadImage = await getImage({
42+
src: snowflakeImages[imagePath](),
43+
format: 'png',
44+
width: 800,
45+
quality: 90,
46+
});
47+
updatedApplication.teaser = optimizedLeadImage.src;
48+
} else if (teaserName) {
49+
updatedApplication.teaser = `/images/snowflake/sample-apps/${teaserName}`;
50+
}
2951
}
3052
return updatedApplication;
3153
})
3254
);
33-
---
3455
56+
---
3557
<ApplicationsShowcase
3658
applications={applicationsUpdated}
3759
services={services}
3860
integrations={integrations}
61+
docs={docs}
3962
client:load
4063
/>

src/components/applications/ApplicationsShowcase.tsx

Lines changed: 105 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,40 @@ import React, { useState, useMemo } from 'react';
33
interface Application {
44
name: string;
55
description: string;
6-
githubUrl: string;
6+
githubUrl?: string;
7+
docsUrl?: string;
78
teaser: string;
89
services: string[];
910
integrations: string[];
1011
useCases: string[];
12+
// Snowflake-only
13+
features?: string[];
1114
}
1215

1316
interface FilterState {
1417
services: string[];
1518
useCases: string[];
1619
integrations: string[];
20+
// Snowflake-only
21+
features: string[];
1722
}
1823

1924
interface ApplicationsShowcaseProps {
2025
applications: Application[];
2126
services: Record<string, string>;
2227
integrations: Record<string, string>;
28+
docs?: 'aws' | 'snowflake';
2329
}
2430

2531
const ApplicationCard: React.FC<{
2632
app: Application;
2733
services: Record<string, string>;
2834
integrations: Record<string, string>;
29-
}> = ({ app, services, integrations }) => {
35+
docs?: 'aws' | 'snowflake';
36+
}> = ({ app, services, integrations, docs }) => {
3037
return (
3138
<a
32-
href={app.githubUrl}
39+
href={app.githubUrl || app.docsUrl || '#'}
3340
target="_blank"
3441
rel="noopener noreferrer"
3542
className="app-card"
@@ -43,19 +50,31 @@ const ApplicationCard: React.FC<{
4350

4451
<div className="card-content">
4552
<h3 className="card-title">{app.name}</h3>
46-
<div className="service-icons">
47-
{app.services.slice(0, 10).map((serviceCode) => (
48-
<div key={serviceCode} className="service-icon" title={services[serviceCode] || serviceCode}>
49-
<img
50-
src={`/images/aws/${serviceCode}.svg`}
51-
alt={services[serviceCode] || serviceCode}
52-
/>
53-
</div>
54-
))}
55-
{app.services.length > 10 && (
56-
<div className="service-more">+{app.services.length - 10}</div>
57-
)}
58-
</div>
53+
{docs === 'aws' && (
54+
<div className="service-icons">
55+
{app.services.slice(0, 10).map((serviceCode) => (
56+
<div key={serviceCode} className="service-icon" title={services[serviceCode] || serviceCode}>
57+
<img
58+
src={`/images/aws/${serviceCode}.svg`}
59+
alt={services[serviceCode] || serviceCode}
60+
/>
61+
</div>
62+
))}
63+
{app.services.length > 10 && (
64+
<div className="service-more">+{app.services.length - 10}</div>
65+
)}
66+
</div>
67+
)}
68+
{docs === 'snowflake' && (app.features?.length ?? 0) > 0 && (
69+
<div className="feature-pills">
70+
{(app.features as string[]).slice(0, 10).map((feature) => (
71+
<span key={feature} className="feature-pill">{feature}</span>
72+
))}
73+
{(app.features as string[]).length > 10 && (
74+
<div className="service-more">+{(app.features as string[]).length - 10}</div>
75+
)}
76+
</div>
77+
)}
5978
<p className="card-description">{app.description}</p>
6079

6180
<div className="card-footer">
@@ -73,11 +92,13 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
7392
applications,
7493
services,
7594
integrations,
95+
docs,
7696
}) => {
7797
const [filters, setFilters] = useState<FilterState>({
7898
services: [],
7999
useCases: [],
80100
integrations: [],
101+
features: [],
81102
});
82103

83104
const [searchTerm, setSearchTerm] = useState('');
@@ -89,6 +110,13 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
89110
return Array.from(allServices).sort((a, b) => (services[a] || a).localeCompare(services[b] || b));
90111
}, [applications, services]);
91112

113+
const uniqueFeatures = useMemo(() => {
114+
const allFeatures = new Set(
115+
applications.flatMap(app => (app.features ?? []))
116+
);
117+
return Array.from(allFeatures).sort();
118+
}, [applications]);
119+
92120
const uniqueUseCases = useMemo(() => {
93121
const allUseCases = new Set(applications.flatMap(app => app.useCases));
94122
return Array.from(allUseCases).sort();
@@ -109,13 +137,19 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
109137
app.name.toLowerCase().includes(searchLower) ||
110138
app.description.toLowerCase().includes(searchLower) ||
111139
app.useCases.some(useCase => useCase.toLowerCase().includes(searchLower)) ||
112-
app.services.some(service => (services[service] || service).toLowerCase().includes(searchLower)) ||
140+
(docs === 'aws' && app.services.some(service => (services[service] || service).toLowerCase().includes(searchLower))) ||
141+
(docs === 'snowflake' && (app.features ?? []).some(feature => feature.toLowerCase().includes(searchLower))) ||
113142
app.integrations.some(integration => (integrations[integration] || integration).toLowerCase().includes(searchLower));
114143
if (!matchesSearch) return false;
115144
}
116145

117146
// Other filters
118-
if (filters.services.length > 0 && !filters.services.some(service => app.services.includes(service))) return false;
147+
if (docs === 'aws') {
148+
if (filters.services.length > 0 && !filters.services.some(service => app.services.includes(service))) return false;
149+
} else if (docs === 'snowflake') {
150+
const appFeatures = app.features ?? [];
151+
if (filters.features.length > 0 && !filters.features.some(feature => appFeatures.includes(feature))) return false;
152+
}
119153
if (filters.useCases.length > 0 && !filters.useCases.some(useCase => app.useCases.includes(useCase))) return false;
120154
if (filters.integrations.length > 0 && !filters.integrations.some(integration => app.integrations.includes(integration))) return false;
121155

@@ -126,7 +160,7 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
126160
return filtered.sort((a, b) => {
127161
return a.name.localeCompare(b.name);
128162
});
129-
}, [applications, filters, searchTerm, sortBy, services, integrations]);
163+
}, [applications, filters, searchTerm, sortBy, services, integrations, docs]);
130164

131165
const isSingleResult = filteredApplications.length === 1;
132166
const gridStyle = useMemo<React.CSSProperties>(() => ({
@@ -148,13 +182,15 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
148182
services: [],
149183
useCases: [],
150184
integrations: [],
185+
features: [],
151186
});
152187
setSearchTerm('');
153188
};
154189

155190
const hasActiveFilters = filters.services.length > 0 ||
156191
filters.useCases.length > 0 ||
157192
filters.integrations.length > 0 ||
193+
filters.features.length > 0 ||
158194
searchTerm.length > 0;
159195

160196
return (
@@ -401,6 +437,22 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
401437
font-size: 0.875rem;
402438
}
403439
440+
.feature-pills {
441+
display: flex;
442+
gap: 0.375rem;
443+
flex-wrap: wrap;
444+
margin: 0 0 0.75rem 0;
445+
}
446+
447+
.feature-pill {
448+
padding: 0.25rem 0.5rem;
449+
background: var(--sl-color-bg);
450+
border: 1px solid var(--sl-color-gray-6);
451+
border-radius: 0.25rem;
452+
font-size: 0.75rem;
453+
color: var(--sl-color-gray-3);
454+
}
455+
404456
.card-footer {
405457
display: flex;
406458
justify-content: flex-start;
@@ -557,21 +609,39 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
557609
)}
558610
</div>
559611

560-
<select
561-
value={filters.services[0] || ''}
562-
onChange={(e) => setFilters(prev => ({
563-
...prev,
564-
services: e.target.value ? [e.target.value] : []
565-
}))}
566-
className="filter-select"
567-
>
568-
<option value="">Services</option>
569-
{uniqueServices.map((service) => (
570-
<option key={service} value={service}>
571-
{services[service] || service}
572-
</option>
573-
))}
574-
</select>
612+
{docs === 'aws' ? (
613+
<select
614+
value={filters.services[0] || ''}
615+
onChange={(e) => setFilters(prev => ({
616+
...prev,
617+
services: e.target.value ? [e.target.value] : []
618+
}))}
619+
className="filter-select"
620+
>
621+
<option value="">Services</option>
622+
{uniqueServices.map((service) => (
623+
<option key={service} value={service}>
624+
{services[service] || service}
625+
</option>
626+
))}
627+
</select>
628+
) : (
629+
<select
630+
value={(filters.features?.[0] as string) || ''}
631+
onChange={(e) => setFilters(prev => ({
632+
...prev,
633+
features: e.target.value ? [e.target.value] : []
634+
}))}
635+
className="filter-select"
636+
>
637+
<option value="">Features</option>
638+
{uniqueFeatures.map((feature) => (
639+
<option key={feature} value={feature}>
640+
{feature}
641+
</option>
642+
))}
643+
</select>
644+
)}
575645

576646
<select
577647
value={filters.useCases[0] || ''}
@@ -624,6 +694,7 @@ export const ApplicationsShowcase: React.FC<ApplicationsShowcaseProps> = ({
624694
app={app}
625695
services={services}
626696
integrations={integrations}
697+
docs={docs}
627698
/>
628699
))}
629700

src/content/docs/aws/sample-apps.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ sidebar:
88

99
import ApplicationsShowcase from "../../../components/ApplicationsShowcase.astro";
1010

11-
<ApplicationsShowcase />
11+
<ApplicationsShowcase docs="aws" />
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: Sample Apps
3+
description: Sample Apps to help LocalStack for Snowflake users adopt real-world scenarios to rapidly and conveniently create, configure, and test applications locally.
4+
template: doc
5+
sidebar:
6+
order: 4
7+
---
8+
9+
import ApplicationsShowcase from "../../../components/ApplicationsShowcase.astro";
10+
11+
<ApplicationsShowcase docs="snowflake" />
12+
13+

0 commit comments

Comments
 (0)