Skip to content

Commit

Permalink
Display AI Studio links in endpoints list (#3922)
Browse files Browse the repository at this point in the history
Adds links for AI studio workspace and deployment within the AI endpoint service target endpoints list.
  • Loading branch information
wbreza authored May 20, 2024
1 parent 531d630 commit 90cf522
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 8 deletions.
26 changes: 24 additions & 2 deletions cli/azd/pkg/ai/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"fmt"
)

// AzureAiStudioLink returns a link to the Azure AI Studio for the given tenant, subscription, resource group, and workspace
func AzureAiStudioLink(tenantId string, subscriptionId string, resourceGroup string, workspaceName string) string {
// AiStudioWorkspaceLink returns a link to the Azure AI Studio workspace page
func AiStudioWorkspaceLink(tenantId string, subscriptionId string, resourceGroup string, workspaceName string) string {
return fmt.Sprintf(
//nolint:lll
"https://ai.azure.com/build/overview?tid=%s&wsid=/subscriptions/%s/resourcegroups/%s/providers/Microsoft.MachineLearningServices/workspaces/%s",
Expand All @@ -15,3 +15,25 @@ func AzureAiStudioLink(tenantId string, subscriptionId string, resourceGroup str
workspaceName,
)
}

// AzureAiStudioDeploymentLink returns a link to the Azure AI Studio deployment page
func AiStudioDeploymentLink(
tenantId string,
subscriptionId string,
resourceGroup string,
workspaceName string,
endpointName string,
deploymentName string,
) string {
return fmt.Sprintf(
//nolint:lll
"https://ai.azure.com/projectdeployments/realtime/%s/%s/detail?wsid=/subscriptions/%s/resourceGroups/%s/providers/Microsoft.MachineLearningServices/workspaces/%s&tid=%s&deploymentName=%s",
endpointName,
deploymentName,
subscriptionId,
resourceGroup,
workspaceName,
tenantId,
deploymentName,
)
}
19 changes: 17 additions & 2 deletions cli/azd/pkg/ai/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,30 @@ import (
"github.com/stretchr/testify/require"
)

func Test_AzureAiStudioLink(t *testing.T) {
func Test_AiStudioLink(t *testing.T) {
tenantId := "tenantId"
subscriptionId := "subscriptionId"
resourceGroup := "resourceGroup"
workspaceName := "workspaceName"

//nolint:lll
expected := "https://ai.azure.com/build/overview?tid=tenantId&wsid=/subscriptions/subscriptionId/resourcegroups/resourceGroup/providers/Microsoft.MachineLearningServices/workspaces/workspaceName"
actual := AzureAiStudioLink(tenantId, subscriptionId, resourceGroup, workspaceName)
actual := AiStudioWorkspaceLink(tenantId, subscriptionId, resourceGroup, workspaceName)

require.Equal(t, expected, actual)
}

func Test_AiStudioDeploymentLink(t *testing.T) {
tenantId := "tenantId"
subscriptionId := "subscriptionId"
resourceGroup := "resourceGroup"
workspaceName := "workspaceName"
endpointName := "endpointName"
deploymentName := "deploymentName"

//nolint:lll
expected := "https://ai.azure.com/projectdeployments/realtime/endpointName/deploymentName/detail?wsid=/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.MachineLearningServices/workspaces/workspaceName&tid=tenantId&deploymentName=deploymentName"
actual := AiStudioDeploymentLink(tenantId, subscriptionId, resourceGroup, workspaceName, endpointName, deploymentName)

require.Equal(t, expected, actual)
}
16 changes: 15 additions & 1 deletion cli/azd/pkg/project/service_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package project
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"time"

"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/output/ux"
)

// Some endpoints include a discriminator suffix that should be displayed instead of the default 'Endpoint' label.
var endpointPattern = regexp.MustCompile(`(.+):\s(.+)`)

// ServiceLifecycleEventArgs are the event arguments available when
// any service lifecycle event has been triggered
type ServiceLifecycleEventArgs struct {
Expand Down Expand Up @@ -112,7 +116,17 @@ func (spr *ServiceDeployResult) ToString(currentIndentation string) string {
builder.WriteString(fmt.Sprintf("%s- No endpoints were found\n", currentIndentation))
} else {
for _, endpoint := range spr.Endpoints {
builder.WriteString(fmt.Sprintf("%s- Endpoint: %s\n", currentIndentation, output.WithLinkFormat(endpoint)))
label := "Endpoint"
url := endpoint

// When the endpoint pattern is matched used the first sub match as the endpoint label.
matches := endpointPattern.FindStringSubmatch(endpoint)
if len(matches) == 3 {
label = matches[1]
url = matches[2]
}

builder.WriteString(fmt.Sprintf("%s- %s: %s\n", currentIndentation, label, output.WithLinkFormat(url)))
}
}

Expand Down
47 changes: 45 additions & 2 deletions cli/azd/pkg/project/service_target_ai_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,21 +217,64 @@ func (m *aiEndpointTarget) Endpoints(
return nil, fmt.Errorf("failed initializing AI project: %w", err)
}

tenantId, has := m.env.LookupEnv(environment.TenantIdEnvVarName)
if !has {
return nil, fmt.Errorf(
"tenant ID not found. Ensure %s has been set in the environment.",
environment.TenantIdEnvVarName,
)
}

workspaceScope, err := m.getWorkspaceScope(serviceConfig, targetResource)
if err != nil {
return nil, err
}

workspaceLink := ai.AiStudioWorkspaceLink(
tenantId,
workspaceScope.SubscriptionId(),
workspaceScope.ResourceGroup(),
workspaceScope.Workspace(),
)

endpoints := []string{
fmt.Sprintf("Workspace: %s", workspaceLink),
}

endpointName := filepath.Base(targetResource.ResourceName())
onlineEndpoint, err := m.aiHelper.GetEndpoint(ctx, workspaceScope, endpointName)
if err != nil {
return nil, err
}

return []string{
var deploymentName string
for key, value := range onlineEndpoint.Properties.Traffic {
if *value == 100 {
deploymentName = key
break
}
}

if deploymentName != "" {
deploymentLink := ai.AiStudioDeploymentLink(
tenantId,
workspaceScope.SubscriptionId(),
workspaceScope.ResourceGroup(),
workspaceScope.Workspace(),
endpointName,
deploymentName,
)

endpoints = append(endpoints, fmt.Sprintf("Deployment: %s", deploymentLink))
}

endpoints = append(
endpoints,
fmt.Sprintf("Scoring: %s", *onlineEndpoint.Properties.ScoringURI),
fmt.Sprintf("Swagger: %s", *onlineEndpoint.Properties.SwaggerURI),
}, nil
)

return endpoints, nil
}

// getWorkspaceScope returns the scope for the workspace
Expand Down
6 changes: 5 additions & 1 deletion cli/azd/pkg/project/service_target_ai_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func Test_MlEndpointTarget_Deploy(t *testing.T) {
mockContext.Clock.Set(time.Now())
env := environment.NewWithValues("test", map[string]string{
AiProjectNameEnvVarName: "AI_WORKSPACE",
environment.TenantIdEnvVarName: "TENANT_ID",
environment.SubscriptionIdEnvVarName: "SUBSCRIPTION_ID",
environment.ResourceGroupEnvVarName: "RESOURCE_GROUP",
})
Expand Down Expand Up @@ -85,6 +86,9 @@ func Test_MlEndpointTarget_Deploy(t *testing.T) {
Properties: &armmachinelearning.OnlineEndpointProperties{
ScoringURI: convert.RefOf("https://SCRORING_URI"),
SwaggerURI: convert.RefOf("https://SWAGGER_URI"),
Traffic: map[string]*int32{
deploymentName: convert.RefOf(int32(100)),
},
},
}

Expand Down Expand Up @@ -131,7 +135,7 @@ func Test_MlEndpointTarget_Deploy(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, deployResult)
require.IsType(t, &AiEndpointDeploymentResult{}, deployResult.Details)
require.Len(t, deployResult.Endpoints, 2)
require.Len(t, deployResult.Endpoints, 4)

deploymentDetails := deployResult.Details.(*AiEndpointDeploymentResult)

Expand Down

0 comments on commit 90cf522

Please sign in to comment.